先日とあるイベントでこちらの内容の発表をさせていただきました。
その中でNxに関して触れた際に(77ページあたり)、もう少し掘り下げて聞いてみたいという声を多方からいただきまして、私がNxを推す理由を記事にしてみることにしました。
Nxとはなんぞや
Nxはひととでいうと、「モノレポ管理ツール」の一種です。
マイクロサービスやマイクロフロントエンドでサービスを構築する場合に、複数のサービス・プロジェクトを運用する必要がありますが、そのプロジェクトを別々のリポジトリで運用するのではなく、一つのリポジトリでモノリシックに扱うことで、依存関係の解決などの管理をしやすくするためのツールです。
中でもNxはJavascript/Node/Typescriptに特化しているため、フロントエンドやBFF開発での文脈でよく登場します。
他のモノレポ管理ツールとの違い
Nxの比較対象としてLernaがよくあがります。
2021/12/13現在、Lernaの方が3倍近くスターが多く付いているため、単純な認知度はLernaの方が高いといえます。
単純な「モノレポ管理ツール」ということであれば、Lernaの方がその名前にふさわしく、
- パッケージの依存管理
- ビルドを始めとしたタスクの実行順序の担保
といった基本的なモノレポ管理の機能を備えています。
詳しくは後述していきますが、Lernaは基礎的な機能のみを提供しているため、カスタマイズ性が高く、利用するフレームワーク・開発スタイルに順応できます。
その一方でカスタマイズのためにはそれなりにツールや周辺技術に対しての理解が必要です。
Nxはそのへんがプラグインベースになっているため、Lernaと比べればカスタマイズ性は損なわれますが、選定した技術やスタイルがプラグインにマッチすれば、メンテナンスコストを支払う必要がなくなります。
また、相対評価になりますが、Lernaが「純粋なモノレポ管理ツール」であるとすれば、Nxは「依存管理と高速化ができるエコシステムscaffoldツール」だと私は捉えています。
それでは私がNxを推す理由を一つずつ説明していきます。
Nxのイケてるところ
テストやリンターツールを設定不要で導入できる
Nxを利用してプロジェクトを構築すると下記のようなツールをほぼ設定不要で導入可能です。
- Cypress (E2E test)
- Jest (Unit test)
- ESLint (Code linter)
- Prettier (Code formatter)
- Storybook (Design catalog)
通常これらのツールを導入する場合は、それなりの導入・構築コストが必要です。
例えば、JestやCypressの導入の場合には、tsconfigをテスト用にかき分けたり、ESLintはプロジェクトで利用しているフレームワークに最適なものにカスタマイズしたりなど、プロジェクトの運用において本質的ではないところに時間を割かれがちです。
もちろん導入後は、更に周辺の細かいライブラリを導入したり、設定などを細かくカスタマイズすることも可能です。
@(アットマーク) エイリアス
Nxを利用してリポジトリを構築すると、appsとlibsディレクトリが生成されます。
appsにはアプリケーション(サービス)を構築するコードを、libsにはappsの各サービスで利用するための共通コンポネントやHooks、ユーティリティ関数を構築していきます。
これらのディレクトリへはNxコマンドを利用してコードを配置していくと、ルートのtsconfigが自動拡張され、各libsのコードをアットマークエイリアスでインポートすることが可能になります。
パスを意識することなく、libsに配置したコードをnpmパッケージに見立ててインポートできる点がクールです。
import { Header } from '@myproject/components' // ../../../libs/components/index.ts
import { timeFormatter } from '@myproject/utils' // ../../../libs/utils/index.ts
キャッシュによる高速化
これまでに上げた特徴は、多少コストが掛かりますが、手動構築すればNx以外のモノレポ管理ツールでも同様のことができます。
しかし、ここで上げるキャッシュ機構は、Nxを選択する特権であると言えます。
そのため、libsのとあるコードを変更した場合に、どの範囲で影響があって、どの範囲は影響がないということを知っています。
そして、Nxコマンドを通じたオペレーションの実行結果及び副作用は自動的にキャッシュされ、不要なタスクの実行をスキップすることができます。
例えば、プロジェクト全体でテストを実行した際に、とある一部分だけテストが失敗したとします。
テストが通るようにコードを改変して再度実行すると、Nxは変更箇所に対して依存関係のないテストの実行をパスして、前回実行のテストが失敗したところと、変更に対して依存関係があるテストのみを再実行します。
これはテストだけではなく、コードのビルドやフォーマットも同様です。
つまり、プロジェクトが巨大になっても、実装速度を低下させずに、開発体験を一定維持し続けるための仕組みをNxは備えています。
差分実行(affectedコマンド)
前述したキャッシュだけでなく、更に強力なaffectedコマンドというものを備えています。
これは、自身が指定した地点(デフォルトはメインブランチ)との差分を測定し、その差分の影響範囲内でテストやリンターを実行する仕組みです。
通常の開発はメインブランチやフィーチャーブランチから新しいブランチを生やして開発していくと思いますが、その際のコマンドをaffectedコマンドを通じて行うことで、
自身の変更とは関係のないところの実行をスキップできます。
この機能とhuskyを組み合わせることで、lint-stagedなどのプラグインの導入が不要になります。
また、開発中だけでなく、デプロイでもこのaffectedコマンドで差分実行することで、不用意なデプロイをスキップすることが可能です。
こちらは、Vercelで影響のあるプロジェクトのみデプロイするための設定の例です。
IDEプラグイン
ここまでの内容でお気づきかもしれませんが、Nxの恩恵を受けるためには、Nxコマンドを通じたタスクの実行が必要です。
appsやlibsにプロジェクトを追加する場合は、generateコマンドが必要ですし、テストの実行にはrunコマンドが必要です。
「プロジェクトメンバーにはデザイナーもいて、コマンドを覚えてもらうにはそれなりのコストがいるのでちょっと。。。」という方もいるかも知れません。
package.jsonにエイリアスとして登録することも可能ですが、エイリアスの数が多ければ問題は解決できません。
Nxはその問題に対処するために、VScodeとIntelliJ向けのプラグインを公開しています。
基本的なタスクの実行はコマンドそのものを覚えなくても、エディタのサイドバーリストからワンクリックで実行が可能です。
また、generateコマンドは視覚的にオプションを選択可能であり、DryRunも備えています。
コマンドリスト | ジェネレートパネル |
---|---|
カスタムジェネレータ
Nx内でのジェネレーションは基本的に、プラグインとして公開されているものを利用して行いますが、自身で個別に設定・カスタマイズすることも可能です。
例えばHygenのようなコンポネントテンプレートジェネレータを、Nxのジェネレータとして設定することが可能です。
そうすることで、前述のIEDのプラグインからの実行が可能になります。
node_modules, package.jsonがリポジトリに1つ
モノレポ管理ツールというと、プロジェクトごとにpackage.jsonを持たせるものが多いです。
lernaがまさにそうで、これは、lernaが各プロジェクトをnpmレジストリにpublishする前提にあるためです。
しかし、Nxはそういった思想ではないためpackage.jsonは基本的にルートディレクトリに一つだけになります。
(※個別設定でnpmにパブリッシュするための構成でgenerateすることもできます。)
これの何が嬉しいかというと、package.jsonが一つだけということは、ライブラリのバージョン違いがプロジェクト内に複数紛れる可能性が少なく、バージョンのbumpupも容易になります。
ブラグインによるエコシステム
ここまでプラグインという言葉が多く出てきました。
Nxは他のモノレポ管理ツールと違い、プラグインで構成を拡張してリポジトリを育てていきます。
そうすることで利用するフレームワークのベストプラクティスな設定でプロジェクトの構築が可能になります。
また、自身でプラグインを作成し公開することも可能です。
プラグインは、Nx公式のプラグインと、有志によって開発されたプラグインがあります。
いくつか書き出しますが、実際には書ききれないほどのプラグインが存在していますので、ぜひgithubなどで検索してみてください。
- 公式
- @nrwl/react (React (react create app))
- @nrwl/next (Next.js)
- @nrwl/gatsby (Gatsby)
- @nrwl/nest (Nest.js)
- @nrwl/express (Express)
- @nrwl/web (プレーンなクライアント用コード)
- その他
- 非公式(有志)
- @nx-plus/nuxt (Nuxt)
- @nx-plus/vue (Vue)
- @nx-tools/nx-prisma (Prisma)
開発が安定している
NxはOSSである一方で、Nrwlという企業がオーナーを務めています。
NrwlはもとはAngular Consoleの開発元であり、そういったノウハウをベースにツールが開発されています。
また、githubのdiscussionでは、ロードマップが示されており、そういった点から安定した開発体制と将来性を伺うことができます。
ちょっとイケてないところ
プラグインベース故にフレームワーク側のパンプアップから若干時間差が生じる
タイトルのとおりですが、プラグインベースでフレームワークを利用するため、フレームワーク側のバージョンが上がっても、Nxのプラグインがそれに対応しなければ最新版を導入することはできません。
自身でpackege.jsonのバージョンを変更すればbumpup可能ですが、動作は保証されないためあまりおすすめしません。
一方で、プラグイン側の方で対応さえしていれば、bumpup自体は専用のNxコマンドを叩くだけで完了しますし、マイグレーションも自動的に行なってくれます。
まとめ
いかがでしたでしょうか?
キャッシュや差分検知による高速化、プラグインベースという点が、他のモノレポ管理ツールとの大きな違いであることがわかるかと思います。
また、これらの点から、たとえモノレポでプロジェクトを構築しなかったとしても、Nxを取り入れることで初期コストなしで様々な開発ツールを導入することが可能であることも理解いただけるかと思います。
冒頭に述べた
Lernaが「純粋なモノレポ管理ツール」であるとすれば、Nxは「依存管理と高速化ができるエコシステムscaffoldツール」だと私は捉えています。
ということの意味が多少でも理解していただけると幸いです。
補足 (Turborepoとの比較)
先日、VercelがTurborepoというモノレポ管理ツールを買収したとニュースになりました。
チュートリアル程度にサンプルのリポジトリを立ち上げてみましたが、所感としては、Nxのようなキャッシュ機構を備えているものの、根本はLernaを強くインスパイアしている感じがします。
turboのrun
コマンド自体は、各プロジェクトのpackage.jsonのエイリアスのプロキシになっているため、その思想はLernaに近く、Nxのようにserve
やdev
といった決まったコマンドを持っているわけではありません。
実行速度という点に関しては、ハッシュ値でキャッシュを取ることで、差分が発生したところだけを再ビルドできるので、Nxと同等に高速なビルドを実現できています。しかし、Nxはキャッシュと合わせて、優秀なaffected
コマンドを持っているため、単純比較してしまうと、どうしてもNxに軍配が上がります。
ローカルキャッシュだけでなく、Vercelがリモートキャッシュの保存先として無料開放されている点については、かなり将来性を期待できますが、現時点では、まだ実用性が高いとは言い難いと感じました。
追記 2021/12/14
タイムリーなことにちょうどNxのリポジトリにTurborepoとの比較ドキュメントが作成されました。
Nx側もかなり意識していることが伺えます。健全に競争してもらって、どちらを使っても開発体験がよくなる方向に進んでくれることを願います。