Closed59

[キャッチアップ] Turbopack

shingo.sasakishingo.sasaki

Turbopack のキャッチアップ

主に公式ドキュメントの要約
https://turbo.build/pack

ドキュメントの内容を元に事実をスレッド投稿して、個人的な感想とかメモを返信に追記する形にしてます。

普段は Vue を書いてる都合、Next.js にはあまり詳しくないので、純粋に webpack ユーザーの視点で読んでる。

記事の内容はいずれも 2022/10/29 時点のもの。

shingo.sasakishingo.sasaki

Getting Started with Turbopack

https://turbo.build/pack/docs

  • Turbopack は Vercel 社の Next.js や webpack の開発者によって Rust を用いて新たに開発された JS/TS 向けのトランスパイラ
  • Webpack の根本的な置き換えが目的
  • Vite より10~20倍、Webpack よりも 700倍速いのがウリの一つ
  • 現在はアルファバージョンでプロダクション利用は推奨されないが、 Next.js v13 と組み合わせすぐに利用することができる
npx create-next-app --example with-turbopack
shingo.sasakishingo.sasaki

ベンチマークについてはマーケティング上の戦略もあるからそこまで鵜呑みにしないほうが良さそう。
Bun が Node や Deno よりも圧倒的に速いってアピールしてたのも懐疑的だったりするし。

shingo.sasakishingo.sasaki

Why Turbopack?

https://turbo.build/pack/docs/why-turbopack

  • Next.js の開発にあたって、開発スピードを向上させたいという課題があった
  • JS ツールを使用している限りスピードの限界があったため、Babel や Teser を使わないようにしたので、次は webpack を廃止する段階に入った
  • esbuild や src のような、非JSのツールは既に出回っているが、諸々検討した結果、新規開発することにした(後述)
shingo.sasakishingo.sasaki

Next の開発に Babel や Teser は組み込んでないんだ。この辺も swc さえあればどうにかなるのかな。

shingo.sasakishingo.sasaki

Why Turbopack? > Bundling vs Native ESM

  • Vite は開発モードではモジュールバンドルをせずに、Native ESM を活用することによって、変更のあったファイルのみを再ビルドすることで開発スピードを劇的に向上させた
  • 一方で、Vite でもアプリケーションがスケールしてモジュール数が増大した時に、問題に衝突するようになった
  • Native ESM はモジュールの数だけネットワークリクエストが発生するため、モジュール数が増えるほどに、リクエストの洪水が起こり、初回読み込みに時間がかかるようになった
  • それをふまえ、開発環境であっても Webpack のようなモジュールバンドラを作ることにした
  • Webpack 自体はモジュールバンドルが低速だが、Turbopack では Rust を使った低レベルの最適化によって、アプリケーションがスケールするほどに webpack よりも高速さが目立つバンドラとなった
shingo.sasakishingo.sasaki

規模の大きなアプリケーションにおける Vite の初回読み込みの遅さは自社アプリでも既に実感してる。HMR の爆速さのほうが重要だと思って割り切ってはいるけど、そっちはバンドルのほうが優れてると理解してるので Turbopack が良いとこ取りしてくれるなら嬉しい。

shingo.sasakishingo.sasaki

まだ swc のモジュールバンドル機能に投資する方向でも良いような気がするけど、別途説明されるかな?

shingo.sasakishingo.sasaki

Why Turbopack? > Incremental Computation

  • プロセスを高速化するためには作業量を減らすか、並列実行数を増やすかがあるが、TurboPack ではその両方を実現している
  • 両方を実現するため、再利用可能な Turbo Build Engine を新たに作成した。これは関数呼び出しのスケジューラのように動作し、関数への呼び出しを利用可能なすべてのコアで並列化することができる
  • 関数の呼び出し結果はキャッシュされるため、2度同じ処理をする必要はなくなる

Vite and esbuild

  • Vite は作業量を減らすためのアプローチとして、開発モードでは Native ESM を使用しているが、TurboPack では前述の通り採用しなかった
  • Vite はバンドルには esbuild を使用しており、これも高速なバンドラではあるが、以下の理由でこちらも採用しなかった
    • HMR の機能を有していない
    • キャッシュの仕組みを有していない
  • esbuild は次世代のモジュールバンドラの実証実験の役目をしたと考え、Incremental computation を行う Rust を使ったバンドルによって、esbuild よりも大きなスケールでよりよいパフォーマンスを発揮できると考えた
shingo.sasakishingo.sasaki

