Turborepoのチュートリアル
はじめに
Turborepoは、高速でスケーラブルなモノレポ(monorepo)ビルドシステムです。自身の知識の棚卸しを目的に記事化しました。
本記事では、以下について解説します。
- モノレポとマルチレポの違い
- Turborepo とは何か、どのような課題を解決しているのか
- Turborepo を実際に利用するためのチュートリアル
Turborepo のチュートリアルは以下の公式チュートリアルをベースに、自身で足りないと思った要素を追加しています。
マルチレポとは
モノレポ(monorepo)とマルチレポ(multirepo)は、プロジェクトのコード管理戦略の異なる形態です。
モノレポでは、すべてのプロジェクトやパッケージが 1 つのリポジトリで管理されるのに対し、マルチレポでは各プロジェクトやパッケージが独自のリポジトリで管理されます。
以下の図がマルチレポの構成を表した例です。Repo1、Repo2、Repo3 という 3 つのリポジトリが存在し、依存関係が存在しています。
開発体験に基づいた、マルチレポの主なメリットです。
項目 | 説明 |
---|---|
スケーラビリティ | 各リポジトリが独立しているため、プロジェクトが大規模になってもリポジトリのサイズが適切に保たれ、クローンやビルドの速度が維持されます。 |
権限管理 | 各プロジェクトごとにアクセス制御を容易に設定できます。 |
プロジェクトの独立性 | 各プロジェクトが独立しているため、開発やデプロイのサイクルが他のプロジェクトに影響を与えにくくなります。 |
シンプルなワークフロー | プロジェクトごとにリポジトリが分割されているため、開発者は特定のプロジェクトに集中しやすくなります。 |
開発体験に基づいた、マルチレポの主なデメリットです。
項目 | 説明 |
---|---|
一貫性の欠如 | コーディングスタイルやツールの統一が難しくなることがあります。 |
依存関係の管理 | パッケージ間の依存関係の追跡や更新が複雑になることがあります。 |
コード共有 | 共通のコードやリソースをパッケージ間で共有するのが難しくなり、重複が増える可能性があります。 |
統合テスト | プロジェクト間の統合テストを実行するのが難しくなることがあります。 |
コードレビュー | チームメンバーが複数のリポジトリの変更を追跡することが難しくなり、コードレビューが効率的でなくなることがあります。 |
モノレポとは
モノレポ(monorepo)は、すべてのプロジェクトやパッケージが 1 つのリポジトリで管理されるコード管理戦略です。
マルチレポと異なり、Repo という 1 つのリポジトリしかありません。依存関係は同じです。
開発体験に基づいた、モノレポの主なメリットです。
項目 | 説明 |
---|---|
一貫性 | 全てのコードが同じリポジトリで管理されるため、コーディングスタイルやツールの統一が容易になります。 |
依存関係の管理 | パッケージ間の依存関係を簡単に追跡し、更新することができます。 |
コード共有 | 共通のコードやリソースをパッケージ間で簡単に共有でき、重複を減らすことができます。 |
統合テスト | 一元化されたリポジトリを使用することで、統合テストを容易に実行できます。 |
ワークフローの単純化 | プロジェクト全体での変更やリファクタリングを行いやすくなります。 |
コードレビュー | チームメンバーがリポジトリ全体の変更を追跡しやすくなり、コードレビューが効率的に行えます。 |
開発体験に基づいた、モノレポの主なデメリットです。
項目 | 説明 |
---|---|
スケーラビリティ | プロジェクトが大規模になると、リポジトリのサイズが大きくなり、クローンやビルドの速度が低下する可能性があります。 |
権限管理 | モノレポでは、プロジェクトごとのアクセス制御が難しくなることがあります。 |
学習コスト | モノレポの管理には、特定のツールやワークフローを習得する必要があります。 |
今回紹介する Turborepo は初期の学習コストは発生しますが、スケーラビリティを解決できます。具体的には、Turborepo では、リポジトリサイズが大きくなっても、ビルド時間が大きくなることはありません。
権限管理の課題は残るため、マルチレポとモノレポの選択は、プロジェクトの規模、チーム構成、開発プロセスなどの要素に基づいて判断する必要があります。
Turborepoとは
Turborepoは、高速でスケーラブルなモノレポ(monorepo)ビルドシステムです。あらためて、モノレポは、複数のプロジェクトやパッケージを 1 つのリポジトリで管理するコード管理戦略です。組織がコードの共有や再利用を効率的に行うことができます。しかし、モノレポは、ビルド時間の増加やデプロイの複雑さといった課題を抱えています。
Turborepo は、これらの課題へ対処するために設計されたビルドシステムで、以下のような機能を提供します。
項目 | 説明 |
---|---|
キャッシュとインクリメンタルビルド | Turborepoは、ビルドキャッシュを使用して、変更された部分だけを再ビルドします。これにより、ビルド時間が大幅に短縮され、開発の効率が向上します。 |
並列化 | Turborepoは、複数のタスクを並行して実行することで、リント、ビルド、テストの速度を向上させます。 |
依存関係の自動解決 | Turborepoは、プロジェクト間の依存関係を自動的に解決し、必要なタスクを適切な順序で実行します。 |
柔軟な設定 | Turborepoは、プロジェクト固有の設定や共通の設定を簡単に管理できるように設計されており、開発者が効率的に作業できる環境を提供します。 |
なお、TypeScript 及び JavaScript のモノレポ環境に特化したツールで、Next.js を提供している Vercel によって開発されてます。
Turborepo は、モノレポを使用している開発者や組織にとって、リント、ビルド、テストの速度を向上させる有益なツールです。開発の効率を向上させたいモノレポの利用者には、Turborepo の導入を検討する価値があります。
以下は、Turborepo 公式サイト、Turbo GitHub です。
Turborepoの追加説明
Turborepo が提供する機能の簡易説明をします。
タスクの並列実行
Turborepo が、複数のタスクを並行して実行するによる、実行時間の削減します。
例えば、3 つのリソースがあるとします。これらの lint
、build
、test
を実行する場合は、lint
、build
、test
の順に一連の流れで処理を行います。
Turborepo を利用すると、各リソースの依存関係が自動的に解決され、必要なタスクが適切な順序で実行されます。これにより、タスクを実行する時間が大幅圧縮されます。例として、web と docs の build
は ui に依存しているため、ui の build
が完了するまで実行されません。
ローカルキャッシュを利用したインクリメンタルビルド
Turborepo のローカルキャッシュを利用することで、変更された部分だけを再ビルドします。これにより、ビルド時間が大幅に短縮され、開発の効率が向上します。
具体的な利用シナリオシナリオを見ていきます。
- 貴方は開発者で、ソースコードを更新したとします。貴方は、ビルドが通るか、
turbo run build
をローカルで実行します。Turborepo は、ビルドのタスクの入力(つまりソースコードなどから)、ハッシュ値を算出します(例:f3adfa123a)
- 次に、ハッシュ値がローカルファイルシステム(例:/node_modules/.cache/turbo/)に存在するか確認します。
- Turborepo は、ハッシュが見つからない場合、ローカルでタスクを実行します。
- タスクが完了すると、Turborepo は実行結果をアーティファクトとしてハッシュ値に紐付いてローカルファイルシステムのキャッシュに保存します。
次に、ファイルに何も変更せずに、貴方は再度、ビルドのタスクを実行します。
- Turborepo は、ビルドのタスクの入力(つまりソースコードなどから)、ハッシュ値を算出します(例:f3adfa123a)
- 次に、ハッシュ値がローカルファイルシステム(例:/node_modules/.cache/turbo/)に存在するか確認します。
- 今回はキャッシュが存在したため、タスクを再度実行するのではなく、Turborepo は以前のアーティファクトを使うことで、時間とコンピュータリソースを節約します。
リモートキャッシュを利用したインクリメンタルビルド
Turborepo のリモートキャッシュを利用することで、チームメンバーがビルドした結果を共有し、ビルド時間を短縮します。
- 例えば、リモートキャッシュにキャッシュ化されていないタスクをまず実行します。
- 実行されたタスクの結果は、リモートキャッシュに保存されます。
- 他のチームメンバーが同一の内容をビルドする際には、リモートキャッシュからデータが取得され、ビルド時間が短縮されます。
なお、デプロイ先に Vercel を利用している場合、このリモートキャッシュは自動的に有効化されます。
事前準備
では、実際に Turborepo を利用していきます。まずは、事前準備として、以下のツールをインストールします。
turbo
Turborepo を利用するために、turbo
コマンドを利用できるようにします。turbo
をグローバルインストールする場合は、以下のコマンドを実行します。
$ pnpm install turbo --global
バージョンを確認します。
$ turbo --version
1.8.3
その他の詳細や、ローカルにインストールする場合は、Turborepo 公式のインストールガイドを参照ください。
新規プロジェクト作成
create-turbo
コマンドを利用し、新規に Turborepo プロジェクトを作成します。
$ pnpm dlx create-turbo@latest
>>> TURBOREPO
>>> Welcome to Turborepo! Let's get you set up with a new codebase.
? Where would you like to create your turborepo? ./my-turborepo
? Which package manager do you want to use? pnpm
Downloading files. This might take a moment.
>>> Created a new Turborepo with the following:
apps
- apps/docs
- apps/web
packages
- packages/eslint-config-custom
- packages/tsconfig
- packages/ui
Installing packages. This might take a couple of minutes.
>>> Success! Created a new Turborepo at "my-turborepo".
Inside that directory, you can run several commands:
pnpm run build
Build all apps and packages
pnpm run dev
Develop all apps and packages
pnpm run lint
Lint all apps and packages
Turborepo will cache locally by default. For an additional
speed boost, enable Remote Caching with Vercel by
entering the following command:
pnpm dlx turbo login
We suggest that you begin by typing:
cd my-turborepo
pnpm dlx turbo login
今回は、パッケージ管理に pnpm
を選択しました。pnpm
以外に、yarn
や npm
を利用できますが、pnpm
を公式は推奨しています。
If you're not sure, we recommend choosing pnpm. If you don't have it installed, cancel create-turbo (via ctrl-C) and take a look at the installation instructions(opens in a new tab).
プロジェクト名は my-turborepo
としています。作成したプロジェクトのディレクトリに移動します。
$ cd my-turborepo
プロジェクト構成を確認
プロジェクトの構成をツリー形式で表示します。
node_modules
, .git
, .next
は表示しないようにしています。
$ tree -I node_modules -I .git -I .next --dirsfirst -a
.
├── apps
│ ├── docs
│ │ ├── pages
│ │ │ └── index.tsx
│ │ ├── .eslintrc.js
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── next-env.d.ts
│ │ ├── next.config.js
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── web
│ ├── pages
│ │ └── index.tsx
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── README.md
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ └── tsconfig.json
├── packages
│ ├── eslint-config-custom
│ │ ├── index.js
│ │ └── package.json
│ ├── tsconfig
│ │ ├── base.json
│ │ ├── nextjs.json
│ │ ├── package.json
│ │ └── react-library.json
│ └── ui
│ ├── Button.tsx
│ ├── index.tsx
│ ├── package.json
│ └── tsconfig.json
├── .eslintrc.js
├── .gitignore
├── .npmrc
├── README.md
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json
全体構成
プロジェクトの構成は大きく3つに別れています。
要素 | 説明 |
---|---|
apps |
独立して動作するアプリケーションが配置 |
packages |
プロジェクトで共有で利用するプログラムや設定が配置 |
ルート | プロジェクト全体の管理に必要となる設定が配置 |
apps
apps
には、独立して動作するアプリケーションとして web
と docs
はに配置されています。それぞれがワークスペースに該当し、計 2 つのワークスペースが apps
に含まれます。ワークスペースとは何かは後ほど解説します。
ワークスペース名 | パス | 説明 |
---|---|---|
web |
apps/web |
TypeScriptのNext.jsアプリ |
docs |
apps/docs |
TypeScriptのNext.jsアプリ |
web
と docs
は同じ構成のため、web
に該当することは docs
についても同様のことが言えると理解してください。
.
└── apps
├── docs
│ ├── pages
│ │ └── index.tsx
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── README.md
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ └── tsconfig.json
└── web
├── pages
│ └── index.tsx
├── .eslintrc.js
├── .gitignore
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
└── tsconfig.json
packages
packages
には、他のワークスペースで利用するプログラムや設定として ui
、eslint-config-custom
、tsconfig
が配置されています。それぞれがワークスペースに該当し、計 3 つのワークスペースが packages
に含まれます。ワークスペースとは何かは後ほど解説します。
ワークスペース名 | パス | 説明 |
---|---|---|
ui |
packages/ui |
共有のReactコンポーネント |
eslint-config-custom |
packages/eslint-config-custom |
共有のESLint設定 |
tsconfig |
packages/tsconfig |
共有のTypeScriptの設定 |
.
└── packages
├── eslint-config-custom
│ ├── index.js
│ └── package.json
├── tsconfig
│ ├── base.json
│ ├── nextjs.json
│ ├── package.json
│ └── react-library.json
└── ui
├── Button.tsx
├── index.tsx
├── package.json
└── tsconfig.json
ルート
ルートにプロジェクト全体の管理に必要となる設定が配置されています。
.
├── .eslintrc.js
├── .gitignore
├── .npmrc
├── README.md
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json
ファイル名 | 説明 |
---|---|
.eslintrc.js |
ESLintの設定ファイル |
.gitignore |
Gitで管理しないファイルを指定するためのファイル |
.npmrc |
npmの設定ファイル |
README.md |
READMEファイル |
package.json |
プロジェクトの設定ファイル |
pnpm-lock.yaml |
pnpmでインストールしたパッケージの情報が記録されているファイル |
pnpm-workspace.yaml |
pnpmの設定ファイル |
turbo.json |
ESLintの設定ファイル |
プロジェクト構成の制約
今回作成した公式のサンプルでは、便宜上、アプリケーションは apps
配下に、パッケージは packages
配下に配置されています。しかし、これらの構成は Turborepo を利用する上で必須の構成ではありません。極論、以下のように、apps
の配下に ui
、eslint-config-custom
、tsconfig
が配置されていても設定が正しければ正しく動作します。ただし、公式は apps
と packages
の構成を推奨しています。
.
├── apps
│ ├── docs
│ ├── web
│ ├── eslint-config-custom
│ ├── tsconfig
│ └── ui
├── .eslintrc.js
├── package.json
├── pnpm-workspace.yaml
├── README.md
└── turbo.json
ワークスペース
ワークスペースについて解説します。ワークスペースとはアプリケーションやパッケージを管理するための単位です。
サンプルで作成される5つのワークスペース
Turborepo のモノレポは複数のワークスペースから構成されます。前記の通り、今回作成した公式サンプルには 5 つのワークスペース(docs
、web
、eslint-config-custom
、tsconfig
、ui
)が含まれています。
以下がそれぞれのワークスペースの概要です。
ワークスペース名 | パス | 説明 |
---|---|---|
web |
apps/web |
TypeScriptのNext.jsアプリ |
docs |
apps/docs |
TypeScriptのNext.jsアプリ |
ui |
packages/ui |
共有のReactコンポーネント |
eslint-config-custom |
packages/eslint-config-custom |
共有のESLint設定 |
tsconfig |
packages/tsconfig |
共有のTypeScriptの設定 |
.
├── apps
│ ├── docs
│ └── web
└── packages
├── eslint-config-custom
├── tsconfig
└── ui
package.json
各ワークスペースは package.json
を必ず含む必要があり、package.json
の name
フィールドの値によってワークスペースの名称が決まります。
{
"name": "web",
}
{
"name": "docs",
}
{
"name": "eslint-config-custom",
}
{
"name": "tsconfig",
}
{
"name": "ui",
}
ワークスペース間の依存
ワークスペースにはそれぞれ役割をもたせて、依存関係をもたせることができます。
-
docs
はui
、eslint-config-custom
、tsconfig
に依存しています。 -
web
はui
、eslint-config-custom
、tsconfig
に依存しています。 -
ui
はeslint-config-custom
、tsconfig
に依存しています。 -
tsconfig
は依存していません。 -
eslint-config-custom
は依存していません。
他ワークスペースを依存しているかどうかは、package.json
で確認できます。参考までに、web
の package.json
を確認すると、ui
、eslint-config-custom
、tsconfig
への依存が確認できます。
{
"name": "web",
"dependencies": {
"ui": "workspace:*"
},
"devDependencies": {
"eslint-config-custom": "workspace:*",
"tsconfig": "workspace:*",
}
}
ワークスペースに依存する場合は、workspace:*
という形で記述します。ワークスペースの詳細はpnpmのページに記載されています。
ローカルで動作確認
ローカルで実行しどのような結果になるか確認します。
$ pnpm i && pnpm build && pnpm dev
【参考】実行ログ
Scope: all 6 workspace projects
Lockfile is up to date, resolution step is skipped
Already up to date
Done in 876ms
> my-turborepo@ build /Users/hayato94087/Private/my-turborepo
> turbo run build
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running build in 5 packages
• Remote caching disabled
web:build: cache miss, executing a22776b494a1f7b2
docs:build: cache miss, executing 6836a2f4497ba1de
web:build:
web:build: > web@1.0.0 build /Users/hayato94087/Private/my-turborepo/apps/web
web:build: > next build
web:build:
docs:build:
docs:build: > docs@1.0.0 build /Users/hayato94087/Private/my-turborepo/apps/docs
docs:build: > next build
docs:build:
docs:build: info - Linting and checking validity of types...
web:build: info - Linting and checking validity of types...
docs:build: info - Creating an optimized production build...
web:build: info - Creating an optimized production build...
web:build: info - Compiled successfully
docs:build: info - Compiled successfully
web:build: info - Collecting page data...
docs:build: info - Collecting page data...
docs:build: info - Generating static pages (0/3)
web:build: info - Generating static pages (0/3)
docs:build: info - Generating static pages (3/3)
web:build: info - Generating static pages (3/3)
web:build: info - Finalizing page optimization...
docs:build: info - Finalizing page optimization...
web:build:
docs:build:
web:build: Route (pages) Size First Load JS
web:build: ┌ ○ / 301 B 74.2 kB
web:build: └ ○ /404 182 B 74.1 kB
web:build: + First Load JS shared by all 73.9 kB
web:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
web:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
web:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
web:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
web:build:
docs:build: Route (pages) Size First Load JS
docs:build: ┌ ○ / 302 B 74.2 kB
docs:build: └ ○ /404 182 B 74.1 kB
docs:build: + First Load JS shared by all 73.9 kB
docs:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
docs:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
docs:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
docs:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
docs:build:
web:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
web:build:
docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
docs:build:
Tasks: 2 successful, 2 total
Cached: 0 cached, 2 total
Time: 22.644s
> my-turborepo@ dev /Users/hayato94087/Private/my-turborepo
> turbo run dev
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running dev in 5 packages
• Remote caching disabled
web:dev: cache bypass, force executing 41ce06a2e6649cc6
docs:dev: cache bypass, force executing ef76b7b2ab004403
web:dev:
web:dev: > web@1.0.0 dev /Users/hayato94087/Private/my-turborepo/apps/web
web:dev: > next dev
web:dev:
docs:dev:
docs:dev: > docs@1.0.0 dev /Users/hayato94087/Private/my-turborepo/apps/docs
docs:dev: > next dev --port 3001
docs:dev:
docs:dev: ready - started server on 0.0.0.0:3001, url: http://localhost:3001
web:dev: ready - started server on 0.0.0.0:3000, url: http://localhost:3000
web:dev: event - compiled client and server successfully in 1271 ms (156 modules)
docs:dev: event - compiled client and server successfully in 1284 ms (156 modules)
web
が http://localhost:3000 が動作します。
docs
が http://localhost:3001 が動作します。
uiを探索
ui
は他のワークスペースに共有の React コンポーネントライブラリーを提供するためのワークスペースです。ここでは、ui
の中身を探索していきます。
ワークスペースの中身
packages/ui
のフォルダ構成は以下のとおりです。
.
└── packages
└── ui
├── Button.tsx
├── index.tsx
├── package.json
└── tsconfig.json
ファイル名 | 説明 |
---|---|
Button.tsx |
他ワークスペースに共有されるReactのコンポーネント |
index.tsx |
Reactのコンポーネントをexportしているファイル |
package.json |
設定ファイル |
tsconfig.json |
TypeScriptの設定ファイル |
package.json
以下が packagejson です。
{
"name": "ui",
"version": "0.0.0",
"main": "./index.tsx",
"types": "./index.tsx",
"license": "MIT",
"scripts": {
"lint": "eslint \"**/*.ts*\""
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"eslint": "^7.32.0",
"eslint-config-custom": "workspace:*",
"react": "^17.0.2",
"tsconfig": "workspace:*",
"typescript": "^4.5.2"
}
}
ワークスペースの名前
あらためて、ワークスペースの名前は package.json
の name
フィールドにて指定されています。
{
"name": "ui",
}
他のワークスペースへの依存
ui
は eslint-config-custom
、tsconfig
に依存しています。
{
"devDependencies": {
"eslint-config-custom": "workspace:*",
"tsconfig": "workspace:*",
}
}
eslint-config-custom
は lint
が実行可能です。
{
"scripts": {
"lint": "eslint \"**/*.ts*\""
},
}
tsconfig
は以下の設定ファイルで利用されています。
{
"extends": "tsconfig/react-library.json",
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}
エクスポート
他のワークスペースが、ui
ワークスペースをインポートする際に呼ばれるスクリプトファイルを指定している場所は package.json
の main
です。ui
の場合は、index.tsx
が呼び出されます。
{
"name": "ui",
"main": "./index.tsx",
"types": "./index.tsx",
}
index.tsx
で Button
がエクスポートされています。これにより、ui
に依存している web
で Button
をインポートできます。
import * as React from "react";
export * from "./Button";
import * as React from "react";
export const Button = () => {
return <button>Boop</button>;
};
他ワークスペースからのインポート
web
では、ui
の Button
を利用しています。
import { Button } from "ui";
export default function Web() {
return (
<div>
<h1>Web</h1>
<Button />
</div>
);
}
下記の画面の通り、Boop と表示されています。
docs
も web
同様に、ui
の Button
をインポートし利用しています。説明は省略します。
コンポーネントの更新
文字列を Boop
から Hello World
に変更してみます。
import * as React from "react";
export const Button = () => {
- return <button>Boop</button>;
+ return <button>Hello World</button>;
};
ローカル環境で確認します。既に実行中であれば、ホットリローディングにて、変更はリアルタイムに反映されます。
$ pnpm dev
無事変更されました。
新しいコンポーネントを追加
新しくコンポーネントを ui
に追加し、web
で使ってみます。
コンポーネントを定義するファイルを作成します。
import * as React from "react";
export const Button2 = () => {
return <button>Hello Universe</button>;
};
packages/ui/index.tsx
に追加し Button2
を export します。
import * as React from "react";
export * from "./Button";
+export * from "./Button2";
apps/web/pages/index.tsx
に Button2
を追加します。
-import { Button } from "ui";
+import { Button, Button2 } from "ui";
export default function Web() {
return (
<div>
<h1>Web</h1>
<Button />
+ <Button2 />
</div>
);
}
ローカル環境で確認します。既に実行中であれば、ホットリローディングにて、変更はリアルタイムに反映されます。
pnpm dev
無事追加されました。
tsconfigを探索
tsconfig
は他のワークスペースへの共有の TypeScript の設定が保管されています。ここでは、tsconfig
の中身を探索していきます。
ワークスペースの中身
packages/tsconfig
のフォルダ構成は以下のとおりです。重要なもののみ記載しています。
.
└── packages
└── tsconfig
├── base.json
├── nextjs.json
├── react-library.json
└── package.json
ファイル名 | 説明 |
---|---|
base.json | TypeScriptの設定ファイル |
nextjs.json | base.jsonを拡張しているnext.js用のTypeScriptの設定ファイル |
react-library.json | base.jsonを拡張しているReactライブラリー用のTypeScriptの設定ファイル |
package.json | tsconfigワークスペースの設定ファイル |
package.json
以下が packagejson です。
{
"name": "tsconfig",
"version": "0.0.0",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
}
}
ワークスペースの名前
あらためて、ワークスペースの名前は package.json
の name
フィールドにて指定されています。
{
"name": "tsconfig",
}
他のワークスペースへの依存
tsconfig
は他のワークスペースに依存していません。
エクスポート
package.json
で publicConfig
にて access
を public
に設定されています。これにより、tsconfig
を他のワークスペースから利用できるようになります。
{
"publishConfig": {
"access": "public"
}
}
以下のように files
を使用し特定ファイルを共有可能にできます。(以前のバージョンでは files
が使用されていました。)
{
"files": [
"base.json",
"nextjs.json",
"react-library.json"
]
}
インポート&拡張
tsconfig
をインポートするワークスペースにて、tsconfig.json
を作成します。作成した tsconfig.json
の extends
を利用し tsconfig
の TypeScript の設定をインポートします。設定は上書きも可能です。
ui
、docs
、web
では、tsconfig/react-library.json
の設定を読み込み、include
と exclude
を利用し設定をカスタマイズしています。
{
"extends": "tsconfig/react-library.json",
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}
{
"extends": "tsconfig/react-library.json",
"include": ["."],
"exclude": ["node_modules"]
}
{
"extends": "tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
eslint-config-customを探索
eslint-config-custom
は他のワークスペースに共有の ESTLint の設定を提供するためのワークスペースです。ここでは、eslint-config-custom
の中身を探索していきます。
ワークスペースの中身
packages/eslint-config-custom
のフォルダ構成は以下のとおりです。重要なもののみ記載しています。
.
└── packages
└── eslint-config-custom
├── index.js
└── package.json
ファイル名 | 説明 |
---|---|
index.js | ESLintの設定ファイル |
package.json | eslint-config-customワークスペースの設定ファイル |
ワークスペースの名前
あらためて、ワークスペースの名前は package.json
の name
フィールドにて指定されています。
{
"name": "eslint-config-custom",
}
ワークスペース名に、eslint-config-
というプレフィックスが付いています。これは、ESLintの設定ファイルの命名規則に従っているためです。
他のワークスペースへの依存
eslint-config-custom
は他のワークスペースに依存していません。
エクスポート
package.json
の main
にて、index.js
を指定しています。
{
"name": "eslint-config-custom",
"main": "index.js",
}
ESLintのルール
index.js
には ESLint のルールが記述されています。
module.exports = {
extends: ["next", "turbo", "prettier"],
rules: {
"@next/next/no-html-link-for-pages": "off",
},
parserOptions: {
babelOptions: {
presets: [require.resolve("next/babel")],
},
},
};
extends
にて、next
、turbo
、prettier
の設定を継承しています。詳細は補足参照。
【参考】eslint-config の補足
- eslint-config-next
- eslint-config-turbo
- eslint-config-prettier
rules
にて、@next/next/no-html-link-for-pages
のルールを無効化しています。詳細は補足参照。
【参考】no-html-link-for-pages の補足
- no-html-link-for-pages
parserOptions
にて、babelOptions
を指定しています。詳細は補足参照。
【参考】parserOptions の補足
babelOptions
は、ESLint が Babel という JavaScript のコンパイラを使って、コードを解析する際に適用されるオプションを指定します。Babel は、最新の JavaScript 機能を古いブラウザでも動作するように変換するために使用されます。presets オプションに require.resolve("next/babel")が指定されています。これは、Next.js が提供する Babel プリセットを適用することを意味します。プリセットは、Babel がどのようにコードを変換するかを決定する一連のプラグインです。Next.js の Babel プリセットは、Next.js アプリケーションに最適化された設定を含んでいます。まとめると、ESLint が Babel を使用してコードを解析する際に、Next.js の Babel プリセットを適用するように指定しています。これにより、ESLint が Next.js アプリケーションで適切な解析を行えるようになります。
インポート方法その1
2 つの方法で ESLint の設定をインポートできます。1 つ目の方法は eslint-config-custom
をインポートするワークスペースにて、.eslintrc.js
を作成します。extends
にて eslint-config-custom
の設定をインポート/拡張します。
web
と docs
は、eslint-config-custom
に依存しています。
{
"name": "web",
"devDependencies": {
"eslint-config-custom": "workspace:*",
}
}
{
"name": "docs",
"devDependencies": {
"eslint-config-custom": "workspace:*",
}
}
web
と docs
はワークスペースに、.eslintrc.js
を配置しています。extends
にて custom
を指定することで、eslint-config-custom
の設定をインポートしています。ESLint ルールを extends
する際は、eslint-config-
というプレフィックスを省略します。
module.exports = {
root: true,
extends: ["custom"],
};
module.exports = {
root: true,
extends: ["custom"],
};
さらに、root: true
を設定することで、実行時のカレントディレクトリを起点にして、上位のディレクトリの設定ファイル (.eslintrc.*
) を探索しないように設定を拡張しています。
【参考】root: true の補足
ESLint の設定ファイル(.eslintrc.js
)が各ワークスペースではなくルートに配置されています。ESLint は、実行時のカレントディレクトリを起点にして、上位のディレクトリの設定ファイル (.eslintrc.*
) を探索していきます。ルートに配置することで、各ワークスペースの ESLint の設定ファイルを共通化できます。例えば、packages/ui
には .eslintrc.js
が存在しないため、次に packages
を確認します。ここにも .eslintrc.js
が存在しないため、次にルートを確認します。ルートに .eslintrc.js
が存在するため、ルートの .eslintrc.js
を利用します。他のワークスペースも同様な挙動をします。
インポート方法その2
2 つ目の方法は eslint-config-custom
をインポートするワークスペースにて、.eslintrc.js
を作成しない方法です。上の階層の .eslintrc.js
を参照し eslint-config-custom
の設定をインポートします。
ui
は、eslint-config-custom
に依存しています。
{
"name": "ui",
"devDependencies": {
"eslint-config-custom": "workspace:*",
}
}
ui
はワークスペースに、.eslintrc.js
を配置していません。ESLint は挙動として、実行時のカレントディレクトリを起点にして、上位のディレクトリの設定ファイル (.eslintrc.*
) を探索していきます。この挙動を利用し、ルート(./
)に配置されている .eslintrc.js
を利用しています。
module.exports = {
root: true,
// This tells ESLint to load the config from the package `eslint-config-custom`
extends: ["custom"],
settings: {
next: {
rootDir: ["apps/*/"],
},
},
};
settings についての解説です。詳細はNext.jsの公式サイトに記述があります。
ふりかえり
これまでのふりかえりです。
- モノレポとは複数のパッケージを 1 つのリポジトリで管理するです
- ワークスペースとはモノレポを構成する単位であり、モノレポは複数のワークスペースから構成されます。
- ワークスペース間の依存関係を
ui
、tsconfig
、eslint-config-custom
のワークスペースの探索を通し理解しました。
次は、turbo
コマンドの詳細を理解していきます。
turbo.jsonを探索
turbo
がどのようにタスクを実行するか学んでいきます。
turbo
は、turbo.json
というファイルを読み込み、タスクを実行します。turbo.json
の内容は以下です。
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
$schema
,globalDependencies
,pipeline
について簡単に説明します。
$schema
$schema
は、turbo.json
のバリデーションを行うためのものです。https://turbo.build/schema.json
にアクセスすると、JSON Schema が確認できます。
{
"$schema": "https://turbo.build/schema.json",
}
globalDependencies
globalDependencies
は、ワークスペース間で共有するファイルを指定します。.env.*local
というファイルを共有していますが、今回はファイルを作成していません。
{
"globalDependencies": ["**/.env.*local"],
}
pipeline
実行するタスクは、turbo.json
の pipeline
にて定義します。今回は pipeline
にて、build
, lint
, dev
のタスクが定義されています。
{
"pipeline": {
"build": {
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
以下の通り、タスクを指定し、turbo
で実行することで、タスクを実行できます。以下は build
のタスクを実行しています。
$ turbo run build
ちなみに、run
を省略できます。
$ turbo build
また、タスクを複数指定できます。以下は build
と lint
のタスクを実行しています。
$ turbo run build lint
run
を省略できます。
$ turbo build lint
続いて、実際にタスクを実行しながら turbo
を理解していきます。
プロジェクトの再作成
Turborepo でタスクを実行するとローカルのファイルシステムにキャッシュが自動的に保存されます。キャッシュは、node_modules/.cache/turbo
に保存されます。node_modules/.cache/turbo
を削除することで、ローカルキャッシュを削除できます。キャッシュについて理解するために、念の為、プロジェクトを my-turborepo-2
で再作成します。
$ pnpm dlx create-turbo@latest
【参考】実行ログ
>>> TURBOREPO
>>> Welcome to Turborepo! Let's get you set up with a new codebase.
? Where would you like to create your turborepo? my-turborepo-2/
? Which package manager do you want to use? pnpm
Downloading files. This might take a moment.
>>> Created a new Turborepo with the following:
apps
- apps/docs
- apps/web
packages
- packages/eslint-config-custom
- packages/tsconfig
- packages/ui
Installing packages. This might take a couple of minutes.
>>> Success! Created a new Turborepo at "my-turborepo-2".
Inside that directory, you can run several commands:
pnpm run build
Build all apps and packages
pnpm run dev
Develop all apps and packages
pnpm run lint
Lint all apps and packages
Turborepo will cache locally by default. For an additional
speed boost, enable Remote Caching with Vercel by
entering the following command:
pnpm dlx turbo login
We suggest that you begin by typing:
cd my-turborepo-2
pnpm dlx turbo login
プロジェクト全体をLint
プロジェクト全体に対して Lint を実行します。
$ turbo run lint
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running lint in 5 packages
• Remote caching disabled
ui:lint: cache miss, executing 092988374bf8f828
docs:lint: cache miss, executing 188608743c4ff1e4
web:lint: cache miss, executing 997cb3ca5a2129cf
ui:lint:
ui:lint: > ui@0.0.0 lint /Users/hayato94087/Private/my-turborepo-2/packages/ui
ui:lint: > eslint "**/*.ts*"
ui:lint:
docs:lint:
docs:lint: > docs@1.0.0 lint /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:lint: > next lint
docs:lint:
web:lint:
web:lint: > web@1.0.0 lint /Users/hayato94087/Private/my-turborepo-2/apps/web
web:lint: > next lint
web:lint:
web:lint: ✔ No ESLint warnings or errors
docs:lint: ✔ No ESLint warnings or errors
Tasks: 3 successful, 3 total
Cached: 0 cached, 3 total
Time: 3.88s
次のセクションで Cache について解説するので、実行ログの最後のアウトプットについて覚えておいてください。
Tasks: 3 successful, 3 total
Cached: 0 cached, 3 total
Time: 3.88s
turbo run lint
の裏では、5 つのワークスペースに対して lint
のタスクが実行されます。web
、docs
、ui
は lint
が定義されているので lint
が実行されます。
{
"name": "web",
"scripts": {
"lint": "next lint"
},
}
{
"name": "docs",
"scripts": {
"lint": "next lint"
},
}
{
"name": "ui",
"scripts": {
"lint": "eslint \"**/*.ts*\""
},
}
eslint-config-custom
、tsconfig
、は package.json
に lint
が定義されていないため、lint
は実行されません。
ローカルキャッシュについて
Turborepo は実行結果をキャッシュしておくことで、変更がない場合は実行をスキップします。これにより、実行時間を短縮できます。
あらためて、もう一度、Lint を実行します。
$ turbo run lint
実行ログです。
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running lint in 5 packages
• Remote caching disabled
ui:lint: Skipping cache check for ui#lint, outputs have not changed since previous run.
ui:lint: cache hit, replaying output 092988374bf8f828
docs:lint: Skipping cache check for docs#lint, outputs have not changed since previous run.
docs:lint: cache hit, replaying output 188608743c4ff1e4
ui:lint:
ui:lint: > ui@0.0.0 lint /Users/hayato94087/Private/my-turborepo-2/packages/ui
ui:lint: > eslint "**/*.ts*"
ui:lint:
docs:lint:
docs:lint: > docs@1.0.0 lint /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:lint: > next lint
docs:lint:
docs:lint: ✔ No ESLint warnings or errors
web:lint: cache hit, replaying output 997cb3ca5a2129cf
web:lint:
web:lint: > web@1.0.0 lint /Users/hayato94087/Private/my-turborepo-2/apps/web
web:lint: > next lint
web:lint:
web:lint: ✔ No ESLint warnings or errors
Tasks: 3 successful, 3 total
Cached: 3 cached, 3 total
Time: 414ms >>> FULL TURBO
上記のログから、キャッシュが有効になっているログを以下に抜粋しています。docs
,web
,ui
とキャッシュが有効になっていることがわかります。
ui:lint: cache hit, replaying output 092988374bf8f828
docs:lint: cache hit, replaying output 188608743c4ff1e4
web:lint: cache hit, replaying output 997cb3ca5a2129cf
また、初回に、turbo lint
を実行した際に、最後の結果は以下でした。
Tasks: 3 successful, 3 total
Cached: 0 cached, 3 total
Time: 3.88s
2 回目は、以下です。
Tasks: 3 successful, 3 total
Cached: 3 cached, 3 total
Time: 414ms >>> FULL TURBO
1 回目は、0 cached
とあるように、キャッシュが 1 件も効きませんでした。
2 回目は、3 cached
とあり、cache hit
のメッセージからも、docs
,web
,ui
の 3 件のキャッシュが効きました。実行時間も 3.88s
から 414ms
に短縮されました。このようにキャッシュにより、変更がないタスクの処理については短縮化されます。
試しに、web
のページを更新し、Lint を実行します。
import { Button } from "ui";
export default function Web() {
return (
<div>
- <h1>Web</h1>
+ <h1>Web2</h1>
<Button />
</div>
);
}
$ turbo run lint
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running lint in 5 packages
• Remote caching disabled
ui:lint: Skipping cache check for ui#lint, outputs have not changed since previous run.
ui:lint: cache hit, replaying output 092988374bf8f828
docs:lint: Skipping cache check for docs#lint, outputs have not changed since previous run.
docs:lint: cache hit, replaying output 188608743c4ff1e4
web:lint: cache miss, executing fa9cd72ef8223180
docs:lint:
docs:lint: > docs@1.0.0 lint /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:lint: > next lint
ui:lint:
ui:lint: > ui@0.0.0 lint /Users/hayato94087/Private/my-turborepo-2/packages/ui
ui:lint: > eslint "**/*.ts*"
ui:lint:
docs:lint:
docs:lint: ✔ No ESLint warnings or errors
web:lint:
web:lint: > web@1.0.0 lint /Users/hayato94087/Private/my-turborepo-2/apps/web
web:lint: > next lint
web:lint:
web:lint: ✔ No ESLint warnings or errors
Tasks: 3 successful, 3 total
Cached: 2 cached, 3 total
Time: 3.37s
期待したとおり、docs
,ui
の 2 件にはキャッシュが効いていますが、変更しているため cache miss
と出ているように web
にはキャッシュが効きませんでした。
ui:lint: cache hit, replaying output 092988374bf8f828
docs:lint: cache hit, replaying output 188608743c4ff1e4
web:lint: cache miss, executing fa9cd72ef8223180
Tasks: 3 successful, 3 total
Cached: 2 cached, 3 total
Time: 3.37s
存在しないタスクの実行
ちなみに tubro.json
に存在しないタスクを実行した場合どうなるかも見ておきます。試しに、存在しないタスク hello
を実行します。
$ turbo run hello
以下の通りタスクが見つからないとエラーが出ました。
ERROR run failed: error preparing engine: Could not find the following tasks in project: hello
Turbo error: error preparing engine: Could not find the following tasks in project: hello
turbo.json
で定義されていないタスクが実行できません。
プロジェクト全体をビルド
続いて、プロジェクト全体をビルドするために、turbo
で build
のタスクを実行します。
$ turbo run build
以下が実行ログです。build
は 1 度も実行していないため、cache miss
と出ているように、キャッシュにはヒットしていません。
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running build in 5 packages
• Remote caching disabled
web:build: cache miss, executing f60525cfaa657509
docs:build: cache miss, executing 6836a2f4497ba1de
web:build:
web:build: > web@1.0.0 build /Users/hayato94087/Private/my-turborepo-2/apps/web
web:build: > next build
web:build:
docs:build:
docs:build: > docs@1.0.0 build /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:build: > next build
docs:build:
docs:build: info - Linting and checking validity of types...
web:build: info - Linting and checking validity of types...
web:build: info - Creating an optimized production build...
docs:build: info - Creating an optimized production build...
web:build: info - Compiled successfully
docs:build: info - Compiled successfully
web:build: info - Collecting page data...
docs:build: info - Collecting page data...
web:build: info - Generating static pages (0/3)
docs:build: info - Generating static pages (0/3)
web:build: info - Generating static pages (3/3)
web:build: info - Finalizing page optimization...
web:build:
web:build: Route (pages) Size First Load JS
web:build: ┌ ○ / 302 B 74.2 kB
web:build: └ ○ /404 182 B 74.1 kB
web:build: + First Load JS shared by all 73.9 kB
web:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
web:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
web:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
web:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
web:build:
web:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
web:build:
docs:build: info - Generating static pages (3/3)
docs:build: info - Finalizing page optimization...
docs:build:
docs:build: Route (pages) Size First Load JS
docs:build: ┌ ○ / 302 B 74.2 kB
docs:build: └ ○ /404 182 B 74.1 kB
docs:build: + First Load JS shared by all 73.9 kB
docs:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
docs:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
docs:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
docs:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
docs:build:
docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
docs:build:
Tasks: 2 successful, 2 total
Cached: 0 cached, 2 total
Time: 21.327s
turbo run build
の裏では、5 つのワークスペースに対して build
のタスクが実行されます。web
と docs
は build
が定義されているので build
が実行されます。
{
"name": "web",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
}
{
"name": "docs",
"scripts": {
"dev": "next dev --port 3001",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
}
eslint-config-custom
、tsconfig
、ui
は build
が定義されていないため、build
は実行されません。
-
eslint-config-custom
、tsconfig
はpackage.json
にscripts
がありません。 -
ui
にはpackage.json
にscripts
はありますが、build
は定義されています。
もう一度、build
してみます。
$ turbo run build
以下が実行ログです。
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running build in 5 packages
• Remote caching disabled
web:build: Skipping cache check for web#build, outputs have not changed since previous run.
web:build: cache hit, replaying output f60525cfaa657509
docs:build: Skipping cache check for docs#build, outputs have not changed since previous run.
docs:build: cache hit, replaying output 6836a2f4497ba1de
web:build:
docs:build:
docs:build: > docs@1.0.0 build /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:build: > next build
docs:build:
docs:build: info - Linting and checking validity of types...
web:build: > web@1.0.0 build /Users/hayato94087/Private/my-turborepo-2/apps/web
web:build: > next build
web:build:
web:build: info - Linting and checking validity of types...
web:build: info - Creating an optimized production build...
web:build: info - Compiled successfully
web:build: info - Collecting page data...
web:build: info - Generating static pages (0/3)
web:build: info - Generating static pages (3/3)
web:build: info - Finalizing page optimization...
web:build:
web:build: Route (pages) Size First Load JS
web:build: ┌ ○ / 302 B 74.2 kB
web:build: └ ○ /404 182 B 74.1 kB
web:build: + First Load JS shared by all 73.9 kB
web:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
docs:build: info - Creating an optimized production build...
docs:build: info - Compiled successfully
docs:build: info - Collecting page data...
docs:build: info - Generating static pages (0/3)
docs:build: info - Generating static pages (3/3)
docs:build: info - Finalizing page optimization...
docs:build:
docs:build: Route (pages) Size First Load JS
docs:build: ┌ ○ / 302 B 74.2 kB
docs:build: └ ○ /404 182 B 74.1 kB
docs:build: + First Load JS shared by all 73.9 kB
docs:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
docs:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
docs:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
docs:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
docs:build:
docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
docs:build:
web:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
web:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
web:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
web:build:
web:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
web:build:
Tasks: 2 successful, 2 total
Cached: 2 cached, 2 total
Time: 422ms >>> FULL TURBO
build
は 1 度実行しているため、cache hit
と出ているように、キャッシュにヒットしています。以下がログの抜粋です。
web:build: cache hit, replaying output f60525cfaa657509
docs:build: cache hit, replaying output 6836a2f4497ba1de
Tasks: 2 successful, 2 total
Cached: 2 cached, 2 total
Time: 422ms >>> FULL TURBO
outputsについて
turbo.json
を確認すると、outputs
が build
には記述されています。web
と docs
は自身のワークスペースをビルとすると、それぞれのワークスペース配下のフォルダ(./.next
)に実行結果が保存されます。outputs
で、それらフォルダを指定することで、タスク実行後に、指定されたディレクトリの中身を Turborepo に記憶させることができます。記憶させることで、指定のディレクトリを削除してもプロジェクトに変更なければキャッシュから復元できます。
{
"pipeline": {
"build": {
"outputs": [".next/**", "!.next/cache/**"]
},
}
}
web
の ./.next
のフォルダを削除して、もう一度ビルドしてみます。
$ rm -rf apps/web/.next && turbo build
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running build in 5 packages
• Remote caching disabled
docs:build: Skipping cache check for docs#build, outputs have not changed since previous run.
docs:build: cache hit, replaying output 6836a2f4497ba1de
docs:build:
docs:build: > docs@1.0.0 build /Users/hayato94087/Private/my-turborepo/apps/docs
docs:build: > next build
docs:build:
docs:build: info - Linting and checking validity of types...
docs:build: info - Creating an optimized production build...
docs:build: info - Compiled successfully
docs:build: info - Collecting page data...
docs:build: info - Generating static pages (0/3)
docs:build: info - Generating static pages (3/3)
docs:build: info - Finalizing page optimization...
docs:build:
docs:build: Route (pages) Size First Load JS
docs:build: ┌ ○ / 302 B 74.2 kB
docs:build: └ ○ /404 182 B 74.1 kB
docs:build: + First Load JS shared by all 73.9 kB
docs:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
docs:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
docs:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
docs:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
docs:build:
docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
docs:build:
web:build: cache hit, replaying output 14f1c96235930a1d
web:build:
web:build: > web@1.0.0 build /Users/hayato94087/Private/my-turborepo/apps/web
web:build: > next build
web:build:
web:build: info - Linting and checking validity of types...
web:build: info - Creating an optimized production build...
web:build: info - Compiled successfully
web:build: info - Collecting page data...
web:build: info - Generating static pages (0/3)
web:build: info - Generating static pages (3/3)
web:build: info - Finalizing page optimization...
web:build:
web:build: Route (pages) Size First Load JS
web:build: ┌ ○ / 302 B 74.2 kB
web:build: └ ○ /404 182 B 74.1 kB
web:build: + First Load JS shared by all 73.9 kB
web:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
web:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
web:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
web:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
web:build:
web:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
web:build:
Tasks: 2 successful, 2 total
Cached: 2 cached, 2 total
Time: 651ms >>> FULL TURBO
一瞬でビルドが終わり、削除したディレクトリも復元されました。以下はログの抜粋です。
docs:build: cache hit, replaying output 6836a2f4497ba1de
web:build: cache hit, replaying output 14f1c96235930a1d
Tasks: 2 successful, 2 total
Cached: 2 cached, 2 total
Time: 651ms >>> FULL TURBO
理解が正しければ、FULL TURBO
と出ている場合、キャッシュのミスが無いことを指します。
プロジェクト全体を開発環境で起動
今度は turbo run dev
を実行します。lint
や dev
同様に、turbo dev
の裏では、5 つのワークスペースに対して dev
のタスクが実行されます。web
と docs
は dev
が定義されているので dev
が実行されます。eslint-config-custom
、tsconfig
、ui
は dev
が定義されていないため、dev
は実行されません。
以下が dev
で実際に実行されるコマンドです。docs
は --port 3001
で実行するポート番号を指定しています。
{
"name": "docs",
"scripts": {
"dev": "next dev --port 3001",
},
}
{
"name": "web",
"scripts": {
"dev": "next dev",
},
}
$ turbo run dev
以下が実行ログです。
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running dev in 5 packages
• Remote caching disabled
docs:dev: cache bypass, force executing ef76b7b2ab004403
web:dev: cache bypass, force executing 29abc4a5fc01acc6
web:dev:
web:dev: > web@1.0.0 dev /Users/hayato94087/Private/my-turborepo-2/apps/web
web:dev: > next dev
web:dev:
docs:dev:
docs:dev: > docs@1.0.0 dev /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:dev: > next dev --port 3001
docs:dev:
web:dev: ready - started server on 0.0.0.0:3000, url: http://localhost:3000
docs:dev: ready - started server on 0.0.0.0:3001, url: http://localhost:3001
docs:dev: event - compiled client and server successfully in 1550 ms (156 modules)
web:dev: event - compiled client and server successfully in 1547 ms (156 modules)
実行ログから、web
は http://localhost:3000
で、docs
は http://localhost:3001
で実行されている事がわかります。また、実行ログで、cache bypass, force executing.
と出ています。
docs:dev: cache bypass, force executing ef76b7b2ab004403
web:dev: cache bypass, force executing 29abc4a5fc01acc6
web:dev: ready - started server on 0.0.0.0:3000, url: http://localhost:3000
docs:dev: ready - started server on 0.0.0.0:3001, url: http://localhost:3001
実行ログから、キャッシュヒットが発生していないことがわかります。
turbo.json
を確認します。
{
"dev": {
"cache": false,
"persistent": true
}
}
}
cache
が false
になっています。つまり何もキャッシュしないように設定されています。これは、開発環境を実行しているだけで、何もキャッシュすべきコンテンツを生成しないため、キャッシュを無効にしています。詳細は以下を参照ください。
また、"persistent": true
は長期実行されるタスクが、他のタスクによって中断されないようにするための設定です。dev
は長期実行されるタスクなので、"persistent": true
が設定されています。"persistent": true
については、公式サイトの説明がわかりにくいです。
特定のワークスペースに対してのみタスクを実行
デフォルトでは、turbo run dev
とすると、全てのワークスペースの dev
が実行されます。しかし、--filter={ワークスペース名}
でワークスペースを絞って実行できます。以下では、web
のみ開発環境を実行しています。
turbo dev --filter=web
• Packages in scope: web
• Running dev in 1 packages
• Remote caching disabled
web:dev: cache bypass, force executing 29abc4a5fc01acc6
web:dev:
web:dev: > web@1.0.0 dev /Users/hayato94087/Private/my-turborepo-2/apps/web
web:dev: > next dev
web:dev:
web:dev: ready - started server on 0.0.0.0:3000, url: http://localhost:3000
web:dev: event - compiled client and server successfully in 505 ms (156 modules)
実行ログから、web
のみが実行されていることがわかります。
--filter
については、以下を参照ください。
pnpmを利用しturboコマンドを実行
ルートからプロジェクト管理する場合、turbo
を直接使うのではなく、pnpm
を使って turbo
を実行します。以下が、package.json
の script
です。
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
}
script
の中で turbo
が指定されていることがわかります。lint
を実行する場合は、以下のように実行します。
$ pnpm lint
> my-turborepo-2@ lint /Users/hayato94087/Private/my-turborepo-2
> turbo run lint
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running lint in 5 packages
• Remote caching disabled
ui:lint: Skipping cache check for ui#lint, outputs have not changed since previous run.
ui:lint: cache hit, replaying output 092988374bf8f828
docs:lint: Skipping cache check for docs#lint, outputs have not changed since previous run.
docs:lint: cache hit, replaying output 188608743c4ff1e4
docs:lint:
docs:lint: > docs@1.0.0 lint /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:lint: > next lint
docs:lint:
ui:lint:
ui:lint: > ui@0.0.0 lint /Users/hayato94087/Private/my-turborepo-2/packages/ui
ui:lint: > eslint "**/*.ts*"
ui:lint:
docs:lint: ✔ No ESLint warnings or errors
web:lint: cache hit, replaying output fa9cd72ef8223180
web:lint:
web:lint: > web@1.0.0 lint /Users/hayato94087/Private/my-turborepo-2/apps/web
web:lint: > next lint
web:lint:
web:lint: ✔ No ESLint warnings or errors
Tasks: 3 successful, 3 total
Cached: 3 cached, 3 total
Time: 461ms >>> FULL TURBO
build
を実行する場合は、以下のように実行します。
$ pnpm build
> my-turborepo-2@ build /Users/hayato94087/Private/my-turborepo-2
> turbo run build
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running build in 5 packages
• Remote caching disabled
docs:build: cache hit, replaying output 6836a2f4497ba1de
web:build: cache hit, replaying output f60525cfaa657509
docs:build:
web:build:
web:build: > web@1.0.0 build /Users/hayato94087/Private/my-turborepo-2/apps/web
web:build: > next build
web:build:
web:build: info - Linting and checking validity of types...
docs:build: > docs@1.0.0 build /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:build: > next build
docs:build:
docs:build: info - Linting and checking validity of types...
docs:build: info - Creating an optimized production build...
web:build: info - Creating an optimized production build...
web:build: info - Compiled successfully
web:build: info - Collecting page data...
web:build: info - Generating static pages (0/3)
web:build: info - Generating static pages (3/3)
web:build: info - Finalizing page optimization...
web:build:
web:build: Route (pages) Size First Load JS
web:build: ┌ ○ / 302 B 74.2 kB
web:build: └ ○ /404 182 B 74.1 kB
web:build: + First Load JS shared by all 73.9 kB
web:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
web:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
web:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
web:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
web:build:
web:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
web:build:
docs:build: info - Compiled successfully
docs:build: info - Collecting page data...
docs:build: info - Generating static pages (0/3)
docs:build: info - Generating static pages (3/3)
docs:build: info - Finalizing page optimization...
docs:build:
docs:build: Route (pages) Size First Load JS
docs:build: ┌ ○ / 302 B 74.2 kB
docs:build: └ ○ /404 182 B 74.1 kB
docs:build: + First Load JS shared by all 73.9 kB
docs:build: ├ chunks/framework-ffffd4e8198d9762.js 45.2 kB
docs:build: ├ chunks/main-c781174d1546c2ca.js 27.8 kB
docs:build: ├ chunks/pages/_app-7b4ea0a6077fc727.js 195 B
docs:build: └ chunks/webpack-4e7214a60fad8e88.js 712 B
docs:build:
docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props)
docs:build:
Tasks: 2 successful, 2 total
Cached: 2 cached, 2 total
Time: 446ms >>> FULL TURBO
dev
を実行する場合は、以下のように実行します。
$ pnpm dev
> my-turborepo-2@ dev /Users/hayato94087/Private/my-turborepo-2
> turbo run dev
• Packages in scope: docs, eslint-config-custom, tsconfig, ui, web
• Running dev in 5 packages
• Remote caching disabled
docs:dev: cache bypass, force executing ef76b7b2ab004403
web:dev: cache bypass, force executing 29abc4a5fc01acc6
web:dev:
web:dev: > web@1.0.0 dev /Users/hayato94087/Private/my-turborepo-2/apps/web
web:dev: > next dev
web:dev:
docs:dev:
docs:dev: > docs@1.0.0 dev /Users/hayato94087/Private/my-turborepo-2/apps/docs
docs:dev: > next dev --port 3001
docs:dev:
docs:dev: ready - started server on 0.0.0.0:3001, url: http://localhost:3001
web:dev: ready - started server on 0.0.0.0:3000, url: http://localhost:3000
web:dev: event - compiled client and server successfully in 564 ms (156 modules)
docs:dev: event - compiled client and server successfully in 581 ms (156 modules)
--filter
を利用する場合は以下のように実行します。
$ pnpm dev --filter=web
> my-turborepo-2@ dev /Users/hayato94087/Private/my-turborepo-2
> turbo run dev "--filter=web"
• Packages in scope: web
• Running dev in 1 packages
• Remote caching disabled
web:dev: cache bypass, force executing 29abc4a5fc01acc6
web:dev:
web:dev: > web@1.0.0 dev /Users/hayato94087/Private/my-turborepo-2/apps/web
web:dev: > next dev
web:dev:
web:dev: ready - started server on 0.0.0.0:3000, url: http://localhost:3000
web:dev: event - compiled client and server successfully in 583 ms (156 modules)
パッケージをインストールする場合
特定のワークスペースにパッケージをインストールしたい場合があります。以下のように --filter
を利用することで、フォルダ移動することなくルート(./
)からインストールできます。
以下では react-icons
パッケージをインストールします。
pnpm i react-icons --filter=web
{
"name": "web",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-icons": "^4.8.0",
"ui": "workspace:*"
},
"devDependencies": {
"@types/node": "^17.0.12",
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"eslint-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"typescript": "^4.5.3"
}
}
react-icons
を追加します。
import { Button } from "ui";
+import { FaBeer } from "react-icons/fa";
export default function Web() {
return (
<div>
<h1>Web2</h1>
<Button />
+ <div>
+ <FaBeer />
+ </div>
</div>
);
}
実行します。
$ pnpm dev --filter=web
アイコンが追加されました。
ローカルキャッシュの削除
ローカルキャッシュは ./node_modules/.cache/turbo/
に保存されると記載されています。
が、こちらの内容を削除しても、残ることがあるため、./node_modules/.cache/turbo/
以外の場所にも保存されていると思われます。
リモートキャッシュについて
最後に、リモートキャッシュについて解説します。
Turborepo のリモートキャッシュ機能は、ビルドやテストの結果をリモートサーバーに保存して、チーム全体で共有できる機能です。これにより、ビルド時間の短縮や効率的なリソース利用が実現できます。リモートキャッシュは、特に大規模なプロジェクトや分散したチームでの開発において有用です。
まとめ
本記事では、以下について解説しました。
- モノレポとマルチレポの違い
- Turborepo の概要
- Turborepo のチュートリアル
知識の棚卸しのために書き始めたら、1 ヶ月ぐらいまとめるの時間かかりました。自身のプロジェクトでは少数のメンバーで開発しており、かつ、技術スタックは Next.js TypeScript を採用しています。Turborepo を採用することで、開発効率が向上できていると考えています。
Discussion
すごく参考になりました!
良かったです!時間たったのでどこかのタイミングで更新します m(_ _)m