Skip to content

Instantly share code, notes, and snippets.

@asa1984
Last active December 7, 2024 12:59
Show Gist options
  • Save asa1984/e112af9dccdb97c5668dee9291da1a80 to your computer and use it in GitHub Desktop.
Save asa1984/e112af9dccdb97c5668dee9291da1a80 to your computer and use it in GitHub Desktop.
Turborepo / asa1984 Advent Calendar 2024 7日目

Turborepo

「asa1984 Advent Calendar 2024」7日目の記事です。

今日は Turborepo の機能とユースケースについて説明していきます。

モノレポ管理ツール・Turborepo

Turborepo は JavaScript/TypeScript のモノレポをいい感じに管理するツールです。モノレポとは、普通は別々のリポジトリに分離するようなプロジェクトを1つのリポジトリにまとめて管理する手法で、 JS/TS の場合だと1リポジトリに package.json で管理された複数のパッケージが配置されている感じです。

モノレポ内のパッケージは相互に依存することができ、パッケージ単位で開発スコープを分離できる上、1リポジトリにまとまっているのでコロケーション的な意味でスピーディーに開発を進められます。npm, yarn, pnpm, Bun には標準でモノレポを実現する機能が搭載されており、子パッケージを「ワークスペース」と呼称して管理できます。

しかし、モノレポで開発を行おうとすると次のような面倒なことが発生します。

  1. Lint や型検査といった共通のタスクをワークスペースごとにいちいち手動で実行しないといけない
  2. ビルドの際、パッケージ間の依存関係を考慮して手続き的に実行しないといけない
  3. ある変更がどのワークスペースに影響を与えるか判断するのが難しく、無駄な静的検査やビルドが発生する

これらをいい感じに管理しようというのがモノレポ管理ツールです。Tureborepo は比較的新しいツールで、Vercel が開発しています。Rust で実装されており、自動で依存関係を追跡するタスク管理機能とキャッシュ機能、そして複数のタスクをいい感じに表示してくれる TUI が特徴です。

Turborepo は 各パッケージマネージャのワークスペース機能に対応しており、それらをラップする形でタスクを実行してくれます。

タスク管理

turbo.json というファイルでタスクを管理します。次のような turbo.json があったとします。

{
  "tasks": {
    "gen": {},
    "typecheck": {
      "dependsOn": ["gen"]
    },
    "build": {
      "dependsOn": ["gen", "^build"]
    },
    "dev": {
      "dependsOn": ["gen"],
      "cache": false,
      "persistent": true
    }
  }
}

コード生成、型検査、ビルド、開発サーバーの起動という典型的なタスクです。

turbo.jsontasks はちょうど package.jsonscripts に対応します。この状態で turbo run gen を実行すると、package.jsongen を持つワークスペース全てで 並列に スクリプトが実行されます。

typecheck タスクは gen タスクでコード生成を行った後に実行しなければいけません。そういう時は dependsOn でタスクを他のタスクに依存させます。今回の例の場合は、typecheck タスクは gen タスク終了後に実行されます。

build タスクの dependsOn には ^build という先頭に記号のついたものが指定されています。これは、各ワークスペースのスクリプトを 直列 に実行するというモノです。ワークスペース間の依存関係に従って、ワークスペースごとに build スクリプトを逐次実行します。パッケージ A のビルド成果物に パッケージ B が依存している、というようなケースで使用します。Turborepo は package.json の dependencies/ devDependencies を解析して自動で依存関係グラフを作ってくれるので、開発者が実行順序を指定する必要はありません。

dev タスクは、自分では終了しない他とは性質の異なるタスクです。まず、実行結果をキャッシュする必要がない(できない)ので、cache を false にして後述のキャッシュ機能をオフにします。また、persistant を true にしてタスクを終了せずにずっと実行状態のままでいることを許可します。

キャッシュ機能

Turborepo はデフォルトでタスクの実行結果をキャッシュします。前述の例の gen タスクを2回実行すると、2回目にはキャッシュされた前回の実行結果(標準出力の内容)を出力して終了します。ソースコードを変更すると Turborepo は自動的にキャッシュを破棄し、それに依存しているワークスペースのキャッシュも同様に破棄されます。

デフォルトのキャッシュは最低限のキャッシュしか行わないので、開発者が厳密な条件を指定してより強いキャッシュを実現する、というのが Turborepo の基本的な使い方になります。

詳細は公式ドキュメントに任せますが、ここでは一番つまづきやすい環境変数の扱い方について解説します。

環境変数を扱う

Tureborepo を初めて使う人や Turborepo 1.x を使っていた人が特に詰まりやすいのが環境変数の扱いです。Turborepo 2.x は、デフォルト設定だと環境変数を strict mode で扱います。

strict mode は、タスクの env に明示的に指定された環境変数以外をスクリプトの実行環境に渡さないというモードです。env で指定された環境変数はキャッシュを識別するハッシュの算出に利用されるので、Turborepo の自動キャッシュをより安全に利用することができます。 当然ながら env に環境変数を指定してやらないとタスクがこけるので注意してください。

globalEnv に環境変数を指定すると全タスクでその環境変数を利用することができますが、環境変数が変わると問答無用で全タスクのキャッシュが無効になるので、できる限り使用は避けましょう。

タスクの実行結果に影響しない環境変数に関しては、タスクの passThroughEnv に渡すか、globalPassThroughEnv に指定して全タスクから利用できるようにしましょう。ここで指定された環境変数の中身が変わってもキャッシュは有効なまま残ります。

筆者は wrangler の認証に用いる ID とトークンを globalPassThrough に指定して運用しています。

TUI

"ui": "tui"を指定するとタスクのログを TUI で眺めることができます。並列実行しているタスクの確認や複数の開発サーバーのログの確認に非常に便利です。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment