ファインディ株式会社でフロントエンドのリードをしております 新福(@puku0x)です。
この記事では、ファインディで導入しているモノレポ管理ツール「 Nx 」について紹介します。
モノレポとは
モノレポは全てのコードベースを単一のリポジトリで管理する手法です。
コードの共通化や可視化、ツール・ライブラリの標準化、一貫性のあるCI/CDパイプラインを構築できるといったメリットがあります。また、マイクロサービスと相性が良いとも言われています。
ファインディでは主にフロントエンド系のリポジトリをモノレポとして運用しています。
アプリケーションとそれに関連するフィーチャー、UIライブラリがひとつにまとまっているため、複数のリポジトリを移動することなくコードを参照できます。
Nxとは
Nxはモノレポ管理やアプリケーションのビルド、テストの実行、コード生成などの機能を備えた統合的なツールです。
モノレポ管理ツールは他にもLernaやTurborepoなどがあります。アプリケーション開発において有用な機能が多くあることや、コミュニティが活発で継続的なメンテナンスが見込めること、前職で導入実績があったことなどがNxを選んだ理由です。
後述する「変更検知」や「キャッシュ機構」といった機能は、Pull Requestの粒度を細かくするファインディの開発スタイルと高い親和性があり、導入当初から私達の開発を支える頼もしいツールとなっています。
大手企業でも採用事例が増えており、Nxはモノレポ管理ツールとして有力な選択肢といえるでしょう。
https://npmtrends.com/lerna-vs-nx-vs-turbo
(個人的に推していたOSSがここまで有名になったことには感慨深いものがありますね!)
Nxワークスペースの作成
create-nx-workspace
を実行するとワークスペースを作成できます。
Reactをはじめ、メジャーなフレームワーク用のプリセットもあるためすぐに開発を始められます。
npx create-nx-workspace@latest <ワークスペース名> --preset=react-monorepo --appName=<アプリケーション名> --bundler=webpack --e2eTestRunner=playwright --style=scss --nxCloud=skip
詳細は公式のチュートリアルをぜひご覧ください。
Nxの機能
コード生成
nx generate
コマンドでプロジェクト(アプリケーションやライブラリ)を作成できます。
npx nx generate @nx/react:application --name=<アプリケーション名> --directory=<ディレクトリ> --routing=false --e2eTestRunner=playwright --projectNameAndRootFormat=as-provided
npx nx generate @nx/react:library --name=<ライブラリ名> --directory=<ディレクトリ> --bundler=rollup --unitTestRunner=jest --projectNameAndRootFormat=as-provided
nx generate
で実行できるGeneratorは自分で作ることもできます。
ファインディではフィーチャー用のファイル一式を生成するGeneratorを作って開発を効率化しているチームもあります。また別の機会に紹介できればと思います。
変更検知
Nxはプロジェクト間の依存関係を自動的に算出する機能を備えています。
npx nx affected --graph
を実行すると次のように依存関係を可視化できます。
npx nx affected --target=build
のように指定すると、変更のあったプロジェクトとそれに依存する他のプロジェクトを全てビルドしてくれます。
個人的なおすすめは、npm-scriptsやCI上で実行するコマンドを nx affected
ベースにすることです。
"scripts": { "build": "nx affected --target=build", "test": "nx affected --target=test", "lint": "nx affected --target=lint", ... },
必要なタスクのみ実行されるためスピーディーに開発できます。
依存関係の管理
Nxが持つプロジェクト間の依存関係はLintルールにも応用されます。
ファインディでは、@nx/enforce-module-boundaries
ルールを適用し、意図しない依存関係の逆転が起こらないように制御しています。
具体的には、各プロジェクトに次のようなタグを設定し、
{ "name": "utils", "sourceRoot": "libs/utils/src", "projectType": "library", "tags": ["scope:shared", "type:util"], ... }
- apps/ - app1 (scope:app1) - app2 (scope:app2) - libs/ - app1/ - feature-dashboard (scope:app1, type:feature) - ui (scope:app1, type:ui) - app2/ - feature-user (scope:app2, type:feature) - ui (scope:app2, type:ui) - utils (scope:shared, type:util)
.eslintrc.json
に対応するルールを設定しています。
"@nx/enforce-module-boundaries": [ "error", { "allow": [], "depConstraints": [ { "sourceTag": "scope:shared", "onlyDependOnLibsWithTags": ["scope:shared"] }, { "sourceTag": "scope:app1", "onlyDependOnLibsWithTags": ["scope:shared", "scope:app1"] }, { "sourceTag": "scope:app2", "onlyDependOnLibsWithTags": ["scope:shared", "scope:app2"] }, { "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:ui", "type:util"] }, { "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:ui", "type:util"] }, { "sourceTag": "type:util", "onlyDependOnLibsWithTags": ["type:util"] } ] } ]
設定したルールを図にするとこのようになります。scope
がプロジェクト間の依存関係、type
が内部のレイヤーの依存関係を表します。
@nx/enforce-module-boundaries
ルールが設定されたプロジェクトでは依存してはいけないモジュールに対してエラーが表示されます。
大規模なコードベースの管理においては、モジュール同士の依存関係の制御が非常に重要であり、こういった「かゆいところに手が届く」機能を持っていることがNxの魅力でもあります。
キャッシュ機構
Nxは一度実行されたコマンドのキャッシュを保持しており、同じコマンドが実行された場合にキャッシュから結果を再現する機能を持っています。
また、関連サービスである「 Nx Cloud」のリモートキャッシュ機能を有効にすると大幅なCI高速化が可能です。
実際にファインディでは、キャッシュの活用で毎月1,000時間以上のCI時間を削減できました。
自動マイグレーション
NxにはCodemodやAngular CLIの ng update
と似たコマンドが備わっています。
npx nx migrate latest npx nx migrate --run-migrations
nx migrate
を実行すると、Nx本体と各種プラグイン、react
や eslint
、typescript
などの依存ライブラリ・ツールがまとめて更新されます。
バージョンアップに伴うマイグレーションは手動で対応すると大変ですが、Nxでは自動化されています。メンテナンスの手間が省けると同時に属人化も抑えられるため重宝しています。
まとめ
この記事では、Nxの概要と基本的な機能について紹介しました。
Nxは単なるモノレポ管理ツールに留まらず、「開発者体験の改善」や「開発生産性の向上」といったポテンシャルを秘めています。
✨✨✨ Nxはいいぞ! ✨✨✨
Nxはいいぞおじさんから伝えたいことは以上です。
より詳細な活用法やNx Cloudの高度な機能については、今後の記事で取り上げる予定ですのでご期待ください。
ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。