表題のとおりです。結論からいうといろいろ問題があり、このmonorepoは失敗ということでそのうち解散するつもり。
Nx workspaceを使ったmonorepo
Angular用ライブラリのmonorepoには、Nrwlが提供するNx Workspaceを使うのが早いし便利だ。
Nxとは何かというのはリンク先に任せるとして、次のコマンドでNxのインストールとワークスペースの作成をおこなう。
今回作ったmonorepoは ngx
というリポジトリ名なので、ワークスペース名も ngx
とした。
# install CLIs > yarn global add @nrwl/schematics > yarn global add @angular/cli # create a workspace > create-nx-workspace ngx
create-nx-workspace
コマンドを実行すると、デフォルトのアプリケーションがひとつ含まれたワークスペースが作成される。
プロジェクト全体はng new
を拡張したもので、.angular-cli.json
ファイルがあるのでng generate
コマンドによるコード生成なども可能だ。
ライブラリ開発用のワークスペースとして必要なのは、サンプル用のアプリケーションがひとつと複数のライブラリを管理することだが、これ自体はNxが最初からサポートするユースケースなので簡単にできる。
ドキュメントの
Create an Angular module lib を読めば、 nx generate lib
コマンドでライブラリが1モジュール作れるのがわかる。言われたとおりにやるだけで、ワークスペース内のアプリケーションからアクセス可能なライブラリができあがる。
ng-packagrを使ったnpmパッケージ化
問題はここから。Nxで作られるライブラリはあくまでもワークスペース内のアプリケーションから呼び出すためのもので、npmパッケージとして切り出して公開するためにいろいろと作業が必要だ。
Angular用ライブラリのパッケージングには、ng-packagrというツールを使う。Angular本体と同じようにUMDやESM、FESMなど、Angular Package Formatに則った複数の形式のバンドルを自動で作成してくれる優れもの。
ng-packagrはnpmモジュールとして切り出したいディレクトリのpackage.json
を指定して使う。Nxのワークスペースであっても、libs
ディレクトリ内のライブラリごとのディレクトリに、それぞれのpackage.json
を配置してあげればよい。ngxでは次のようなコマンドでパッケージをそれぞれビルドしている。
{ "name": "ngx", "version": "0.0.0", "license": "MIT", "scripts": { ... "build:libs:store": "ng-packagr -p libs/store/package.json", "build:libs:ngx-store": "ng-packagr -p libs/ngx-store/package.json", "build:libs:ngx-grid-layout": "ng-packagr -p libs/ngx-grid-layout/package.json", ... }, ... }
ライブラリのディレクトリはこのようになっている。package.json
があるディレクトリのファイルはコピーされるので、README.mdもここに置く必要がある。
実はng-packagrの作者がNxとのインテグレーションのサンプルを作ってくれてるので、そっちを参考にするとよい。
publishとバージョニング
ng-packagrを使ってパッケージができあがるところまではうまくいったが、ここから先がいい感じにならなかった。
ビルドされたパッケージをnpmにpublishするには、それぞれのパッケージがバージョンを持たなければならない。 ご存知のとおりnpmパッケージのバージョンは重複が許されないので、publishするにはバージョンを変えないといけないのだけど、ここで既存のツールとうまく噛み合わなくなる。
普通の単パッケージのリポジトリだと、バージョンを上げるのはnpm version
やyarn version
のコマンドを使うとよくて、バージョニングと同時にgitのコミットとタグも打ってくれる。
しかし今回のmonorepoの場合、バージョンを上げるにはpackage.jsonを手書きで編集するしかない。
yarn publish
コマンドはpublish時にバージョンを上書きする機能があるけれど、これで編集しても書き換わるのはビルド後のdist
の中のpackage.json
なので、元のファイルには影響しない。
package.json
が二重に存在する状態になるとこの辺が面倒。いいアイデアが浮かばない。
CHANGELOG
バージョニングはまあなんとかなるかと思ったけど、CHANGELOGはどうにもならなかった。 conventional-commitlogに従ってstandard-changelogでCHANGELOG.mdを生成したいのだけど、バージョンごとのgitのタグがないので、全バージョンが過去分すべてのcommitをCHANGELOGに出してくる。これはどうしようもない。
今は重複のコミットを手で消しているけどすぐに破綻するので、これがmonorepo断念の一番の理由。CHANGELOG自動生成と相性が悪い。 Lernaとかはmonorepo側とインテグレーションしていい感じにできるっぽいけど、Nxには期待できなさそう。
ライブラリ間の依存
あとmonorepoのビルドプロセスを作っててつらいのが、ライブラリ間に依存関係ができてしまったとき。具体的にはngx-store
がstore
に依存しているんだけど、Nxのワークスペース内モジュール解決はNxでビルドするときにしか使えなくて、ng-packagrでビルドしようとするとnode_modules内に自分のライブラリがなくてエラーになる。そのため、ライブラリのビルド前にnpm link
でsymlinkを貼っておく必要がある。
まあなんかこの辺見てもらえればわかると思う。依存関係があるからライブラリのビルドが並列にできないのもつらい。
まとめ
- Nxは便利。複数アプリから呼び出される共通自前モジュールがあるときにはめっちゃいいと思う
- ng-packagrは便利。これ無しでまともなAngular向けパッケージビルドするのつらい。
- CHANGELOG.mdの自動生成を考えるとmonorepoはgitタグが付けられないのでつらい。
ngxのリポジトリ見ればいろいろわかると思うけど、聞きたいことがあったらng-japanのslackとかtwitterとかで聞いてください。