Turbo Build Engine の説明は正直何をいってるのかよくわからないけど、Rust を使うことで低レベルで高速かつ安全性の高い最適化が出来るんだろうなぁとか適当に納得しておく。

shingo.sasakishingo.sasaki

Why Turbopack? > Lazy bundling

  • 初期の Next.js では開発モードでもアプリケーション全体をビルドしていたが、すぐにそれが不適切であることに気づいて、現在ではリクエストのあったページだけをオンデマンドでビルドするようになっている(これが Lazy bundling)
  • この Lazy bundling をさらに怠惰に、本当に必要になったアセットだけをビルドすることが開発サーバーの高速化の鍵である
  • Vite は Native ESM を用いることで、必要なモジュールが必要な別のモジュールを要求するといったシンプルな(魔法的でない)仕組みとなっているが、前述の理由からこれを採用せずにモジュールバンドルを使いたかった
  • せっかくバンドルが高速な esbuild には Lazy の概念がなく、エントリーポイントに従ってオール・オア・ナッシングなビルドをしてしまう
  • Turbopack の開発モードは、モジュールの import/export に基づいたミニマムな依存グラフを作成し、必要最低限のモジュールのみバンドルする (Core Concepts にて後述)
  • この戦略によって、Native ESM を使用せずに、リクエストに応じた必要最低限のバンドルのみを作成することによって、劇的なパフォーマンス向上を実現した
shingo.sasakishingo.sasaki

つまりやってることは現状の Next.js のオンデマンドビルドと一緒だけど、それをさらに最適化するために Rust で TarboPack を開発したってことでいいのかな。

webpack 自体が遅いのは JS の限界であることは esbuild が証明したわけだから。

shingo.sasakishingo.sasaki

アプリケーションがスケールどんなにスケールしても、ネットワークリクエストはバンドルされたファイル1件だから Native ESM よりも安定する。本当かな〜?

shingo.sasakishingo.sasaki

Why Turbopack? > Summary

  • スケールしたアプリケーションにおいては Native ESM よりもバンドラのほうが最適なのでモジュールバンドラを作ることにした
  • Turbo Engine による Incremental computation ビルドするアーキテクチャによって最小の労力で最大の成果を出せるようになった
  • 依存グラフによる必要最低限のバンドルによって開発サーバーを最適化した
shingo.sasakishingo.sasaki

Core Concepts > The Turbo engine

画像の引用元は元ページ

  • Turbopack には Turbo engine という、Rust 製で Incremental computation を持った再利用可能なライブラリが含まれており、それこそが速さの秘訣である
  • Turbo engine のプログラムは、呼び出された関数の出力をインメモリにキャッシュし、再利用する

Function-level caching

以下はバンドラのイメージ

api.ts sdk.ts の②ファイルがあり、それぞれが readFile を呼び出している場合、まずそれぞれのファイルについてバンドルを作成してから、両バンドルを結合して、最終的なバンドルが完成する。

このとき、sdk.ts を変更した場合は、そこから依存するほうのバンドル、及びそこから作成される concat fullBandle のみが再構築されるが、影響のない api.ts 及びそのバンドルは変更されない。

実際のアプリケーションはモジュール数が膨大だが、基本は上記の仕組みがそのまま機能するため、モジュール数が多ければ多いほど Incremental computation の恩恵は大きくなる。

The cache

  • 現状ではオンメモリキャッシュを使用することができ、プロセスが起動している間、関数の実行結果がキャッシュされ再利用される
  • Next.js v13 では next dev--turbo コマンドですぐに利用可能
  • 将来的にはファイルシステムなどを用いた永続キャッシュの導入も予定している

How does it help?

  • 現状ではこの仕組によって開発サーバーの読み込みを爆速化させ、差分ビルドも即座に反映させることができる
  • 将来的にキャッシュの永続化ができるようになれば、プロダクションビルドについても差分のみ再実行できるようになることも
shingo.sasakishingo.sasaki

incremental computation は図入りで丁寧に説明されてるけど、webpack-dev-server でもやってることだよね?それを Rust を使って低レベルにチューニングしてるから webpack より速いって考えて良いのかな。

とはいえ、そこそこ大きなアプリケーションだと webpack-dev-server の HMR もだいぶ遅いのでそこがめちゃくちゃ改善されてるなら楽しみ。

shingo.sasakishingo.sasaki

プロダクションビルドでのキャッシュ利用も理論上は webpack-dev-server でも可能よね。
webpack 5 ならファイルシステム使った Persistent cache が使えるから一応…。

https://webpack.js.og/configuration/cache/

ただ安全性の都合で production mode では無効化されてたと思う。

リスクはあるけど、プロダクションビルドしたバンドルで最低限E2Eテスト走らせるぐらいの仕組みがあればまぁいいのかな。

shingo.sasakishingo.sasaki

Core Concepts > Compiling by Request

  • 前項の Trubo Engine の仕組みは差分ビルドを最低限に高速に行うためのものだった
  • それとは別に、開発用サーバーの初回起動を高速化させる仕組みが必要になる
  • Turbo Pack には開発サーバ起動からユーザーがブラウザで最初の操作をするまでの時間を最小限にするための仕組みがある

Page-level compilation

  • Next.js 11 から導入された、リクエストされたページのみをコンパイルする仕組み
  • これだけでも充分だが、ページに紐付いたすべてのモジュール、アセットがコンパイルの対象となってしまう
  • コンパイル対象のうち、実際にそのページで必要とされるものが一部に過ぎない可能性もある(非表示要素など)

Request-level compilation

  • Turbopack では実際に要求されたコードのみをコンパイルすることができる
  • 例えば CSS が要求されたら CSS はコンパイルするが、そこから参照された画像はまだ解決しないなど
  • また、ブラウザの devTools が開くまではソースマップさえ必要ないと判断する
  • Native ESM についても同じような挙動をするが、Native ESM には前述のデメリットもある
shingo.sasakishingo.sasaki

webpack の思想はそのままに、Native ESM の良いとこを取り入れたって感じかな。リクエストベースがまさにそう。

shingo.sasakishingo.sasaki

devTools 開くまでソースマップの生成をしないってのはすごい。確かにたまにしか devtools 開かないのに毎回しっかり生成して時間かかってるところはあるしね。

shingo.sasakishingo.sasaki

Roadmap

Turbopack の今後について

https://turbo.build/pack/docs/roadmap

Next.js

  • 既にオプトイン形式で Next.js の開発用サーバーとして使用可能
  • プロダクションビルドで使用できるように準備中

Svelte

  • Svelte をファーストクラスとしてサポートすることを計画中
  • SvelteKit に Turbopack を組み込む予定

Other Frameworks

  • 目下検討中

Remote Caching and Replication

  • Turbopack はキャッシング機構を最大限活用するためにもゼロから再構築されている
  • 現状のキャッシュはインメモリのみだが、開発サーバーを高速で動かすために使える
  • 将来的にはファイルシステムを使ったキャッシュの永続化を予定しており、開発サーバー再起動時にも高速で動くようにする
  • ファイルシステムキャッシュが動くようになったら次はリモートキャッシュを進む
  • リモートキャッシュを通じて、チーム全体でキャッシュを共有できるようになる

Migration for Webpack users

Migrating from Webpack のページで後述

Fusion with Turborepo

本スクラップでは Turborepo のほうは扱ってないので割愛

shingo.sasakishingo.sasaki

Svelte をファーストクラスサポートするってのが意外っちゃ意外。
Vue というか Nuxt は Nuxt 3 で Vite を標準サポートしてるしライバルっちゃライバルなのかな。

shingo.sasakishingo.sasaki

Features

https://turbo.build/pack/docs/features

Webアプリケーション開発を構成する仕組みは多彩で、CSSに限っても単独のCSSから SCSS, Less, CSS Modules, Post CSS, さらに多彩なCSSライブラリ。React や Vue, Svelte のようなフレームワークを併用する場合は個別のセットアップも必要だったり。

バンドラーを構築する上で、それらを以下の3通りのパターンで考慮する必要がある

  • ビルトイン: 最初から設定無く使用可能
  • プラグインでの対応: 別途プラグインパッケージをインストールし、個別に設定することで使用可能
  • 非対応: 使用することができない

Trubopack は現在アルファバージョンであり、一部の限られた機能のみが使用可能で、プラグインシステムも用意されていない。

この章ではどんな機能がビルトインで利用可能できるかについて触れていく。

shingo.sasakishingo.sasaki

この章は多分段階的なバージョンアップを経てどんどん進化していくのでそこまで深堀りする必要はなさそう。

shingo.sasakishingo.sasaki

Features > JavaScript

ECMA Script

  • Turbopack では SWC を使って JavaScript / TypeScript のバンドルを行う
  • よって SWC でサポートされている ECMAScript はすべてサポートしていると言える (= ESNext までOK)

Browserslist

Browserslist を使ったターゲットブラウザの指定もビルトインでサポートされているので、package.jsonbrowserslist フィールドを追加するなどで、レガシーブラウザをサポートすることも可能

Babel

現在はサポートしていない。今後はプラグイン形式でサポートするかも。

shingo.sasakishingo.sasaki

トランスパイルは普通に SWC 使ってるんだ。そこも含めてフルスクラッチで開発してるのかと思ってた。

shingo.sasakishingo.sasaki

Features > TypeScript

TypeScript 及び JSX もビルトインでサポート対象で、.ts .tsx をインポートすることで自動でトランスパイルされる。

Resolving paths and baseUrl

tsconfig で設定した paths baseUrl を認識してくれるので、それ以上の設定をすることなく絶対パスだけでインポートができるようになる。

Type Checking

SWC を使用している都合、型チェックは行わない。型チェックをしたければ素直に tsc を使用するのが良い。

shingo.sasakishingo.sasaki

Features > Frameworks

Turbopack は複数のフレームワークに対してファーストクラスサポートをしたいと検討している

React

JSX/TSX

.jsx .tsx は SWC を使用しているためビルトインで利用可能。 JSX を使用するために react をインポートする必要もない。

React Server Components

Server Components は Next.js 13 からファーストクラスサポートをされるようになった。

Server Comonents の登場によってバンドルの仕組みに制約が生まれた。コードベース上はサーバーコンポーネントとクライアントコンポーネントが混在しているが、サーバーコンポーネントがクライアントのバンドルに混ざってはいけないし、逆もしかり。

そういった問題に立ち向かうためにも Turbopack はゼロから構築され、ビルトインサポートに至った。

Next

Turbopack のアルファバージョンは Next.js の開発体験を向上させることにフォーカスしたため、Turbopack のすべての機能を Next.js では使用することが出来る。

将来的には Next.js から分離し、低レベルのエンジンとして他のフレームワークにも適合できるようにする。

Vue and Svelte

今は対応してないけど将来的にはサポートする予定

shingo.sasakishingo.sasaki

他の章では Svelte をファーストクラスサポートするって言って Vue には言及してなかったけど、ここでは普通に名前を出したな。

shingo.sasakishingo.sasaki

Features > CSS

CSS のバンドルは Turbopack に統合された swc_css と呼ばれる Rust で書かれた仕組みを利用しつつ、SWCでバンドルしており、いくつかのCSS機能を有する。

Global CSS

import './globals.css のような形でグローバルスコープに対してCSSを読み込める

CSS Modules

.model.css の形式で CSS モジュールを JS/TS からインポートできる。
モジュール内では postcss-nested 形式や、 @import シンタックスも利用可能。

PostCSS

現状はサポートしてない。将来的にするかも。

SCSS and LESS

現状はサポートしてない。将来的にするかも。

Tailwind CSS

現状はサポートしていない。そもそも PostCSS を使った仕組みなのでそっちに依存するため。

shingo.sasakishingo.sasaki

PostCSS / SCSS をサポートしてないとは言うけど、TurboPack 自体に組み込めないという話なので、ツールチェインを別プロセスとして動かしておけば普通に組み合わせて使用することはできる。

shingo.sasakishingo.sasaki

Features > Dev Server

Turbopack は開発サーバを高速化するために最適化されてるので、 Dev Server 機能はマスト

HMR

Core Concepts の章で述べた通り強力なビルトインサポートがある

Fast Refresh

Fast Refresh は HMR 上に構築されるフレームワークレベルの統合で、例えばコンポーネントのステートを維持したまま再読込する仕組みを指す。

HMR 同様に、ビルトインサポートされてる (React)

shingo.sasakishingo.sasaki

↑の How it Works を見るに、コンポーネントのみを export したファイルの場合は編集してもモジュール全体を置き換えずにコード改変だけして、コンポーネントの再レンダリングを行うみたい。

再レンダリングだけなら副作用として分離してるステートは保持されたままだからってことね。すごい。

shingo.sasakishingo.sasaki

Features > Static Assets

Import static assets

画像、動画、音声など多くの静的ファイルはそのまま import 可能

Public directory

/public がデフォルトでパブリックディレクトリになる

JSON

JSON ファイルを直接インポート可能

shingo.sasakishingo.sasaki

Features > Imports

  • CommonJS / ESM ともにビルトインサポート (static/dynamic いずれも)
  • Turbopack はバンドラーなので import は ESM としては解決されないので注意
shingo.sasakishingo.sasaki

Features > Environment Variables

.env files

各種 .env ファイルをサポート

  • .env
  • .env.local
  • .env.development
  • .env.production.local

また、これらの値が変更されたとき、自動でライブリロードが行われる(=開発サーバ再起動は不要)

process.env

  • .env で定義した環境変数は process.env に展開される
  • これは Node 及び Webpack, Next などの伝統に従ったもの
shingo.sasakishingo.sasaki

Comparisons > Turbopack vs Vite

Vite は Native ESM を使った高速な開発サーバを提供するツールで、Turbopack を開発する前は Next.js に Vite を組み込むことも検討したが、Why Turbopack? の通り Native ESM 上のボトルネックに衝突したため見送った。

Turbopack のほうが開発サーバーの起動は5.5倍、ファイル変更時の差分反映は5.8倍速いというベンチマークもでている。

shingo.sasakishingo.sasaki

Comparisons > Turbopack vs Webpack

Turbopack は Webpack を置き換えることを目的として開発された。

こちらもベンチマークによって Turbopack のほうが開発サーバーの起動は3.9倍、ファイル変更時の差分反映は 8.9倍速いことが確認された。

なお、Webpack にはその挙動をカスタマイズするためのプラグインが豊富に用意されており、その組み合わせによって多種多様なバンドルを生成することができるのだが、Turbopack のα版ではそういった機能は用意されていない。将来的には Webpack と同様の拡張性をもたらす予定である。

shingo.sasakishingo.sasaki

あれ、 Quickstart では以下のように webpack より 700倍速いって言ってるのに、ここでは現実的な数字が出てるのはなんでだろ。

On large applications Turbopack updates 10x faster than Vite and 700x faster than Webpack. For the biggest applications the difference grows even more stark with updates up to 20x faster than Vite.

shingo.sasakishingo.sasaki

On large applications っていってるけど、ベンチマークをとった 1000 モジュール程度じゃ large とは言わないのかな。

shingo.sasakishingo.sasaki

Migrating from Webpack To Turbopack

Turbopack は Webpack の後継になることを計画し、いずれは Webpack アプリに必要なすべてを Turbopack でも実現することを目指している。

現状で言えば webpack からのマイグレーションは不可能だが、いずれはスムーズなマイグレーション手順を提供する予定である。

https://turbo.build/pack/docs/migrating-from-webpack

Will it be compatible with Webpack's API?

Webpack は柔軟性に富んでいてそれが人気の理由の一つである。 Turbopack も優れた柔軟性を持つように計画しているが、 webpack API との一対一の互換性は考えていない。スピードと効率を最適化しつつ、Webpack の API を改善していくための選択である。

Will we be able to use Webpack plugins?

API 互換を予定していないため、プラグインをそのまま動かすことも不可能になる。ただし主要なプラグインの多くは Turbopack プラグインとして取り入れる予定である。

shingo.sasakishingo.sasaki

Webpack 後継を目指すけど、あくまでさらに良いものを作っていくために互換性を意識しないってスタンスなんだね。

Node に対する Deno に近いけど、結局 npm という膨大なエコシステムに引きずられるってのがあったけどこれはどうだろ。 webpack の主要以外のプラグインってそんなに言うほど使われてるのかな。

個人的にはある程度の機能が出揃えば、ほとんどのユースケースにおいてマイグレーションが可能になる気がしてる。まして Next.js みたいなフレームワークに乗っかってるプロダクトならなおさら。

shingo.sasakishingo.sasaki

まとめ

本スクラップでは、Turbopack のドキュメントを意訳しつつ、自分の感想やメモをぶらさげる形でキャッチアップした。

自分の理解は以下の通り

  • webpack の後継として開発された開発サーバー兼モジュールバンドラ
  • Rust で低レベルでの最適化を施したり、SWC を用いた JS を使わないトランスパイルによって高速化を実現
  • webpack の後継と謳うが、互換性はなくより良いものをゼロベースで作っていく
  • Vite のように Native ESM を採用しなかったのは、スケール時に初回読み込み時のネットワークリクエストが増えすぎてボトルネックになるため
  • 現状は Next.js と密だが、徐々に汎用エンジンとして他のフレームワークにも組み込めるようになっていく

まったく新しいツールと言うよりは、Webpack + Next.js での長い経験と、esbuild や swc の次世代的アプローチを組み合わせていいとこどりしたツールという感じがした。

今後 Native ESM を主軸とした Vite のような開発環境がデファクトになっていくのか、モジュールバンドラを再実装した Turbopack が伸びていくのかは個人的にも楽しみにしてる。

このスクラップは2022/10/29にクローズされました