npm workspaces と Monorepo のススメ

JavaScript で自作のパッケージに依存したプログラムを開発する場合、それらを一つのリポジトリにまとめる Monorepo が便利です。Monorepo にすることで開発ワークフローがシンプルになり、 色々と楽になります。そんな Monorepo において、npm 7 より導入された workspaces の機能を用いると、さらにワークフローがシンプルになります。

今回は npm workspaces を使った Monorepo 開発を簡単に説明します。

サンプルリポジトリ:

github.com

ワーキングディレクトリ

npm workspaces を利用せず、ディレクトリごとに独立したパッケージを用意する場合には、npm install などを実行する際はそれぞれのディレクトリに移動して実行する必要がありました。npm workspaces では、基本的にはターミナル上ではパッケージをまとめたルートディレクトリから移動せずに作業を行います。

これだけ覚えておけば OK

npm workspaces を使うときはルートディレクトリから移動しない

プロジェクト構成

今回は packages ディレクトリに各パッケージを配置することにします。

パッケージ構成

名称などは適当です。

  • app: メインのパッケージ
  • lib: app が利用するパッケージ

環境構築

npm init # /package.json が生成されます
npm init -w packages/app # packages/app/package.json が生成され、/package.json の workspace に追記されます
npm init -w packages/lib

既存プロジェクトに導入する場合

  • 各パッケージのディレクトリの node_modules ディレクトリと package-lock.json を削除します
  • ルートディレクトリに package.json を追加し、workspaces にそれぞれのパッケージの場所を列挙します
  • ルートディレクトリで npm install を実行します

npm install (全パッケージ共通)

npm install lodash --workspaces

app, lib それぞれの package.json の dependencies が更新されます。(ルートの package.json ではないことに注意) また、node_modules ディレクトリはルートにだけ生成されます。

webpack や rollup など、共通した devDependencies をインストールする際によく使います。その場合、npm install @types/node --save-dev --workspaces のようになります。

npm install (個別)

npm install react react-dom --workspace=app

app の package.json だけが更新されます。

npm-run-script

package.json の scripts は通常通りそれぞれのパッケージに記載しますが、起動方法が異なります。 npm install と同様のマナーでルートディレクトリで下記を実行します。

npm start --workspace=app

毎回 --workspace=app を付けるのも面倒なので、下記のようにルートの package.json に script を用意すると便利です。

  "scripts": {
    "start": "npm start --workspace=app",
    "build": "npm run build --workspace=lib"
  },

import

workspaces 内のパッケージは npm install しないで使うことができます。 たとえば、lib パッケージが次のようなコードだとします。

export const createGreetingMessage = () => "hello, world"

もちろん、通常のパッケージと同様に、package.json の maintypes が正しくファイルを指している必要があります。

そうすると、app パッケージから import することができます。

import { createGreetingMessage } from "lib"

const message = createGreetingMessage()
alert(message)

ワークフロー

今回の例では、環境構築以降は次のような流れで開発を進めます。

  1. app パッケージの webpack-dev-server を起動
  2. 適宜 lib パッケージを修正・ビルド (webpack-dev-server が自動的にリロードされ反映される)

lib パッケージの更新が頻繁なら、lib パッケージに watch のようなスクリプトを追加して、concurrently などで webpack-dev-server と同時に起動するのも良いでしょう。

まとめ

もうルートディレクトリから動かなくて良い!