余白

https://blog.lacolaco.net/ に移転しました

Angular用ライブラリをnrwl/nx + ng-packagrのmonorepoで管理してみた

表題のとおりです。結論からいうといろいろ問題があり、このmonorepoは失敗ということでそのうち解散するつもり。

Nx workspaceを使ったmonorepo

Angular用ライブラリのmonorepoには、Nrwlが提供するNx Workspaceを使うのが早いし便利だ。

nrwl.io

Nxとは何かというのはリンク先に任せるとして、次のコマンドでNxのインストールとワークスペースの作成をおこなう。 今回作ったmonorepoは ngx というリポジトリ名なので、ワークスペース名も ngx とした。

github.com

# 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に則った複数の形式のバンドルを自動で作成してくれる優れもの。

github.com

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もここに置く必要がある。

f:id:lacolaco:20180202212045p:plain

実はng-packagrの作者がNxとのインテグレーションのサンプルを作ってくれてるので、そっちを参考にするとよい。

github.com

publishとバージョニング

ng-packagrを使ってパッケージができあがるところまではうまくいったが、ここから先がいい感じにならなかった。

ビルドされたパッケージをnpmにpublishするには、それぞれのパッケージがバージョンを持たなければならない。 ご存知のとおりnpmパッケージのバージョンは重複が許されないので、publishするにはバージョンを変えないといけないのだけど、ここで既存のツールとうまく噛み合わなくなる。

普通の単パッケージのリポジトリだと、バージョンを上げるのはnpm versionyarn versionのコマンドを使うとよくて、バージョニングと同時にgitのコミットとタグも打ってくれる。 しかし今回のmonorepoの場合、バージョンを上げるにはpackage.jsonを手書きで編集するしかない。 yarn publishコマンドはpublish時にバージョンを上書きする機能があるけれど、これで編集しても書き換わるのはビルド後のdistの中のpackage.jsonなので、元のファイルには影響しない。 package.jsonが二重に存在する状態になるとこの辺が面倒。いいアイデアが浮かばない。

CHANGELOG

バージョニングはまあなんとかなるかと思ったけど、CHANGELOGはどうにもならなかった。 conventional-commitlogに従ってstandard-changelogCHANGELOG.mdを生成したいのだけど、バージョンごとのgitのタグがないので、全バージョンが過去分すべてのcommitをCHANGELOGに出してくる。これはどうしようもない。

今は重複のコミットを手で消しているけどすぐに破綻するので、これがmonorepo断念の一番の理由。CHANGELOG自動生成と相性が悪い。 Lernaとかはmonorepo側とインテグレーションしていい感じにできるっぽいけど、Nxには期待できなさそう。

ライブラリ間の依存

あとmonorepoのビルドプロセスを作っててつらいのが、ライブラリ間に依存関係ができてしまったとき。具体的にはngx-storestoreに依存しているんだけど、Nxのワークスペース内モジュール解決はNxでビルドするときにしか使えなくて、ng-packagrでビルドしようとするとnode_modules内に自分のライブラリがなくてエラーになる。そのため、ライブラリのビルド前にnpm linkでsymlinkを貼っておく必要がある。 まあなんかこの辺見てもらえればわかると思う。依存関係があるからライブラリのビルドが並列にできないのもつらい。

f:id:lacolaco:20180202214345p:plain

まとめ

  • Nxは便利。複数アプリから呼び出される共通自前モジュールがあるときにはめっちゃいいと思う
  • ng-packagrは便利。これ無しでまともなAngular向けパッケージビルドするのつらい。
  • CHANGELOG.mdの自動生成を考えるとmonorepoはgitタグが付けられないのでつらい。

ngxのリポジトリ見ればいろいろわかると思うけど、聞きたいことがあったらng-japanのslackとかtwitterとかで聞いてください。