JavaScript
npm
TypeScript
lerna

lernaを使ってmonorepoなリポジトリを作ってみた

lerna とは?

lerna/lerna: A tool for managing JavaScript projects with multiple packages.

が何ものかというと、単一のリポジトリで複数の npm module 開発を可能にするツールです。

たとえば、

  1. babel/babel: ?? Babel is a compiler for writing next generation JavaScript.
  2. facebook/create-react-app: Set up a modern web app by running one command.
  3. nuxt/nuxt.js: The Vue.js Developers Framework

このあたりの有名なリポジトリが lerna を採用していて、もう面影がないですが、

facebook/react: A declarative, efficient, and flexible JavaScript library for building user interfaces.

react も同じディレクトリ構成をしています。(脱 lerna でもしたのでしょうか)

monorepo の長所と短所

babel/monorepo.md at master ・ babel/babel

からの引用ですが、

Why is Babel a monorepo?

Pros:

    * Single lint, build, test and release process.
    * Easy to coordinate changes across modules.
    * Single place to report issues.
    * Easier to setup a development environment.
    * Tests across modules are run together which finds bugs that touch multiple modules easier.

Cons:

    * Codebase looks more intimidating.
    * Repo is bigger in size.
    * Can't npm install modules directly from GitHub
    * ???

このあたりはひじょうに痛感していて、依存し合う2つの npm module が別々のリポジトリだとテストをどっちに書こうかなどいろいろと悩みがあります。

そういった部分をマージしましょうというお話ですね。ただし、 Cons もそこそこあると。

lerna のベースとなる環境を作る

lerna/commands/init at master ・ lerna/lerna

$ lerna init

これで以下のように必須のファイル・ディレクトリたちが作られる。
これは npm inityarn initlerna 版 だと思います。

lerna-repo/
  packages/
  package.json
  lerna.json

publish したい npm module の雛形を create する

$ lerna create lemon-sour
$ lerna create cli

上記のように実行すると、 packages の下にこのようにディレクトリが配置される。

packages
├─cli
│  │  package.json
│  │  README.md
│  │
│  ├─lib
│  │      cli.js
│  │
│  └─__tests__
│          cli.test.js
│
└─lemon-sour
    │  package.json
    │  README.md
    │
    ├─lib
    │      lemon-sour.js
    │
    └─__tests__
            lemon-sour.test.js

ローカルパッケージ cli を lemon-sour から参照するよう追加する。

$ lerna add cli --scope lemon-sour

lerna add は yarn add と同じコマンドだが、 --scope を指定いないとすべての packages に追加されてしまう。

なので、たてば lemon-sour だけに lodash を入れたい場合は、

$ lerna add lodash --scope lemon-sour

となる、このあたりは慣れるしかないんでしょうね。

また、 run test を実行すると、すべての package で script:test を実行してくれる。

$ lerna run test

さらに lerna コマンドは、どの階層にいても実行できるので、 create は ROOT に行ってから実行しないと、みたいなものがない。

lerna と yarn を一緒に使う場合

そもそも lerna を使うと npm installyarn add のようなものを直接使うことはできない。

なので、 yarn を使うには、 lerna.json を編集する必要がる。

{
  "npmClient": "yarn",
  "useWorkspaces": true,
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}

ROOT の package.json に workspaces の key を追加する。

{
  "name": "root",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "devDependencies": {
    "lerna": "^3.8.5"
  }
}

これで yarn workspaces が使える状態が整いました。

ワークスペース | Yarn

では試しに packages 全体に typescript を入れて、 lerna で build してみましょう。

$ lerna add typescript --dev

これで packages 個別に typescript がインストールされましたが、面白いことに packages のほうの node_modules のほうには typescript の link しかありません。

これは、同じバージョンの typescript 2つの npm module にインストールされましたが、それは2つ別々に配置するのが無駄なので、 ROOT のほうの node_modules に引き上げられます。

add した module を remove する方法

どうやら、これがまだないらしく、つまり yarn remove 相当のものはありません。

npm uninstall throws 404 for unpublished siblings ・ Issue #1229 ・ lerna/lerna

今後追加されていくようです。

なので、 editor で package.json を修正して、以下のコマンドを実行する必要があります。

$ lerna clean // node_modules たちを消す
$ lerna bootstrap // yarn 相当

publish する

$ lerna publish

これで、 lerna.json に書かれてるバージョンが上がって各 npm module たちが push されていきます。

今回のサンプルコード

hisasann/lerna-npm-sample

hisasann/lerna-yarn-sample

読んだ記事

Lerna を使って、 Babel や React が採用している monorepo を試してみる - Qiita

Lerna はまりポイント - Qiita

Lernaを使ってFirebase環境のためのモノレポ環境一式をカッコよく構築する - Qiita

lernaを使ってnpmプロジェクトをモノレポ化する - つくりおき

橋本商会 ≫ lernaでmonorepoした

単一リポジトリで複数package|projectを管理することをmonorepoというそう - なっく日報

Support Yarn workspaces to replace bootstrap command by bestander ・ Pull Request #899 ・ lerna/lerna