キャッシュの仕組み
タスクを実行する前に、Lernaは計算ハッシュを算出します。計算ハッシュが同じであれば、タスク実行の出力は同じになります。
デフォルトでは、例えば`lerna run test --scope=remixapp`の計算ハッシュには以下が含まれます。
- `remixapp`とその依存関係のすべてのソースファイル
- 関連するグローバル設定
- 外部依存関係のバージョン
- ユーザーが指定したランタイム値(例:Nodeのバージョン)
- CLIコマンドフラグ
この動作はカスタマイズ可能です。例えば、lintチェックはプロジェクトのソースコードとグローバル設定のみに依存する場合があります。ビルドは、ソースではなく、コンパイルされたライブラリのdtsファイルに依存できます。
Lernaはタスクのハッシュを計算した後、以前に全く同じ計算を実行したかどうかを確認します。最初にローカルでチェックし、見つからない場合はリモートキャッシュが設定されていればリモートでチェックします。
Lernaが計算を見つけると、それを取得して再生します。Lernaは適切なファイルを適切なフォルダに配置し、端末出力を表示します。ユーザーの視点からは、コマンドは同じように実行されますが、はるかに高速です。
Lernaが対応する計算ハッシュを見つけられない場合、Lernaはタスクを実行し、完了後に出力と端末ログを取得してローカルに保存します(設定されていればリモートにも保存します)。これはすべて透過的に行われるため、ユーザーは心配する必要はありません。
概念的には非常に単純ですが、Lernaはユーザーエクスペリエンスを向上させるためにこれを最適化しています。例えば、Lernaは以下を行います。
- Windowsを含む、再生された出力が同じに見えるようにstdoutとstderrをキャプチャします。
- どのファイルがどこに再生されるかを記憶することで、IOを最小限に抑えます。
- 大きなタスクグラフを処理する際に、関連する出力のみを表示します。
- キャッシュミスをトラブルシューティングするための機能を提供します。その他多くの最適化があります。
ワークスペースが大きくなると、タスクグラフは次のようになります。
これらの最適化はすべて、Lernaを重要なワークスペースで使用できるようにするために不可欠です。最小限の作業のみが行われます。残りはそのままにされるか、キャッシュから復元されます。
ソースコードハッシュ入力
アプリケーションまたはライブラリのビルド/テストの結果は、そのプロジェクトのソースコードと、それが依存するすべてのライブラリのすべてのソースコード(直接または間接)に依存します。
デフォルトでは、Lernaは保守的です。例えば、`lerna run test --scope=remixapp`を実行すると、Lernaは`remixapp`ディレクトリ内のすべてのファイルと`header`および`footer`ディレクトリ(`remixapp`の依存関係)内のすべてのファイルを考慮します。これは不要なキャッシュミスにつながります。例えば、`footer`のspecファイルを変更しても、上記テストコマンドの結果は変わらないことがわかっています。
より正確な設定は次のように定義できます。
注: "{projectRoot}"と"{workspaceRoot}"はタスクランナーでサポートされている特別な構文であり、コマンド実行時に内部で適切に補間されます。そのため、"{projectRoot}"または"{workspaceRoot}"を固定パスに置き換えないでください。設定の柔軟性が低下します。
{
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"prod": ["!{projectRoot}/**/*.spec.tsx"]
},
"targetDefaults": {
"build": {
"inputs": ["prod", "^prod"]
},
"test": {
"inputs": ["default", "^prod", "{workspaceRoot}/jest.config.ts"]
}
}
}
この設定では、ビルドスクリプトは`remixapp`、`header`、`footer`のテスト以外のファイルのみを考慮します。テストスクリプトは、テスト対象プロジェクトのすべてのソースファイルと、その依存関係のテスト以外のファイルのみを考慮します。また、テストスクリプトはワークスペースのルートにあるjest設定ファイルも考慮します。
ランタイムハッシュ入力
ターゲットはランタイム値にも依存できます。
{
"targetDefaults": {
"build": {
"inputs": [{ "env": "MY_ENV_NAME" }, { "runtime": "node -v" }]
}
}
}
引数ハッシュ入力
最後に、ソースコードハッシュ入力とランタイムハッシュ入力に加えて、Lernaは引数を考慮する必要があります。例えば、`lerna run build --scope=remixapp`と`lerna run build --scope=remixapp -- --flag=true`は異なる結果を生成します。
npmスクリプト自体に渡されるフラグのみが計算結果に影響することに注意してください。例えば、キャッシングの観点からは、次のコマンドは同じです。
npx lerna run build --scope=remixapp
npx lerna run build --ignore=header,footer
言い換えれば、Lernaは開発者が端末に入力した内容をキャッシュしません。
複数のプロジェクトをビルド/テスト/lint…する場合、個々のビルドはそれぞれ独自のハッシュ値を持ち、キャッシュから取得されるか、実行されます。つまり、キャッシングの観点からは、次のコマンドは
npx lerna run build --scope=header,footer
次の2つのコマンドと同じです。
npx lerna run build --scope=header
npx lerna run build --scope=footer
キャッシュされるもの
Lernaはプロセスレベルで動作します。プロジェクトのビルド/テスト/lint/etc.に使用するツールに関係なく、結果はキャッシュされます。
Lernaは、コマンドを実行する前にstdout/stderrを収集するためのフックを設定します。すべての出力はキャッシュされ、キャッシュヒット時に再生されます。
Lernaはコマンドによって生成されたファイルもキャッシュします。ファイル/フォルダのリストは、プロジェクトの`package.json`の`outputs`プロパティに記載されています。
注: "{projectRoot}"と"{workspaceRoot}"はタスクランナーでサポートされている特別な構文であり、コマンド実行時に内部で適切に補間されます。そのため、"{projectRoot}"または"{workspaceRoot}"を固定パスに置き換えないでください。設定の柔軟性が低下します。
{
"nx": {
"targets": {
"build": {
"outputs": ["{projectRoot}/build", "{projectRoot}/public/build"]
}
}
}
}
特定のターゲットの`outputs`プロパティがプロジェクトの`package.json`ファイルで定義されていない場合、Lernaは`nx.json`の`targetDefaults`セクションを参照します。
{
...
"targetDefaults": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"{projectRoot}/dist",
"{projectRoot}/build",
"{projectRoot}/public/build"
]
}
}
}
どちらも定義されていない場合、Lernaはデフォルトでリポジトリのルートにある`dist`と`build`をキャッシュします。
キャッシュのスキップ
キャッシュをスキップしたい場合があります。例えば、コマンドのパフォーマンスを測定する場合、`--skip-nx-cache`フラグを使用して計算キャッシュのチェックをスキップできます。
npx lerna run build --skip-nx-cache
npx lerna run test --skip-nx-cache
追加設定
タスクとキャッシングを設定するその他の方法については、関連するNxドキュメントを参照してください。