nxのキャッチアップ
Nxは、開発者の生産性を向上させ、CIのパフォーマンスを最適化し、コード品質を維持するためのツールとテクニックを提供する、強力なオープンソースのビルドシステムです。Nxの仕組みについてもっと知る。
Nxを使用すると、新しいスタンドアロン・プロジェクトやmonorepo全体を素早くscaffoldすることができます。Nxは段階的に導入することができ、プロジェクトの規模に合わせて成長します。
インストール
npm create nx-workspace
Why Nx?
私たちがNxを作ったのは、開発者が様々なツールやフレームワークの設定、メンテナンス、特に統合に苦労しているからです。一握りの開発者のためにうまく機能し、同時に組織全体に簡単にスケールアップできるシステムを構築するのは大変です。これには、低レベルのビルドツールの設定、高速なCIの設定、コードベースの健全性、最新性、保守性の維持などが含まれます。
私たちは、導入と拡張が容易なソリューションを提供したいと考えました。
How Does Nx Help You?
一言で言えば、Nxは、ローカルおよびCI上での計算(ビルド、テストなど)を高速化し、プラグインを介してツールを統合し自動化するのに役立ちます。これらすべてを段階的に導入することができる。プラグインを使うことはできるが、使う必要はない。詳しくは次のセクションのNxアーキテクチャを見てほしい。
Nxは次のような用途に使えます。
- 既存プロジェクトのビルドとテストを、ローカルでもCI上でも高速化する(monorepoでもスタンドアロンアプリケーションでも)。
- 下位レベルのビルドツールを設定することなく、(Nxプラグインを使用して)新しいプロジェクトを素早く雛形化する。
- 新しいツール(Storybook、Tailwindなど)をプロジェクトに簡単に統合できます。
- カスタムジェネレータと lint ルールにより、一貫性とコード品質を保証します。
- 自動コードマイグレーション機能により、フレームワークやツールを更新し、ワークスペースを常に最新の状態に保つことができます。
How Does Nx Work?
Nxは必要な機能だけを使えるようにモジュール方式で作られています。
- Nxパッケージは、ワークスペース分析、タスク実行、キャッシュ、配布、コード生成、自動コード移行など、技術にとらわれない基本的な機能を提供する。
- プラグインは Nx パッケージが提供する基本的な機能の上に構築された NPM パッケージです。Nx プラグインには、コードジェネレータ、(低レベルのビルドツールを抽象化する)エグゼキュータ、ツールを最新の状態に保つための自動コードマイグレーションが含まれています。NxパッケージはJSプロジェクトでも非JSプロジェクトでも同じように動作しますが、プラグインは通常テクノロジーに特化しています。例えば、@nx/reactはReactアプリとライブラリのビルドをサポートし、@nx/cypressはCypressによるe2eテスト機能を追加する。プラグインは、異なるツールを互いに統合する際の摩擦を取り除き、それらを最新の状態に保つためのユーティリティを提供することで、開発者の生産性を向上させる。NxチームはReact、Next、Remix、Angular、Jest、Cypress、Storybookなどのプラグインを保守しています。nx/pluginパッケージを使えば、簡単に新しいプラグインを作ることができますし、ローカルのワークスペースを自動化することもできます。80以上のコミュニティ・プラグインもあります。
- DevkitはNxプラグインをビルドするためのユーティリティセットです。
- Nx Cloud はリモートキャッシングと分散タスク実行を追加することで、CI 上でのプロジェクトのスケールアップを支援します。また、GitHub、GitLab、BitBucket と統合し、検索可能な構造化ログを提供することで、開発者のエルゴノミクスを改善します。詳しくはnx.appをご覧ください。
- Nx ConsoleはVSCode、IntelliJ、VIM用の拡張機能です。コードの自動補完、インタラクティブなジェネレータ、ワークスペースの視覚化、強力なリファクタリングなどを提供します。こちらからインストールできます。
チュートリアル
Getting Started with Package-Based Repos
新しいワークスペースを作成
npx create-nx-workspace@latest package-based --preset=npm
package-based/
├── packages/
├── nx.json
└── package.json
is-even フォルダを作成
package-based/
├── packages/
│ └── is-even/
│ ├── index.ts
│ └── package.json
├── nx.json
└── package.json
export const isEven = (x: number) => x % 2 === 0;
{
"name": "is-even",
"version": "0.0.0",
"main": "dist/index.js",
"devDependencies": {},
"scripts": {
"build": "tsc index.ts --outDir dist"
}
}
次にTypeScriptをインストールする(上のpackage.jsonでビルドスクリプトにtscを使っていることに注意)。TypeScriptはパッケージレベルでインストールすることもできるが、monorepo全体に対してグローバルにインストールした方が便利だ。ワークスペースのルートで以下のコマンドを実行する。
npm i typescript -D -W
is-even をビルドする
npx nx build is-even
packages/is-even/distディレクトリが生成される
Local Linking of Packages
パッケージベースのmonorepoスタイルでパッケージをローカルにリンクするには、NPM/Yarn/PNPMワークスペースを使用します。この特定のセットアップでは、NPMワークスペースを使用します(ワークスペースのルートにあるpackage.jsonファイルのworkspacesプロパティを参照してください)。
パッケージをローカルでリンクする方法を説明するために、is-oddという別のパッケージを作成してみましょう。既存のis-evenパッケージをコピーできます:
import { isEven } from 'is-even';
export const isOdd = (x: number) => !isEven(x);
{
"name": "is-odd",
"version": "0.0.0",
"main": "dist/index.js",
"devDependencies": {},
"scripts": {
"build": "tsc index.ts --outDir dist"
},
"dependencies": {
"is-even": "*"
}
}
is-oddは、is-evenパッケージからisEven関数をインポートします。したがって、その package.json ファイルには、依存関係として is-even パッケージがリストされているはずです。
ルートレベルのpackage.jsonのworkspacesプロパティは、packagesディレクトリにあるすべてのパッケージのリンクを作成するようNPMに指示します。これにより、最初にNPMレジストリに公開する必要がなくなります。(YarnやPNPMのワークスペースにも同様の機能があります)。
ワークスペースのルートで以下を実行
npm install
NPMはファイルシステム内のnode_modules/is-evenとnode_modules/is-oddにシンボリックリンクを作成するので、packages/is-evenとpackages/is-oddディレクトリの変更が反映されます。
以下を実行すると is-odd がビルドでき、is-odd/distフォルダ作成される。
npx nx build is-odd
Task Dependencies
ほとんどのモノレポは、異なるパッケージ間の依存関係だけでなく、タスク間の依存関係も持っている。
例えば、is-oddをビルドするときは、必ず事前にis-evenがビルドされていることを確認する必要がある。Nxは、nx.jsonにtargetDefaultsプロパティを追加することで、このようなタスクの依存関係を定義することができる。
{
...
"targetDefaults": {
"build": {
"dependsOn": ["^build"]
}
}
}
これは、パッケージ自体のビルドターゲットを実行する前に、すべての依存プロジェクトのビルドターゲットを最初に実行するようにNxに指示します。
既存の dist フォルダーをすべて削除して以下を実行
npx nx build is-odd
まず自動的にis-evenパッケージのビルドが実行され、次にis-oddのビルドが実行される。is-evenが以前にビルドされていた場合は、キャッシュから復元されるだけであることに注意。
Running Multiple Tasks
ワークスペース内の全パッケージのビルドターゲットを実行するには、次のようにする:
~/workspace❯
npx nx run-many -t build
✔ nx run is-even:build [existing outputs match the cache, left as is]
✔ nx run is-odd:build [existing outputs match the cache, left as is]
————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for 2 projects (35ms)
Nx read the output from the cache instead of running the command for 2 out of 2 tasks.
どちらのビルドもキャッシュから再生されていることに注目してほしい。skip-nx-cacheオプションを追加することで、キャッシュをスキップすることができる:
~/workspace❯
npx nx run-many -t build --skip-nx-cache
✔ nx run is-even:build (1s)
✔ nx run is-odd:build (1s)
———————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for 2 projects (2s)
このメソッドを使用すると、is-evenビルドがis-oddビルドの前に実行され、is-evenビルドは一度しか実行されないことに注意。これは、先ほど設定したtargetDefaultsによってrun-manyがどのように通知されるかを示している。
コマンドを使うことで、変更されたパッケージに対してのみタスクを実行することもできる:
~/workspace❯
npx nx affected -t build
> NX Affected criteria defaulted to --base=main --head=HEAD
✔ nx run is-even:build [existing outputs match the cache, left as is]
✔ nx run is-odd:build [existing outputs match the cache, left as is]
——————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for 2 projects (34ms)
Nx read the output from the cache instead of running the command for 2 out of 2 tasks.
baseとheadのオプションにデフォルト値が入力されていることに注目してほしい。必要に応じて、ここに独自のオプションを指定することができる。キャッシュも影響を受けるコマンドで使われていることに注目してほしい。
Integrate with Editors | Nx
Nx ConsoleはNxのUIです。インストールされているジェネレータやワークスペースに定義されているターゲットに対して動作します。Nx Consoleは、例えばコンポーネントを生成するための特定のUIを持っていません。代わりに、Nx ConsoleはNxのコマンドライン版と同じように、同じメタ情報を解析して必要なUIを作成します。つまり、NxでできることはNx Consoleでもできるということです。
モノレポパッケージのグラフやコマンドの実行などができるツール
Getting Started with Integrated Repos
プロジェクト作成
npx create-nx-workspace@latest myorg --preset=ts
myorg/
├── packages/
├── tools/
├── nx.json
├── package.json
├── README.md
└── tsconfig.base.json
Create a Package
Nxには、アプリケーションの足場固めに役立つジェネレーターが用意されている。このジェネレーターを実行すると、is-evenという名前の新しいライブラリーが作成される:
myorg % npx nx generate @nx/js:library is-even --publishable --importPath @myorg/is-even
> NX Generating @nx/js:library
✔ Which unit test runner would you like to use? · jest
✔ Which bundler would you like to use to build the library? Choose 'none' to skip build setup. · vite
Fetching @nx/linter...
Fetching @nx/vite...
Fetching @nx/jest...
UPDATE .vscode/extensions.json
CREATE packages/is-even/tsconfig.json
CREATE packages/is-even/README.md
CREATE packages/is-even/src/index.ts
CREATE packages/is-even/src/lib/is-even.spec.ts
CREATE packages/is-even/src/lib/is-even.ts
CREATE packages/is-even/tsconfig.lib.json
CREATE packages/is-even/package.json
CREATE tools/scripts/publish.mjs
CREATE packages/is-even/project.json
UPDATE package.json
CREATE .verdaccio/config.yml
CREATE project.json
UPDATE nx.json
CREATE packages/is-even/vite.config.ts
CREATE .eslintrc.json
CREATE .eslintignore
CREATE packages/is-even/.eslintrc.json
CREATE jest.preset.js
CREATE jest.config.ts
CREATE packages/is-even/jest.config.ts
CREATE packages/is-even/tsconfig.spec.json
UPDATE tsconfig.base.json
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
added 556 packages, and audited 909 packages in 32s
116 packages are looking for funding
run `npm fund` for details
39 moderate severity vulnerabilities
To address issues that do not require attention, run:
npm audit fix
To address all issues possible (including breaking changes), run:
npm audit fix --force
Some issues need review, and may require choosing
a different dependency.
Run `npm audit` for details.
shiratoritakashi@shiratoinoM1MBP myorg %
このコマンド:
- nx/jsプラグインのライブラリジェネレータを使って、is-evenという名前の新しいライブラリをscaffoldする。
- publishableフラグを指定すると、package.jsonが生成され、NPMに公開するために呼び出すことができる公開ターゲットが生成されます。
- importPathで、NPMパッケージの名前を定義できる。
これで、以下のような構造になるはずだ:
packages/
└── is-even/
├── src/
| └── lib/
| | ├── is-even.spec.ts
| | ├── is-even.ts
| └── index.ts
├── project.json
├── package.json
├── ...
└── tsconfig.json
is-even.tsをこの内容で更新する:
export function isEven(x: number): boolean {
return x % 2 === 0;
}
Nxプラグインは、プロジェクトレベルのproject.jsonを使用して、指定されたプロジェクトで実行可能なターゲットのメタデータを管理します。is-even用に生成されたproject.jsonには、build、publish、lint、testの各ターゲットが含まれています:
{
"name": "is-even",
"targets": {
"build": {
/* ... */
},
"publish": {
/* ... */
},
"lint": {
/* ... */
},
"test": {
/* ... */
}
}
}
実際の内容
{
"name": "is-even",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/is-even/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/is-even"
}
},
"publish": {
"command": "node tools/scripts/publish.mjs is-even {args.ver} {args.tag}",
"dependsOn": ["build"]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/is-even/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "packages/is-even/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
"tags": []
}
様々な設定に飛び込んで、パッケージのビルド、パブリッシュ、リンティング、テストに使われるオプションを微調整することができる。低レベルの詳細は、プラグインによって処理されます。
Running
- npx nx build is-even は src ファイルをビルドし、ワークスペースのルートにある dist/packages/is-even にすぐに公開できるパッケージを配置します。
- npx nx publish is-even は dist/packages/is-even から発行スクリプトを実行し、パッケージを NPM にプッシュします。
- npx nx test is-even は、パッケージ用に設定済みの Jest テストを実行します。
- npx nx lint is-even は、設定済みの ESLint チェックをパッケージに対して実行します。
Local Linking of Packages
統合されたmonorepoスタイルでのパッケージのローカルリンクは、tsconfig.base.jsonファイル内のTypeScriptパスマッピングを活用することで、Nxが自動的に処理する。
これを説明するために、is-oddパッケージを作成してみよう。これについてもジェネレーターを実行することができる:
myorg % npx nx generate @nx/js:library is-odd --publishable --importPath @myorg/is-odd
> NX Generating @nx/js:library
✔ Which unit test runner would you like to use? · jest
✔ Which bundler would you like to use to build the library? Choose 'none' to skip build setup. · vite
CREATE packages/is-odd/tsconfig.json
CREATE packages/is-odd/README.md
CREATE packages/is-odd/src/index.ts
CREATE packages/is-odd/src/lib/is-odd.spec.ts
CREATE packages/is-odd/src/lib/is-odd.ts
CREATE packages/is-odd/tsconfig.lib.json
CREATE packages/is-odd/package.json
CREATE packages/is-odd/project.json
CREATE packages/is-odd/vite.config.ts
CREATE packages/is-odd/.eslintrc.json
CREATE packages/is-odd/jest.config.ts
CREATE packages/is-odd/tsconfig.spec.json
UPDATE tsconfig.base.json
tsconfig.base.jsonに2つのエントリーがあることに注意してください:
{
"compileOnSave": false,
"compilerOptions": {
...
"paths": {
"@myorg/is-even": ["packages/is-even/src/index.ts"],
"@myorg/is-odd": ["packages/is-odd/src/index.ts"]
}
}
}
is-oddパッケージのis-odd.tsの実装を更新し、@myorg/is-evenパッケージからisEvenをインポートする:
import { isEven } from '@myorg/is-even';
export function isOdd(x: number): boolean {
return !isEven(x);
}
必要なのはこれだけ
Task Dependencies
モノレポでは、パッケージ間の依存関係だけでなく、タスク間の依存関係も存在する。
例えば、is-oddをビルドするときは、必ず事前にis-evenがビルドされていることを確認する必要がある。Nxは、nx.jsonにtargetDefaultsプロパティを追加することで、このようなタスクの依存関係を定義することができる。
統合されたmonorepoスタイルでは、これはすでにジェネレーターによって処理されている。現在のnx.jsonファイルにはすでにデフォルトが用意されており、すぐに動作するようになっている:
{
...
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
...
},
...
},
...
}
これは、パッケージ自体のビルドターゲットを実行する前に、すべての依存プロジェクトのビルドターゲットを最初に実行するようにNxに指示します。
ワークスペースのルートにある既存の dist フォルダをすべて削除して実行します:
npx nx build is-odd
最初に自動的にnpx nx build is-evenが実行されるので、distフォルダに両方のパッケージがあるはずだ。is-evenが以前にビルドされていた場合は、キャッシュから復元されることに注意してください。
myorg % npx nx build is-odd
✔ 1/1 dependent project tasks succeeded [0 read from cache]
Hint: you can run the command with --verbose to see the full dependent project outputs
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> nx run is-odd:build
vite v4.4.2 building for production...
✓ 4 modules transformed.
../../dist/packages/is-odd/index.mjs 0.10 kB │ gzip: 0.09 kB
../../dist/packages/is-odd/index.js 0.15 kB │ gzip: 0.15 kB
[vite:dts] Start generate declaration files...
✓ built in 576ms
[vite:dts] Declaration files built in 503ms.
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project is-odd and 1 task it depends on (5s)
View logs and investigate cache misses at https://nx.app/runs/hCtT5ZlDZB
dist
┗ packages
┃ ┣ is-even
┃ ┃ ┣ lib
┃ ┃ ┃ ┗ is-even.d.ts
┃ ┃ ┣ index.d.ts
┃ ┃ ┣ index.js
┃ ┃ ┣ index.mjs
┃ ┃ ┗ package.json
┃ ┗ is-odd
┃ ┃ ┣ lib
┃ ┃ ┃ ┗ is-odd.d.ts
┃ ┃ ┣ index.d.ts
┃ ┃ ┣ index.js
┃ ┃ ┣ index.mjs
┃ ┃ ┗ package.json
packages
┣ is-even
┗ is-odd
package.json
tsconfig.base.json
^
の記号について
Examples:
- inputs: ["^production"]
- same as inputs: [{"input": "production", "projects": "dependencies"}] prior to Nx 16, or "inputs": [{"input": "production", "dependencies": true }] after version 16.
dependsOnと同様に、記号"^"は「依存関係」を意味する。これは非常に重要な考え方なので、例で説明しよう。
v16以降は^
をつけると"dependencies": true
を指定したのと同じ意味になる
{
"targets": {
"test": {
"inputs": ["default", "^production"]
}
}
}
上記の設定は、テストターゲットが、指定されたプロジェクトのすべてのソースファイルと、その依存関係の prod ソース(非テストソース)のみに依存することを意味します。言い換えると、テスト用ソースをプライベートなものとして扱います。
dependsOn
ターゲットは他のターゲットに依存することができる。これがコンフィギュレーションファイルの該当部分である:
{
"targets": {
"build": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
}
}
}
一般的なシナリオでは、プロジェクトをビルドする前に、まずプロジェクトの依存関係をビルドする必要があります。 これは、ビルド ターゲットの "dependsOn": ["^build"] プロパティで設定されるものです。 これは、mylib をビルドする前に、mylib の依存関係もビルドされていることを確認する必要があることを Nx に伝えます。 これは、Nx がそれらのビルドを再実行するという意味ではありません。 適切なアーティファクトがすでに適切な場所にある場合、Nx は何も行いません。 それらが正しい場所になくてもキャッシュ内で利用可能な場合、Nx はそれらをキャッシュから取得します。
もう 1 つの一般的なシナリオは、ターゲットが同じプロジェクトの別のターゲットに依存することです。 たとえば、テスト ターゲットの "dependsOn": ["build"] は、mylib をテストする前に mylib がビルドされていることを確認する必要があることを Nx に伝え、その結果、mylib の依存関係もビルドされます。
targets
プロジェクトに対して実行できるタスクを定義するすべてのターゲットを構成します。
ターゲットとはbuildやtestなどのタスクのこと。
Target Defaults
ターゲットのデフォルトは、ワークスペースの特定のターゲットに共通のオプションを設定する方法を提供します。プロジェクトのコンフィギュレーションをビルドするときに、このマップから最大1つのデフォルトとマージします。与えられたターゲットについて、その名前と実行者を調べます。次に、以下の組み合わせのどれかに該当するターゲットのデフォルトをチェックします:
${executor}
${targetName}
これらのどれが最初に見つかったとしても、そのターゲットのコンフィギュレーションのベースとして使用します。よくあるシナリオを以下に示します。
ターゲットは他のターゲットに依存することができます。よくあるシナリオは、プロジェクトをビルドする前に、まずプロジェクトの依存関係をビルドしなければならない場合です。project.jsonのdependsOnプロパティを使って、個々のターゲットの依存関係のリストを定義することができます。
多くの場合、同じdependsOn設定をレポ内のすべてのプロジェクトに対して定義する必要があり、nx.jsonでtargetDefaultsを定義すると便利です。
{
"targetDefaults": {
"build": {
"dependsOn": ["^build"]
}
}
}
上記の設定は、すべてのプロジェクトのビルド・ターゲットに{"dependsOn":["^build"]}を追加するのと同じである。
dependsOnプロパティの完全な説明は、プロジェクト設定リファレンスを参照してください。
ターゲットの定義
{
"name": "common-ui",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/common-ui/src",
"projectType": "library",
"tags": [],
"targets": {
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/common-ui/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/common-ui"],
"options": {
"jestConfig": "libs/common-ui/jest.config.ts",
"passWithNoTests": true
}
}
}
}
ここではtestとlintという2つのターゲットが定義されているのがわかる。
それぞれのターゲット内のプロパティは以下のように定義されている:
- executor - 実行するNxエグゼキューター。このシンタックスは<プラグイン名>:<エクゼキュータ名
- outputs - このターゲットを実行することによって作成されるファイルの配列です。(これは、4ワークスペースの最適化で説明するキャッシュ機構のためにNxに何を保存すべきかを知らせます)。
- options - 与えられたターゲットに使用するエクゼキュータオプションを定義するオブジェクトです。Nx の全てのエクゼキュータは、その機能をパラメータ化する方法としてオプションを許可しています。
common-ui プロジェクトのテスト ターゲットを実行します。
~/myorg❯
npx nx test common-ui
> nx run common-ui:test
PASS common-ui libs/common-ui/src/lib/common-ui.spec.tsx
PASS common-ui libs/common-ui/src/lib/banner/banner.spec.tsx
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.84 s, estimated 1 s
Ran all test suites.
———————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target test for project common-ui (2s)
次に、common-uiプロジェクトのlintチェックを実行する:
~/myorg❯
npx nx lint common-ui
> nx run common-ui:lint
Linting "common-ui"...
All files pass linting.
———————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target lint for project common-ui (2s)