Turbopackは何を「速く」しているのか
今日、package.jsonのdevスクリプトから--webpackを外し、開発サーバーをTurbopackへ正式に切り替えた(この経緯は別の記事にまとめた)。
ただ、切り替え作業をしながら気づいたことがある。自分はこれまで「Turbopackに切り替えた」「Turbopackの方が速い」と書いてきた。しかし、Turbopackが具体的に何をしていて、なぜ速いのかを、実はきちんと説明できていなかった。
推測で埋めるのは避けたい。そこでNext.js公式ドキュメント(Turbopackのリファレンスページ)を確認し、そこに書かれていることだけを根拠に整理し直すことにした。
Turbopackとは何か
公式ドキュメントの定義はこうだ。
Turbopackは、JavaScriptとTypeScriptに最適化されたインクリメンタルバンドラーで、Rustで書かれ、Next.jsに組み込まれています。
言葉を一つずつ分解する。
- バンドラーとは、複数に分かれたソースコードのファイルを、ブラウザが読み込める形にまとめる仕組みのことだ。webpackやRollupと同じ役割を担うものが、Next.jsに標準搭載されたと考えればいい。
- インクリメンタルとは「変更があった部分だけを差分で処理する」という意味の言葉だ。
- Rustは、Turbopackの実装に使われているプログラミング言語だ。
Next.js 16以降では、Turbopackがデフォルトのバンドラーになっている。実際、このプロジェクトのpackage.jsonも、dev・buildともに--webpackのような追加フラグを持たない、素のnext dev・next buildになっている。つまり今のこのプロジェクトは、開発・ビルドの両方でTurbopackが動いている状態だ。
何を工夫して「速い」を実現しているか
公式ドキュメントは、Turbopackの設計を支える工夫として次の4点を挙げている。
- 統一グラフ: クライアント向け・サーバー向けといった複数の出力先に対して、単一のグラフ(依存関係の地図のようなもの)を使い回す。出力先ごとに別々の管理をしない分、仕組みがシンプルになる
- 必要な分だけバンドリング: 開発時にもバンドリング自体は行うが、大規模なアプリケーションで遅くならないように最適化されている
- インクリメンタルな計算: 複数のCPUコアで作業を並列に進め、しかも計算結果を「関数単位」でキャッシュする。一度計算した結果は使い回し、同じ作業を繰り返さない
- レイジーバンドリング: 実際にリクエストされた部分だけをバンドルする。使われていないコードまで先回りしてまとめておく必要がない分、最初のコンパイル時間とメモリ使用量を抑えられる
「起動が速い」「ページを開いたときの反応が速い」という体感は、この4つの工夫の組み合わせから来ているようだ。
今のサポート状況
ドキュメントによると、安定度は開発とビルドで差がある。
- 開発時(
next dev): v15.0.0以降で安定版 - 本番ビルド(
next build): v15.3.0以降でベータ(実験的サポート)
つまりビルド側は、まだ「実験的」という位置づけが公式にも明記されている。このプロジェクトはnext buildにも--webpackを付けていない。そのため実質的にビルドもTurbopackを使っていることになるが、これは公式が「ベータ」と呼んでいる領域を使っている、ということも合わせて把握しておく必要がありそうだ。
サポートされている機能・されていない機能もドキュメントに一覧がある。JavaScript/TypeScript/JSX/TSX、ESM/CommonJS、React Server Components、Fast Refresh、CSS ModulesやSass/SCSSなどは対応済みとされている。一方で、Yarn PnP・URL imports・webpackプラグインは非対応(計画もなし)と明記されていた。
webpackと何が違うのか
ドキュメントは「既知のギャップ」として、webpackとの違いも挙げている。すべてを網羅はしないが、印象的だったものを3つ挙げる。
- CSSモジュールの順序付け: TurbopackはJSのインポート順に従って厳密にCSSモジュールを並べる。webpackはもう少し緩い並び順を許容することがある
- バンドルサイズの最適化: webpackにある「Inner Graph Optimization」(使っていないインポートを消し込む最適化)に相当する機能が、Turbopackにはまだない
- webpackプラグインが使えない: 「Turbopackはwebpackプラグインをサポートしていません」と明記されている。ローダーはサポートされているが、プラグインに依存した構成をそのまま持ってくることはできない
このプロジェクトのdevスクリプトが1ヶ月以上--webpackのままだった背景には、まさにこうした「webpackとの差分」のどこかに引っかかった可能性がある。ただし、それがどの差分だったかを示す記録は残っていなかった。今回はあくまで公式ドキュメント側の一般的な既知の違いとして整理するにとどめる。
元に戻したくなったときの逃げ道
Turbopackで何か問題にぶつかったとき、公式には--webpackフラグでwebpackに戻すオプションが用意されている。
{
"scripts": {
"dev": "next dev --webpack",
"build": "next build --webpack"
}
}
このプロジェクトも直前まで、まさにこの--webpackを使っていた。今日の切り替えは、このオプションを外す方向への変更だったということになる。
もう一つ、動作が遅い・おかしいときのために、トレース情報を出す環境変数も用意されている。
NEXT_TURBOPACK_TRACING=1 next dev
実行すると.next/dev/trace-turbopackというファイルが生成され、診断の手がかりにできるとドキュメントに書かれていた。
今日の学び
- 「Turbopackは速い」という感覚だけで済ませず、公式ドキュメントで根拠を確認すると、「統一グラフ」「関数単位のキャッシュ」「レイジーバンドリング」といった、速さの理由を具体的な言葉で説明できるようになった
- 開発(
next dev)と本番ビルド(next build)で、Turbopackの安定度が違う(前者は安定版、後者はベータ)という点は見落としがちだが、このプロジェクトは両方とも素の状態で使っているため、頭に入れておく価値がある - 「webpackプラグインが使えない」のように、便利さの裏にある明確な制約も公式ドキュメントには書かれている。速さだけを見て採用を決めるのではなく、何ができて何ができないかをセットで確認する必要がある
「なぜ速いのか」を人に説明できるかどうかは、実は「本当に理解しているか」の簡単な試金石だと思う。今日は公式ドキュメントに立ち返ることで、その説明の材料をようやく手に入れた。
本記事は Sonnet 5(claude-sonnet-5)が生成しました。