pnpm + Turborepo で Nuxt アプリの monorepo 開発環境をつくる
- 投稿した日
- 更新した日
- 書いたひと
- ひらたけ
私のこのウェブサイトをつくるときに、pnpm と Turborepo を使って Nuxt の monorepo 環境を構築したので、備忘録的に残しておこうと思います。
手っ取り早く環境をつくりたい
テンプレートリポジトリをつくりました。 GitHub でリポジトリを開いて「Use this template」から利用できます ので、もしよろしければ使ってください。
もし不具合とか追加で設定しておいたほうがよさげなものとかあれば教えてください🙏
環境
今回作業した環境は以下のとおりです。
ディレクトリ構造
最終的な monorepo 開発環境のディレクトリ構造は以下のとおりです(上にある テンプレートリポジトリ とだいたい一緒です)。
monorepo/
├ app/
│ ├ public/
│ ├ server/
│ ├ .eslintrc.js
│ ├ app.vue
│ ├ nuxt.config.ts
│ ├ package.json
│ └ tsconfig.json
├ packages/
│ └ eslint-config-custom/
│ ├ index.js
│ └ package.json
├ .gitignore
├ .npmrc
├ package.json
├ pnpm-lock.yaml
├ pnpm-workspace.yaml
└ turbo.json
Nuxt アプリケーションは app/
以下にあります。また、Turborepo が用意している例 を参考に、packages/
に ESLint の設定をまとめたパッケージをつくっています。
今回の説明では packages/
の中にあるパッケージは 1 つだけですが、任意のパッケージを複数いれることができます。
monorepo のベースを作成
monorepo 開発環境のベースをつくります。新しくリポジトリをつくり、以下のコマンドまたは手動でリポジトリのルートに package.json
を作成します。
$ pnpm init
続いて、pnpm のワークスペースの設定をしていきます。
npm や yarn では package.json
に workspaces
というプロパティを追加して設定しますが、pnpm では pnpm-workspace.yaml
というファイルをリポジトリのルートにつくり、そこに設定を書きます。今回は app/
と、packages/
の中にある各ディレクトリを指定します。
packages:
- 'app'
- 'packages/*'
Turborepo のインストール
Turborepo は、Next.js などを開発している Vercel 社がつくっているビルドシステムです。
今回の例ではあまり当てはまりませんが、複雑なウェブアプリケーションで、monorepo にたくさんのパッケージを追加し、それぞれのパッケージが同じ monorepo 内のパッケージを利用する場合に 「このアプリケーションは ○○ というパッケージのビルドを実行してからじゃないと動かすことが出来ない」 といったことが発生します。
また、たくさんのパッケージがあると、monorepo 全体のビルドを実行した場合に 何も変更をしていないパッケージまでビルドが実行されてしまい無駄な時間がかかってしまう 、などの問題も起こってしまいます。
そのあたりのいろいろを、いい感じにしてくれるのが Turborepo です。
以下のコマンドを実行して、Turborepo をインストールします。
$ pnpm add -wD turbo
pnpm のワークスペース機能をつかう場合、ルートのプロジェクトにパッケージをインストールする場合に -w
オプションをつけないと警告が表示されインストールができません。
個人的には、「うっかりルートのプロジェクトにパッケージをインストールしちゃった!」ということが多いので、オプションをつけないとインストールできないというのは助かっています。
インストールが完了したら、Turborepo の設定ファイル turbo.json
をリポジトリのルートに作成します。
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {}
}
Turborepo では .turbo
というディレクトリが作成され、その中にログファイルなどがつくられます。なので、このディレクトリを .gitignore
へ忘れずに追加するようにしましょう。
Nuxt アプリケーションを作成
Nuxt のアプリケーションをつくります。今回の monorepo 構成では app/
に Nuxt アプリケーションを置くことになっているので、まずはこのディレクトリを作成します。
ディレクトリを作成したら、そのディレクトリへ移動して新規 Nuxt アプリケーションを作成します。私はいつも公式ドキュメントのコマンドを実行して、全ファイルをカレントディレクトリへ移動してつくっています。
$ cd app
$ npx nuxi@latest init nuxt-app
$ mv nuxt-app/* .
新しく Nuxt アプリケーションを作成すると、.gitignore
や .npmrc
がつくられます。これらは、リポジトリのルートに移動するか、既にルートにあるファイルに内容を追加するなどしてください。
アプリケーションを作成できたら、リポジトリのルートへ戻ります。ルートの package.json
の scripts
に build
スクリプトを追加します。
{
"scripts": {
"build": "turbo run build" // 👈これを追加
},
...
}
turbo run build
は、Turborepo を使用して monorepo にある全てのパッケージの build
スクリプトを実行することができるコマンドです。今回の例では app/
にある Nuxt アプリケーションの package.json
の scripts
にある build
スクリプトが実行されます。
packages/
の中にあるパッケージにも、package.json
の scripts
に build
スクリプトが設定されているものがあれば、Nuxt アプリケーションの build
スクリプトと一緒にまとめて実行することができます。
この turbo run build
の build
の部分は、他のスクリプトを指定することも可能です。この後で設定しますが、turbo run lint
で全てのパッケージの lint
コマンドをまとめて実行することができます。 ただし、指定できるスクリプトは turbo.json
の pipeline
に設定を記述したスクリプトのみです 。
build
スクリプトについて、turbo.json
の pipeline
に設定を追加します。この設定を追加することで、turbo run build
を実行することができるようになります。
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/*", ".output/*", ".nuxt/*"]
},
}
}
dependsOn
に指定している ["^build"]
は、Turborepo に依存関係を考慮してタスクを実行するように伝えるものです。outputs
には、2 回目以降に実行する際にキャッシュしておきたいもの(ビルドで生成されるファイルなど)を指定します。
pipeline
の設定方法の詳細は、Turborepo のドキュメント をご確認ください。
ここまで設定した状態で、リポジトリのルートで build
スクリプトを実行すると、Nuxt アプリケーションのビルドを Turborepo で実行することができます。
$ pnpm build
ESLint の設定を追加
ESLint の設定を追加します。
monorepo の場合複数のパッケージやアプリケーションがあるため、それぞれで異なるルールを設定することになると思います。けれども、全てが完全に違うルールというわけでもないと思います。
- Prettier を導入しているので eslint-config-prettier は全てのプロジェクトで設定する
- React と Vue.js のプロジェクトがそれぞれ複数あり、React のプロジェクトでは React 用の共通ルール、Vue.js のプロジェクトでは Vue.js 用の共通ルールを設定する
こういった場合に、ESLint のルールをまとめたパッケージを monorepo 内に用意し、共通で使えるようにしておくと便利です。ESLint の設定を共有する方法についての詳しい説明については、以下の公式ドキュメントをお読みください。
今回は Nuxt アプリケーションだけしかないので、 Nuxt 用の共通で使える ESLint 設定のパッケージ をつくります。
まず、packages/
の中に eslint-config-custom
ディレクトリを作成し、コマンドまたは手動で package.json
を作成します。
{
"name": "eslint-config-custom",
"version": "0.0.0",
"private": true,
"main": "index.js"
}
ESLint のドキュメント にもありますが、実際に設定を使うときに eslint-config-custom
の eslint-config-
の部分を省略できるため、eslint-config-<任意の名前>
という名前でパッケージをつくると便利です。
次に、共通で使用する ESLint の設定をつくります。
Nuxt には @nuxt/eslint-config という設定があるため、これを使用します。
$ cd packages/eslint-config-custom
$ pnpm add @nuxt/eslint-config
先ほどの package.json
で main
に指定した index.js
に設定を書いていきます。もし追加で設定しておきたいルールがあれば rules
に追加します。
module.exports = {
extends: ['@nuxt/eslint-config'],
rules: {
// 任意のルールを追加
},
}
これで共通で使用する ESLint の設定ができたので、さっそく Nuxt アプリケーションにインストールしていきます。app/
へ移動して、ESLint と eslint-config-custom
をインストールします。
このとき、 確実にワークスペース内の他のパッケージをインストールできるよう --workspace
オプションをつけることをおすすめします 。--workspace
オプションは、ワークスペースで見つかった場合にのみ指定したパッケージをインストールするオプションです。同じ名前のパッケージが一般公開されていた場合に、そのパッケージをインストールしてしまうのを防ぐことができます。
入れるつもりのなかったパッケージをインストールしてしまい、危険なプログラムを実行してしまう可能性もあるので、念のためオプションをつけておいたほうがいいかな、と思います。
$ cd ../../app
$ pnpm add -D eslint
$ pnpm add -D eslint-config-custom --workspace
ESLint の設定ファイル .eslintrc.js
を作成し、インストールした設定を使用するように指定します。先ほど説明したように、eslint-config-
の部分は省略できます。
module.exports = {
root: true,
extends: ['custom'],
}
設定ファイルが作成できたら、Nuxt アプリケーションの package.json
の scripts
に ESLint を実行するスクリプトを追加し、実際に動作することを確認します。
{
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"lint": "eslint . --ext .js,.ts,.vue" // 👈これを追加
},
...
}
最後に、リポジトリのルートに戻り turbo.json
に lint
スクリプトの設定を追加します。
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/*", ".output/*", ".nuxt/*"]
},
"lint": {} // 👈これを追加
}
}
これで今回の環境構築は完了です。おつかれさまでした。