Open5

Turborepo

nakaakistnakaakist

https://turbo.build/repo/docs

Turborepoとはintelligent build systemである。

lint, build, testのようなコマンドを、キャッシュなどの活用により高速に行うことができる。

既存システムにすぐ組み込める

nakaakistnakaakist

Installl Turborepo

pnpm install turbo --globalでグローバルインストールもできるが、チーム開発するならpnpm add turbo --save-dev --ignore-workspace-root-checkでリポジトリごとにインストール。

Add Turborepo to your existing monorepo

  • turborepoは、既存のパッケージマネージャのworkspace機能とcompatible。
  • npm, yarn, pnpmなどでworkspaceがすでに設定されているものとする。

以下導入手順。

  1. モノレポのルートにturbo.jsonを作る。この時、モノレポのタスクの依存関係グラフを構築するために、pipelineを指定する。dependsOnで依存関係を明示する
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      // A package's `build` script depends on that package's
      // dependencies and devDependencies
      // `build` tasks  being completed first
      // (the `^` symbol signifies `upstream`).
      "dependsOn": ["^build"],
      // note: output globs are relative to each package's `package.json`
      // (and not the monorepo root)
      "outputs": [".next/**", "!.next/cache/**"]
    },
    "test": {
      // A package's `test` script depends on that package's
      // own `build` script being completed first.
      "dependsOn": ["build"],
      // A package's `test` script should only be rerun when
      // either a `.tsx` or `.ts` file has changed in `src` or `test` folders.
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
    },
    // A package's `lint` script has no dependencies and
    // can be run whenever. It also has no filesystem outputs.
    "lint": {},
    "deploy": {
      // A package's `deploy` script depends on the `build`,
      // `test`, and `lint` scripts of the same package
      // being completed. It also has no filesystem outputs.
      "dependsOn": ["build", "test", "lint"]
    }
  }
}
  1. できたら、npx turbo run deployなどでタスクを走らせる

リモートキャッシュ

turborepoは、デフォルトではローカルマシンにキャッシュを置くが、リモートキャッシュを使うことで、CIや他の人のタスク実行結果のキャッシュを活用できる(dropboxみたいなもん)

以下手順。

  1. turbo loginでvercelアカウントにログイン。
  2. turbo linkでリモートキャッシュとturborepoを紐付け。そうすると、自動でremote cacheが作られる。この状態でlocal cacheを削除しても、ちゃんとremote cacheが使われるようになってる。
nakaakistnakaakist

Core concepts

キャッシュ

  • turborepoは、タスクのinput (e.g., ソースファイル, デフォルトでは、workspace内の全ファイル)のハッシュを計算、それに対応するキャッシュがあるかチェックし、あったらそれを使う。キャッシュがなかったらタスクを実行、outputに指定されたアーティファクトを保存

リモートキャッシュ

  • 前述の通り

モノレポ

  • 何百ものタスクが走るモノレポはスケールが難しい。
  • これをturborepoは、1. キャッシュ、2. タスク間の依存関係に基づく実行スケジューリング によって解決
  • turborepoは、npm, pnpm, yarnのようにパッケージのインストールは行わない

タスクの実行

  • 3workspaceあるケースで普通に実行した場合が下記。
  • turborepoでは、下記のように実行できる

ワークスペースのフィルタリング

  • デフォルトでは、モノレポのルートでturbo run testとかやると、全パッケージでタスクが走る。--filterオプションで、対象のワークスペースを絞れる

ワークスペースごとの設定

  • version 1.8から導入
  • turbo.jsonを、ルートディレクトリだけでなく、その配下の任意のワークスペースにおける。
  • 例えば、Next.jsとSveleteKitが混在するケース。ルートだけにturbo.jsonを置くパターンだと、下記のようになり気持ち悪い。
{
  "pipeline": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**", ".svelte-kit/**"],
    }
  }
}
  • ワークスペースごとにすれば、SvelteKit側を下記のように書ける。next.js側も同様。
{
  "extends": ["//"],
  "pipeline": {
    "build": {
      "outputs": [".svelte-kit/**"]
    }
  }
}
nakaakistnakaakist

競合ツール

State of Javascript 2022を見ると、下記があげらている。

  • Rush
  • Yalc
  • Lerna
  • Nx
  • npm
  • yarn
  • pnpm

awareness

usage

interest

retention

全体として、pnpm, npm, yarnなどのパッケージマネージャのworkspace機能か、もしくはNxが大きな競合になってそう。

Nxに関しては、turborepoより高機能でDXも良い、という意見がある。一方、turborepoはシンプル。

プロジェクトの規模によって、だいたい下記のような使い分けか。

  • 大規模: Nx
  • 中規模: Turborepo
  • 小規模: pnpm, npm, yarn
nakaakistnakaakist

パッケージごとに設定を分ける

https://turbo.build/repo/docs/reference/package-configurations

  • パッケージごとにturbo.jsonを書いて、タスクの内容をカスタムすることができる。その場合、各appやpackageごとに下記の内容にする
{
  "extends": ["//"],
  "tasks": {
    "build": {
      // Custom configuration for the build task in this package
    },
    "special-task": {} // New task specific to this package
  }
}
  • extendsでtop-levelのturbo.jsonを参照。tasksに、カスタムしたいタスクを書いていく。(何も書かなかったらtop-levelの内容がそのまま引き継がれる)
  • 機能的には、ルートのturbo.jsonでpackageごとの設定をしていくのと同じに見える。しかし、ルートでこれをやると各設定が「merge」ではなく完全に「上書き」になってしまうのでちょっと煩雑。(e.g., 特定タスクのoutputだけカスタマイズしたいのに、inputも書かないといけなくなる)