Webフロントエンドエンジニアの飯塚です。

ワントゥーテンが展開しているQURIOS FIELDというデジタルツイン系サービスのwebフロントエンド開発を担当してます。

7/1(土)、2(日)にはゴルフの「セガサミーカップ」でデジタルツインを活用した新しい観戦体験の提供として”ゆる”SEGA SAMMY CUPを公開しました。

“ゆる” SEGA SAMMY CUP

同サービスでは、「ページ」「UI」といったプロジェクトごとに異なる機能と、「3Dウォークスルー」「チャット」などプロジェクト共通の機能があり、それを1つのmonorepoのリポジトリで管理しています。

package managerはpnpm、フレームワークはNuxtを採用しています。

pnpmはnpmと違い、1つのpackageのversion違いを同じリポジトリで使用することができます。本開発では共通機能をversion管理することで、projectごとに異なるversionの共通機能を使えるようにしています。

今回はそのディレクトリ構造と共通packageのバージョン管理について説明します。

サンプルで作成したリポジトリは以下です。
https://github.com/112KA/nuxt-monorepo-example.git

– ディレクトリ構造

.
└── nuxt-monorepo-example/
    ├── packages/
    │   └── base-project/ - project共通の要素を含むpackage
    │       ├── dist/ - package単体のbuildで、ここに全fileがcompile&copyされる
    │       ├── public/
    │       ├── src/
    │       │   ├── components/
    │       │   ├── css/
    │       │   ├── layouts/
    │       │   ├── middleware/
    │       │   ├── pages/
    │       │   ├── plugins/
    │       │   ├── stores/
    │       │   └── module.ts
    │       └── nuxt.config.ts
    └── projects/
        ├── a/
        │   ├── pages/
        │   ├── public/
        │   ├── app.vue
        │   └── nuxt.config.ts
        └── b/
            ├── pages/
            ├── public/
            ├── app.vue
            └── nuxt.config.ts

projects/a, projects/bの2つから、packages/base-projectにある共通要素を読み込んで使用します。

packages/base-projectは、components, layouts, pages, css, middleware, plugins, stores(pinia), publicAssetsなどnuxtで再利用できるすべての要素を含んでいます。

componets/, pages/について、base-projectと読み込み側projectでファイルパスが被った場合、読み込み側projectの方が優先されます。
これにより、1つのprojectにだけボタンを追加したいなど、projectごとの処理変更がやりやすくなります。

– 環境構築のポイント

Docker環境にする

windows (wsl2)環境VSCodeでpnpm package参照がうまくできていない(syminkがつくれていない)。https://youtrack.jetbrains.com/issue/WEB-49919/WSL2-Packages-installed-using-pnpm-not-detected

そのため、windows環境でもpnpmが動くようにdocker環境を使用してます。

共通package(base-project)はpublishしてversion管理する

pnpm publishしたpacakgeをversion管理します。

version管理には後述のchangesetを利用しています。

これにより、packages/base-projectの要素が更新されても、固定versionを指定したprojectでは影響を受けなくなります。

開発中プロジェクトについては更新をwatchして都度buildしたものを参照します。

– 共通package moduleの設定

nuxtのmuduleの仕組みを使って、それぞれの共通要素を各プロジェクトに追加していきます。

packages/base-project/src/module.ts

const module: NuxtModule = defineNuxtModule({
  setup(_, nuxt) {

    const { resolve } = createResolver(import.meta.url)


    // ----- 共通components
    nuxt.hook('components:dirs', (dirs) => {
      dirs.unshift({
        path: resolve('components'),
      })
    })


    // ----- 共通pages
    extendPages((pages) => {
      pages.unshift(
    //NOTE: 1ページずつ追加しています
        { name: 'second', path: '/second/', file: resolve('pages/second.vue') },
      )
    })


    // ----- 共通middleware
    addRouteMiddleware({ name: 'name', path: resolve('middleware/redirect.global.js'), global: true })


    // ----- 共通plugin
    addPlugin(resolve('plugins/examplePlugin.js'))


    // ----- 共通css
    //NOTE: nuxt.config.tsで未定義だったらファイル追加
    if (nuxt.options.css.length === 0) {
      nuxt.options.css.push(resolve('css/global.scss'))
    }

    // ----- 共通layout
    addLayout({ src: resolve('layouts/default.vue') }, 'default')

    // ----- public
    nuxt.options.nitro.publicAssets = nuxt.options.nitro.publicAssets ?? []
    nuxt.options.nitro.publicAssets.push({
      baseURL: '',
      dir: resolve('../public'),
    })
  },
})

publicAssets

    // ----- public
    nuxt.options.nitro.publicAssets = nuxt.options.nitro.publicAssets ?? []
    nuxt.options.nitro.publicAssets.push({
      baseURL: '',
      dir: resolve('../public'),
    })

module optionなどパラメタ分岐すれば、projectごとに追加するディレクトリを分けることができます。

QURIOS FIELDでは、アバターモデル素材はprojectごとに共通で使うものがあったり、特定のプロジェクトでしかつかわないものがあったりするので、moduleでプロジェクトごとに動的に追加するようにしています。

piniaのstoresディレクトリ

piniaについては、読み込み側projectのnuxt.config.tsのaliasに’base-project’を設定して、aliasのpathからstoreをimportするようにしています。

packages/b/nuxt.config.ts

//aliasの設定
export default defineNuxtConfig({
...
alias: {
      'base-project': path.join(__dirname, `node_modules/base-project/dist`),
    },
...
}
//import例
import { useApplicationStore } from 'base-project/stores/index'

– 共通packageのversion管理

設定

.npmrc

//npm.pkg.github.com/:_authToken=%%PERSONAL_ACCESS_TOKEN%%

exampleリポジトリには.npmrc.orgという名前で.npmrcの元ファイルをcommitしています。

流用する場合は、.npmrcにrenameして、%%PERSONAL_ACCESS_TOKEN%%の文字列を自身のGitHub personal access tokenで置換してください。

開発中projectのversion指定

/projects/a/package.json

{
  ...
  "dependencies": {
    "base-project": "workspace:@112ka/example-base-project@*"
  },
  ...
}

該当packageのversionをworkspace:で指定する

package versionを固定したいprojectのversion指定

/projects/b/package.json

{
  ...
  "dependencies": {
    "base-project": "npm:@112ka/example-base-project@1.0.0"
  },
  ...
}

該当packageのversionをnpm:で指定する

changesets

package version管理は、pnpm公式で推奨しているchangesetsを使います。

install方法、基本的な使い方はこちらを参照してください。

更新があったら、

  1. pnpm build
  2. pnpm changesetで[major/minor/patch]を選択
  3. pnpm changeset versionでversion作成
  4. pnpm publishでpublish

– 終わりに

参考にさせていただいたnuxt3 monorepo記事のmodule指定のやり方が古かったり、なかなか全要素の指定がまとめて書かれたものが見当たらなかったので記事化しました。

半分は社内共有の意図だったりします。

自身が元々nuxtに慣れていたり、前任者からソースを引き継いだ流れで現在はnuxtを採用していますが、なるべくnuxtに依存しないつくりに変えていきたいと思っています。

参考



■ワントゥーテンでは中途採用募集中です!

1→10(ワントゥーテン)のカルチャーや、作品のクリエイティブに共感し、自身のより高い成長を求めている方からのご応募をお待ちしています!