タスクの依存関係
Turborepo は、タスク同士の関係を表現することで最も強力になります。これらの関係を「依存関係」と呼びますが、これは package.json
ファイルからインストールするパッケージの依存関係とは異なります。Turborepo はワークスペースを理解しますが、turbo.json
で dependsOn
設定で明示的に表現しない限り、タスク間の関係を自動的に描画しません。
タスクを他のタスクに依存させる一般的なパターンについて見ていきましょう。
同じワークスペースから
他のタスクの前に実行する必要があるタスクがあるかもしれません。たとえば、build
は deploy
の前に実行する必要があるかもしれません。
両方のタスクが同じワークスペースにある場合は、次のように関係を指定できます。
{
"$schema": "https://turbo.dokyumento.jp/schema.json",
"pipeline": {
"build": {},
"deploy": {
// A workspace's `deploy` task depends on the `build` task of the same workspace.
"dependsOn": ["build"]
}
}
}
これは、turbo deploy
が実行されるたびに、同じワークスペース内で build
も実行されることを意味します。
依存するワークスペースから
モノレポの一般的なパターンは、ワークスペースの build
タスクは、その依存しているワークスペースすべての build
タスクが完了した後にのみ実行するように宣言することです。
これは、ワークスペースの依存関係とタスクの依存関係の両方を指しているため、混乱を招く可能性があります。これらは異なる概念です。ワークスペースの依存関係は、package.json
のdependencies
とdevDependencies
であり、タスクの依存関係はturbo.json
のdependsOn
キーです。
^
記号(「キャレット」と呼ばれる)は、タスクが依存するワークスペース内のタスクに依存することを明示的に宣言します。
{
"$schema": "https://turbo.dokyumento.jp/schema.json",
"pipeline": {
"build": {
// A workspace's `build` command depends on its dependencies'
// and devDependencies' `build` commands being completed first
"dependsOn": ["^build"],
}
}
}
上記の設定では、アプリが別のワークスペースからパッケージをインストールする場合、パッケージのbuild
スクリプトは、常にアプリのbuild
スクリプトより前に実行されます。
任意のワークスペースから
ワークスペースのタスクが別のワークスペースのタスクに依存するようにしたい場合があります。これは、lerna
やrush
から移行するリポジトリで特に役立ちます。これらのツールでは、タスクはデフォルトで別々のフェーズで実行されます。上記のように、これらの構成は、単純なpipeline
構成では表現できない前提を設けている場合があります。または、CI/CDでturbo
を使用する際に、アプリケーションやマイクロサービス間のタスクの順序を表現したい場合もあります。
このような場合、pipeline
構成で<workspace>#<task>
構文を使用してこれらの関係を表現できます。以下の例では、backend
のdeploy
スクリプトとhealth-check
スクリプト、およびui
ワークスペースのtest
スクリプトに依存するfrontend
アプリケーションのdeploy
スクリプトについて説明しています。
{
"$schema": "https://turbo.dokyumento.jp/schema.json",
"pipeline": {
// Explicit workspace-task to workspace-task dependency
"frontend#deploy": {
"dependsOn": ["ui#test", "backend#deploy", "backend#health-check"]
}
}
}
frontend#deploy
に対するこの明示的な構成は、test
およびdeploy
タスクの構成と矛盾するように見えるかもしれませんが、そうではありません。test
とdeploy
は他のワークスペース(例:^<task>
)に依存関係がないため、ワークスペースのbuild
スクリプトとtest
スクリプトが完了した後であれば、いつでも実行できます。
注記
- この
<workspace>#<task>
構文は便利なエスケープハッチですが、一般的には、ビルド時の依存関係ではなく、ヘルスチェックなどのデプロイメントオーケストレーションタスクに使用することをお勧めします。これにより、Turborepoはこれらのタスクをより効率的に最適化できます。 - ワークスペースのタスクはキャッシュ構成を継承しません。現時点では
outputs
を再宣言する必要があります。 <workspace>
は、ワークスペースのpackage.json
のname
キーと一致する必要があります。そうでない場合、タスクは無視されます。
依存関係なし
空の依存関係リスト(dependsOn
が未定義または[]
)は、このタスクの前に何も実行する必要がないことを意味します。結局のところ、依存関係がないのですから。
{
"$schema": "https://turbo.dokyumento.jp/schema.json",
"pipeline": {
// A workspace's `lint` command has no dependencies and can be run any time.
"lint": {}
}
}
タスク外の依存関係
2つのアプリ、docs
とweb
で使用している共通のui
パッケージがあるとしましょう。
apps/
docs/package.json # Depends on ui
web/package.json # Depends on ui
packages/
ui/package.json # No workspace dependencies
turbo.json
package.json
ワークスペースでTypeScriptを記述し、型をチェックするためにtsc
を実行する時が来ました。ここでは2つの要件があります。
- すべてが高速に実行されるように、すべての型チェックを並行して実行する:型チェックの結果は互いに依存しないため、すべてを並行して実行できます。
- 依存関係の変更はキャッシュミスになるはずである:
ui
パッケージが変更された場合、docs
またはweb
の型チェックタスクはキャッシュミスになることを知っておく必要があります。
これを実現するには、グラフ内に偽の再帰的なタスクを作成し、それに依存します。
{
"$schema": "https://turbo.dokyumento.jp/schema.json",
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"typecheck": {
"dependsOn": ["topo"]
}
}
}
topo
タスクはスクリプト内に存在しないため、Turborepoはタスクを「即座に」完了し、そのワークスペースに依存していたすべてのワークスペースを探します。このため、タスクはタスクグラフ内の他のワークスペースとの関係を理解しながら、並行して実行されます。
ここでのtopo
という名前は特別な名前ではありません。「トポロジカル」の略であり、それが存在する理由を示すのに役立ちますが、このタスクは任意の名前を付けることができます。
なぜこれが機能するのですか?
なぜこれが機能するのかをより深く理解するために、要件をほぼ満たすパイプラインを見てみましょう。
以下のようにタスク定義からdependsOn
を省略することで、タスクを並行して実行できます。
{
"$schema": "https://turbo.dokyumento.jp/schema.json",
"pipeline": {
"typecheck": {} // Uh oh, not quite!
}
}
typecheck
タスクは並行して正常に実行されますが、ワークスペースの依存関係については認識しません!
次の手順でこれを実証できます。
turbo typecheck
を実行しますui
パッケージのソースコードを変更します。turbo typecheck --filter=web
を実行します
もしこれを実行すると、ステップ3でキャッシュヒットしますが、それは誤りです! web
ワークスペースで、ui
パッケージのコード変更によって型エラーが発生している可能性があります。ステップ3でのキャッシュヒットは誤ったもので、型エラーを隠蔽してしまいます。
この問題を解決するには、build
タスクと同様に、トポロジカル依存関係グラフに直接依存するように選択できます。
{
"$schema": "https://turbo.dokyumento.jp/schema.json",
"pipeline": {
"typecheck": {
"dependsOn": ["^typecheck"] // Uh oh, not quite!
}
}
}
これで、正しいキャッシュ動作になります。ui
のコードが変更された場合、web
はキャッシュミスになります。これは素晴らしいことです。しかし、パイプラインを高速に実行していた並列処理が失われてしまいました。ui
ワークスペースのtypecheck
タスクが完了するまで、web
のタスクは開始できません。
ui
のタスクで「即座に完了する」ものに依存し、依存するワークスペースでのtypecheck
コマンドをより早く開始できるようにしたらどうでしょうか?
ここで「偽の」topo
タスクが登場します。
{
"$schema": "https://turbo.dokyumento.jp/schema.json",
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"typecheck": {
"dependsOn": ["topo"]
}
}
}
このパイプラインでは、topo
という「合成」タスクを宣言します。package.json
ファイルにtopo
スクリプトがないため、turbo typecheck
パイプラインは、すべてのtypecheck
スクリプトを並行して実行します。これにより、最初の要件が満たされます。
しかし、このtopo
タスクは、web
からui
へ、またdocs
からui
への「合成」ワークスペース-タスク依存関係も作成します。これは、ui
のコードを変更すると、web
とdocs
のワークスペースでもキャッシュミスになることを意味し、2番目の要件を満たします。
パイプラインは、typecheck
がtopo
タスクに依存し、topo
が^topo
に依存すると宣言します。これは、同じワークスペースのtopo
タスクが、すべてのtypecheck
タスクよりも前に実行され、すべてのパッケージ依存関係のtopo
タスクが、topo
タスク自体よりも前に実行される必要があることを意味します。
なぜtypecheck
が直接^topo
に依存しないのか?と疑問に思うかもしれません。それは、ワークスペースが合成タスクを介してパッケージの依存関係を再帰的に結びつけるようにしたいからです。typecheck
が^topo
に依存する場合、turbo
は最初のレベルの依存関係の後にグラフへの追加を停止します。