npm workspace で始めるモノレポ生活
workspace は複数のパッケージ(`package.json`)をレポジトリを管理するために使用されます。このようなレポジトリは**モノレポ**として知られています。
workspace とは npmv7 から追加された機能で、 yarn や pnpm の workspace を追従した機能です。
workspace は複数のパッケージ(package.json)をレポジトリを管理するために使用されます。このようなレポジトリはモノレポとして知られています。例えば以下のようにバックエンドとフロントエンドのプロジェクトをただ一つのレポジトリで管理するようにします。
.
├── backend
│ ├── src
│ │ ├── server.ts
│ │ └── ...
│ ├── package.json
│ └── package-lock.json
└── frontend
├── src
│ ├── main.ts
│ ├── components
│ └── ...
├── package.json
└── package-lock.jsonモノレポの戦略を選択した場合すべてのチームが同じレポジトリを共有することになります。一般にモノレポを選択する理由として以下のようなメリットがあげられます。
- コードの再利用
- チーム間のコラボレーションが容易になる
- コードの結合性
- コードの全体を把握しやすい
- 大規模なリファクタリングがやりやすい
- リリースを管理しやすい
もちろんモノレポを選択した場合にもたらされるのはメリットだけではなく、いくつかのデメリットと複雑性も生じます。例えば以下のような例です。
- 各プロジェクトのパッケージの依存関係の管理
- CI のジョブの複雑化
npm workspace は 1 つ目の「各プロジェクトのパッケージの依存関係の管理」を解決するために用いられます。npm workspace は複数のプロジェクトのパッケージをトップレベルの package-json で管理できます。また似た機能を提供するツールに Lerna が存在します。
例えば、バックエンドとフロントエンドのプロジェクトどちらも Jest パッケージを使用しているという例を考えてます。
npm workspace を始める
それでは実際に npm workspace を始めてみましょう。npm workspace を利用するには npm のバージョン 7 以降が必要です。
$ npm install -g npm@latest
$ npm -v
8.6.0ルートパッケージを作成する
まずはプロジェクトのトップレベルで npm プロジェクトを作成します。
$ npm init -yこのパッケージは公開することはないので private フィールドを true にしておくとよいです。
{
+ "private": true
}ワークスペースを作成する
ルートパッケージ上で npm init コマンドに -w オプションを付与することでワークスペースを作成できます。-w オプションの引数にディレクトリ名を指定します。
$ npm init -w backend上記コマンドを実行すると backend ディレクトリが作成されその中に package.json ファイルが作成されます。さらにルートパッケージの package.json ファイルには workspace フィールドが追加されます。
{
+ "workspaces": [
+ "backend"
+ ]
}続いてフロントエンドのパッケージも追加しておきましょう。
$ npm init -w frontend現在のレポジトリの構成は以下のようになっています。
.
├── package.json
├── frontend
│ └── package.json
└── backend
└── package.jsonパッケージをインストールする
npm install コマンドでパッケージをインストールする際に -ws オプションを付与することですべてのワークスペースに対して単一のパッケージをインストールできます。
$ npm install lodash -wsこの時、レポジトリの構造は以下のようになります。
.
├── backend
│ ├── package.json
├── frontend
│ └── package.json
├── node_modules
│ ├── backend -> ../backend
│ ├── frontend -> ../frontend
│ └── lodash
├── package-lock.json
└── package.jsonルートパッケージの node_modules に lodash が配置されるので npm のホイスティング(巻き上げ)により、よりそれぞれのワークスペースで lodash をインストールせずとも使用できます。
// frontend/index.js
const lodash = require("lodash");
const chunked = lodash.chunk(["a", "b", "c", "d"], 2);
console.log(chunked);$ node frontend/index.js
[ [ 'a', 'b' ], [ 'c', 'd' ] ]特定のワークスペースのみにパッケージをインストールしたい場合には -w オプションで対象のワークスペースを指定してインストールコマンドを実行します。
$ npm i dayjs -w backendフロントエンド、バックエンドのそれぞれの dependencies は次のようになっています。
// frontend/package.json
{
"name": "frontend",
"dependencies": {
"lodash": "^4.17.21"
}
}// frontend/package.json
{
"name": "backend",
"dependencies": {
"dayjs": "^1.11.0",
"lodash": "^4.17.21"
}
}npm スクリプトの実行
npm スクリプトを実行する場合にもパッケージをインストールする場合と同様にワークスペースを指定できます。例えば次のコマンドはすべてのワークスペースをテストを実行します。
$ npm run test -ws以下のコマンドはフロントエンドのテストのみを実行します。
$ npm run test -w frontendまた -ws オプションですべてのワークスペースの npm スクリプトを実行する際にワークスペースを特定のワークスペースのみに対象の npm スクリプトが定義されていないかもしれません。例えば serve コマンドはバックエンドプロジェクトには定義されていますがフロントエンドのプロジェクトには定義されていない例を考えてます。
// backend/package.json
{
"name": "backend",
"scripts": {
"test": "jest",
"serve": "node index.js"
},
}// frontend/package.json
{
"name": "frontend",
"scripts": {
"test": "jest"
},
}このような状態でスクリプトを実行するとエラーが発生してしまいます。
$ npm run serve -ws
> [email protected] serve
> node index.js
server
npm ERR! Lifecycle script `serve` failed with error:
npm ERR! Error: Missing script: "serve"
To see a list of scripts, run:
npm run
npm ERR! in workspace: [email protected]
npm ERR! at location: /npm-workspace/frontend このような場合には --if-present オプションを付与して実行すると npm スクリプトが定義されているワークスペースのみが実行されます。
$ npm run serve -ws --if-present
> [email protected] serve
> node index.js
serverさらにすべてのワークスペースの npm スクリプトを実行するとき実行順序はルートパッケージの workspaces フィールドの順番に依存されます。例えば workspaces の順場が backend → frontend となっている場合。
{
"workspaces": [
"backend",
"frontend"
]
}実行結果は以下のようになります。
$ npm run test -ws
> [email protected] test
> echo I am backend
I am backend
> [email protected] test
> echo I am frontend
I am frontend次にルートパッケージの workspaces の順序を入れ替えて実行しています。
{
"workspaces": [
"frontend",
"backend"
]
}
```sh
$ npm run test -ws
> backend@1.0.0 test
> echo I am backend
I am backend
> frontend@1.0.0 test
> echo I am frontend
I am frontend今度は frontend → backend の順番で実行されます。
$ npm run test -ws
> [email protected] test
> echo I am frontend
I am frontend
> [email protected] test
> echo I am backend
I am backendワークスペース間でコードを共有する
npm workspaces でワークスペースを管理している場合それぞれのワークスペース同士でパッケージを参照できます。例えば、次のようにバックエンドで作成したコードをフロントエンドから参照できます。
// backend/utils.js
const add = (a, b) => a + b;
module.exports = {
add,
};const utils = require("backend/utils");
const result = utils.add(1, 2);
console.log(result);$ node frontend/index.js
3