workspaceを使ったコマンドを最適化して実行するTurborepoについて
Published on
今年からフロントエンドエキスパートチームでは活動内容の一つである探求の一環として、メンバーが気になった技術に対して、気軽に触ってみる会をしています。次の画像は筆者が Slack で、気軽に触ってみる会の開催を宣言してる時のものです。
今回は去年の 12 月に Vercel に買収されたニュースがあった Turborepo を気軽に触ってみました。 個人的には 1 人で調べるときよりも複数人でわいわい調べた方が、その技術や関連する周辺知識の話を色んな人の観点で深掘ってできて、とても有意義な時間でした。
概要
Turborepo はモノレポのためのビルドシステムで次のような特徴があります。
- Yarn, npm, pnpm の workspaces に対応してるリポジトリに対して簡単に導入できる
- workspace 内のコマンドの依存関係をシンプルに設定してくれる
- Turborepo で実行するコマンドがない package はスルーしてくれる(npm workspace の
--if-present
に相当) - ローカルキャッシュ、リモートキャッシュを生成・利用できる
サンプルで作ったモノレポ構成
今回は Yarn v1 の workspace を使ってます。
https://github.com/nus3/p-turborepo/tree/main/yarn
├── apps
│ └── nus3-a: Next.js
└── packages:
├── nus3-button2: Reactのコンポーネント + ViteのLibrary Modeでビルド
├── nus3-config: tsconfig + eslintのconfig
└── nus3-ui: Reactのコンポーネント(ビルドせずに使う)
サンプルで作ったモノレポ内の package たちは次のような依存関係になっています。
(色々試してたので適当な命名になってます。すんません)
nus3-a
はnus3-ui
とnus3-button2
に依存しているnus3-ui
はnus3-button2
に依存している
使い方
- 使っているパッケージマネージャーで(Yarn, npm, pnpm)で workspace の設定
turbo
のインストール- package.json に
pipeline
の設定
1. 使っているパッケージマネージャーで(Yarn, npm, pnpm)で workspace の設定
workspace の設定については割愛します。各パッケージマネージャーのドキュメントをご覧ください。
2. turbo
のインストール
yarn add turbo -W --dev
で Turborepo を追加します。
3. package.json にpipeline
の設定
package.json
にturbo.pipeline
を追加し、そこでturbo run {command}
で実行する command の依存関係やキャッシュの設定をします。このpipeline
で定義した command しかturbo run {command}
では実行できません。
実際に定義したものが次の json になります。
{
"turbo": {
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {},
"deploy": {
"dependsOn": ["build", "test", "lint"]
},
}
}
}
dependsOn
で依存関係を、outputs
でキャッシュするものを指定します。
定義したコマンドを一つずつ見ていきましょう。
"dependsOn": ["^build"],
dependOn に^
を追加したコマンド(^build
)を指定することで workspace 内の各 package の指定したコマンド(build
)を package 間の依存関係を考慮した順番で実行してくれます。
実際に実行してみると、サンプルで作った各 package の依存関係を考慮した順番(@nus3/example-button2
→ nus3-ui
→ nus3-a
)でビルドされていることがわかります。
(※今回サンプルで作った構成ではnus3-ui
はビルドの必要がないですが、依存関係を考慮した順番を表すのにあえて build コマンドを追加しています)
$ turbo run build
• Packages in scope: @nus3/example-button2, nus3-a, nus3-config, nus3-ui
• Running build in 4 packages
@nus3/example-button2:build: cache miss, executing a161062b16a0be35
@nus3/example-button2:build: $ vite build && tsc -p ./tsconfig.build.json
...
@nus3/example-button2:build: dist/nus3-example-button2.es.js 3.30 KiB / gzip: 1.39 KiB
nus3-ui:build: cache miss, executing b32d4fb848c8658f
nus3-ui:build: $ echo 'build nus3-ui'
...
nus3-a:build: cache miss, executing a80d8842624981a8
nus3-a:build: $ next build
...
Tasks: 3 successful, 3 total
Cached: 0 cached, 3 total
Time: 5.702s
次のoutputs
はコマンドを実行した際に、dist
と.next
配下にあるファイル群をキャッシュする設定です。node_modules/.cache/turbo
にキャッシュしたファイルが出力されます。
"outputs": ["dist/**", ".next/**"]
先程の build コマンドをもう一度実行すると cache が使われ、前回より早く実行されているのがわかります。
$ turbo run build
• Packages in scope: @nus3/example-button2, nus3-a, nus3-config, nus3-ui
• Running build in 4 packages
@nus3/example-button2:build: cache hit, replaying output a161062b16a0be35
...
nus3-ui:build: cache hit, replaying output b32d4fb848c8658f
...
nus3-a:build: cache hit, replaying output a80d8842624981a8
...
Tasks: 3 successful, 3 total
Cached: 3 cached, 3 total
Time: 221ms >>> FULL TURBO
✨ Done in 0.57s.
次のように何も指定しない場合、依存関係がないものとして認識され並列に実行されます
"lint": {},
次の deploy コマンドのdependsOn
では pipeline で定義したbuild
, test
, lint
(順不同)を実行し、終了した時点で workspace 内の各 package のdeploy
コマンドを実行します。
"deploy": {
"dependsOn": ["build", "test", "lint"]
},
今回定義したものの場合、Turborepo は次のようなことを考慮しながら各々の package のコマンドを実行します。
build
: モノレポ内の各 package の依存関係を考慮しつつ buildtest
:pipeline
で定義したbuild
が実行された後に実行lint
: 他のコマンドの順番を気にせず、並列で実行
deploy コマンドを実行した結果が次になります。
$ turbo run deploy
• Packages in scope: @nus3/example-button2, nus3-a, nus3-config, nus3-ui
• Running deploy in 4 packages
nus3-a:lint: $ next lint
@nus3/example-button2:build: $ vite build && tsc -p ./tsconfig.build.json
...
nus3-ui:build: $ echo 'build nus3-ui'
...
nus3-a:build: $ next build
...
nus3-a:test: $ echo 'test nus3-a'
...
nus3-a:deploy: $ echo 'deploy nus3-a'
実際に上記を考慮しつつ各 package のコマンドが実行された後に、最後に deploy コマンドが実行されているのがわかると思います。
また、今回はローカルキャッシュの話のみでしたが、Turborepo にはVercelや独自にキャッシュを配置することで、同じハッシュのキャッシュがあった場合にそのキャッシュを使ってくれるリモートキャッシュといった機能も Beta で用意されています。
今のプロジェクトが Yarn や npm の workspace を使っているのであれば、Turborepo を試しに入れてみてもいいかもしれません。
記事に関する報告などはこちらから