Edited at

相互依存関係(Sibling packages)にあるJavaScriptのマルチプロジェクト管理を lerna でやってみる

More than 1 year has passed since last update.


はじめに

たとえば、下記のような相互依存関係のあるプロジェクト群をJavaScriptで作りたい場合が普通にあるかと思います。

start-lerna/

packages/
common/ (Moment.jsに依存)
childA/ (../common に依存)
childB/ (../common, lodash に依存)

本記事で、このようなプロジェクトを lerna を使って構築・管理してみます。あまり高度な使い方には触れません。というか、私自身も初心者なので、触れられません。 (^^;)

なお、パッケージ管理に yarn を使います。別に使わなくてもできます。

この記事で使用しているソースコードは https://github.com/knjname/2018-07_start-lerna にて利用可能です。


lerna プロジェクト(親プロジェクト) を作る

# プロジェクトフォルダを作る

$ mkdir start-lerna
$ cd start-lerna

# lernaの初期化
$ yarn add -D lerna
$ yarn lerna init
...
lerna success Initialized Lerna files

# 中身の確認 (勝手に .git フォルダも作られている)
$ ls -a
.git
lerna.json
package.json
packages

パッケージ管理にyarn を使うので、 lerna.jsonnpmClient キーに "yarn" を指定しましょう。


lerna.json

  "npmClient": "yarn",



子プロジェクトを作る

lerna のルールとしては、 親プロジェクト/packages/子プロジェクトたち という場所に子プロジェクトを置くのがデフォルトなので、それに反しないように作っていきます。


common プロジェクト


PWD=プロジェクトルート

$ cd packages


$ mkdir common
$ cd common

# 下記コマンドで version=1.0.0, main: index.js な package.json ができるはず
$ yarn init

# Moment.js への依存性追加
$ yarn add moment


packages/common/index.js

const moment = require("moment");

module.exports = function (name) {
return "Hello, " + name + ". I am common module you're depending on. And it's " + moment(new Date()).format("h:mm") + " now.";
}



childA プロジェクト (兄弟プロジェクトへの依存性を持っているプロジェクト)


PWD=プロジェクトルート

$ cd packages


$ mkdir childA
$ cd childA
$ yarn init

手作業で package.jsoncommon への依存性を追加しましょう。


packages/childA/package.json

  "dependencies": {

"common": "^1.0.0"
}

common のモジュールを使ったスクリプトも追加しましょう。


packages/childA/index.js

const common = require("common");

console.log(common("knjname"));


上記の common への依存性がある(そして推移的に Moment.js に依存性がある) childA を実際に動かしてみましょう。


PWD=プロジェクトルート

# 子プロジェクトの依存性解決

$ yarn lerna bootstrap

$ cd packages/childA

$ node index.js
Hello, knjname. I am common module you're depending on. And it's 8:41 now.

ちゃんと common への依存性も、 common 自体の依存性(= moment) も解決した状態で実行されましたね。

ちょっと common/index.js の内容を変更してみましょう。


packages/common/index.js

const moment = require("moment");

module.exports = function (name) {
return "Good bye, " + name + ". I am common module you had depended on. And it was " + moment(new Date()).format("h:mm") + " now.";
}


そうすると、特に何もしなくても変更に追従してくれますね。


PWD=packages/childA

$ node index.js

Hello, knjname. I am common module you're depending on. And it's 8:41 now.

packages/childA/node_modules/common../../common へのシンボリックリンクになっているからです。

$ ls -al packages/childA/node_modules

common -> ../../common


childB プロジェクト (兄弟プロジェクトと lodash への依存性を持っているプロジェクト)

今度は兄弟プロジェクトへも、独自 lodash にも依存性を持っているプロジェクトを作成してみます。


PWD=プロジェクトルート

$ cd packages


$ mkdir childB
$ cd childB
$ yarn init

common への依存性をまず足そうと思うのですが、前回のように手で package.json に追加することはせず、 lerna コマンドでやってみます。


PWD=プロジェクトルート

$ yarn lerna add common --scope=childB

...
lerna success Bootstrapped 1 packages
✨ Done in 0.84s.

このコマンドで childB/package.json に依存性エントリが追加されます。


packages/childB/package.json

  "dependencies": {

"common": "^1.0.0"
}

更に lodash への依存性も追加しましょう。


PWD=プロジェクトルート

$ yarn lerna add lodash --scope=childB

:
:
lerna success Bootstrapped 1 packages
✨ Done in 0.84s.

同じように childB/package.json に依存性エントリが追加されるのですが、 childB/yarn.lock ファイルも自動生成されます。ありがたい。


packages/childB/package.json

  "dependencies": {

"common": "^1.0.0",
"lodash": "^4.17.10"
}


packages/childB/yarn.lock

lodash@^4.17.10:

version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"

もちろんこれら依存関係を用いたスクリプトを作らないと意味がありませんね。


packages/childB/index.js

const common = require("common");

const _ = require("lodash")

console.log(_.map(["foo", "bar", "baz"], function (elem) {
return common(elem)
}))



PWD=packages/childB

$ node index.js

[ 'Good bye, foo. I am common module you had depended on. And it was 8:59 now.',
'Good bye, bar. I am common module you had depended on. And it was 8:59 now.',
'Good bye, baz. I am common module you had depended on. And it was 8:59 now.' ]

ちゃんと動きました!


プロジェクトを初期からインストールさせてみる

上記で簡単なプロジェクト構成を作成しましたが、当然 node_modules などはバージョン管理には入れないので、他人がプロジェクトをチェックアウトした際に数コマンドでセットアップできないと困ったことになります。

きちんとできるか、検証してみましょう。


PWD=プロジェクトルート

# まずは node_modules を全消ししてみる

$ rm -rf node_modules packages/*/node_modules

# lerna 自体(親プロジェクト自体)のインストール
$ yarn

# lerna による全プロジェクトの依存性インストール
$ yarn lerna bootstrap
...
lerna success Bootstrapped 3 packages
✨ Done in 2.21s.

ちゃんとできました!

image.png


おわりに

以上のやり方で小規模なプロジェクトは運用できると思います。たとえば、ソースコード生成系のプロジェクトを lerna を使って分離するなどの手法を取ることができます。

もっと高度な使い方は 公式ドキュメント を見ましょう。