どうもこんにちは。
Next.js 13発表されましたね。
aタグが不要になったnext/link
や、React18のSuspenseをつかっていい感じにレンダリングしてくれるapp/ Directory
等、魅力的な新機能が追加されましたが、今回は中でも個人的に気になったTurbopack
を試してみたいと思います。
公式のdocsでは「大規模なアプリケーションでは、TurbopackはVite
よりも10倍速く、Webpack
よりも700倍速く更新されます」なんて書いちゃってるわけですから、相応の実力なのでしょう。さっそくみていきます。
お願い
一通り試すことで知識を深め、忘れないために個人的なメモとしてアウトプットしていますので、英語の解釈が間違っていたり、おかしいことを言ってるかもしれないです。
もし間違ってることを言っていたらコメントで教えていただけると非常に助かります。
Turbopackが速い理由
Turbopackを使ってみる前に、なぜ大規模なアプリケーションでは、TurbopackはVite
よりも10倍速く、Webpack
よりも700倍速く更新されるのか仕組みを理解したいと思います。
docsによるとこのパフォーマンスを実現するために主に関数レベルのキャッシュ
とリクエストレベルのコンパイル
の二つの仕組みを用いているようです。それぞれ見ていきます。
関数レベルのキャッシュ
Turbopackは関数レベルで漸増計算(incremental computation)を行なってプログラムをバンドルします。漸増計算とは、ある入力データが更新されるたびに、更新されたデータに依存した出力のみを再計算することです。
これにより全てを再計算するよりも不必要な計算がなくなり、結果的により高速にバンドルできるようになるようです。
それでは、公式docsの図を引用しつつ、具体的な流れを追ってみましょう。
ここでは、開発サーバーでapi.ts
とsdk.ts
をバンドルする場合を考えてみます。
引用: https://turbo.build/pack/docs/core-concepts
api.ts
とsdk.ts
をバンドルする場合、バンドラーはapi.ts
とsdk.ts
それぞれにおいてバンドルし、連結して一つのファイル(画像のfullBundle)にします。この時Turbopackは、呼び出されたすべての関数の結果をキャッシュに保存します。
ではここで、あなたがsdk.ts
を追加開発してsdk.ts
が更新されたとしましょう。するとどうなるでしょうか。
引用: https://turbo.build/pack/docs/core-concepts
sdk.ts
が更新されたため、Turbopackはファイルシステムイベントを受け取り、再度バンドルする必要があることを認識します。
Turbopackは新しいfullbundleを生成しようとするのですが、重要なのはapi.ts
が更新されていないということです。この時Turbopackはapi.ts
を再度バンドルするわけではなく、先ほどキャッシュしたキャッシュから読み取り、concatに渡します。
このように関数レベルでキャッシュすることで、不必要な再計算を削除し、よりパフォーマンスが向上します。
Turbopack alpha版では、キャッシュはメモリに保存されます。将来的にこのキャッシュはリモートキャッシュに保存する計画のようです。詳しくは「Turbopackの未来」の説で紹介したいと思います。
リクエストレベルのコンパイル
Next.js 11以前では、開発サーバーを起動する際に、アプリケーション全体をコンパイルしてから表示していましたが、パフォーマンスの観点からNext.js 11からリクエストされたページのコードのみをコンパイルするようになったみたいです。
ページ単位でのコンパイルでも十分な気がしますが、Vite
のようなフレームワークと比べたときに完璧ではないように感じます。
というのも、Vite
はNative ESMを開発モードで使用しており、Native ESMはバンドルを行わないためviewに本当に必要なもののみ読み込むためです。
ページ単位でのコンパイルでは、すべてのモジュールやviewに隠されているページのコードも全て読み込みバンドルしてしまうため、やはりNative ESMを使用したVite
のようなフレームワークと比べたときに実行速度で負けてしまいます。
そこでTurbopackでは、Native ESMのようにページのレンダリングに必要なコードのみを計算しブラウザに送信します。
なぜTurbopackはViteより速いのか
なぜTurbopackはVite
より速いのでしょうか。docsによるとNative ESMを使わないことと、esbuildを使用しないことが関係しているようです。
先ほど説明した通り、Vite
はNative ESMを開発モードで使用しています。Turbopackは、Native ESMのようにページのレンダリングに必要なコードのみを計算しブラウザに送信するため、一見同じようなことをしているように見えますが、多くのモジュールで構成される大規模なアプリケーションでは少し事情が異なってくるようです。
Native ESMを使用しているVite
では、コードをバンドルせずブラウザに送信します。多くのモジュールで構成される大規模なアプリケーションでは連鎖的にネットワークリクエストが大量に発生し、起動時間が比較的遅くなる可能性があります。
Native ESMを使用せずバンドルしてから送信するTurbopackは、この連鎖的にネットワークリクエストが大量に発生ことがないため、多くのモジュールで構成される大規模なアプリケーションにおいてVite
より速くなるようです。
また、esbuildを使用しないことも少なからず寄与しているようです。
Vite
は内部でesbuildを多くのタスクで使用しています。esbuildは非常に高速なバンドラーですが、あまりキャッシュを行いません。
先ほど関数レベルのキャッシュの節で説明した通り、積極的にキャッシュを行い無駄な計算を省くRustベースのTurbopackは大規模なアプリケーションにおいて、開発チームはesbuildよりも優れたパフォーマンスを発揮できると考えているようです。
Turbopackをつかってみる
Turbopackをある程度理解したところで、ここからは実際にTurbopackをつかってみたいと思います。 現在TurbopackはNext.js v13でのみ使用することができるためNext.js v13でvercelが用意しているチュートリアルプロジェクトを新規作成します。
$ npx create-next-app --ts --example with-turbopack
作成したプロジェクトのルートディレクトリに移動し、npm install
npm run dev
を行います。
これだけです。これだけでTurbopackを使用した開発サーバーが起動します。
チュートリアルで触ってみた所感
遷移ごとにバンドルされるため若干のラグが若干気になる
これは仕方がないことですが、画面を遷移するたびに100ms~500ms程度のラグが発生します。
Next.js 12の時にもページ遷移時に発生していた100ms~500ms程度のラグがなんで気になるのかなって考えてみたのですが、多分Next.js 12の時には右下に表示されていたこれがなくなってるからですね。
読み込み時には表示してほしいな〜
たまに反応しないボタンが存在する
これは、なんでかわからないのですが、たま〜に反応しないボタンが生まれます。別ページに遷移したりdev toolを開いたりするとクリックできるようになるので何かしらのバグがありそうですね。再現手順がいまいちわからないのでなんとも...
beta版で改善されてたらいいですね。
Turbopackの未来
最後にTurbopackの未来として、docsに記載されていた中で興味深かったリモートキャッシュを紹介したいと思います。
現在alpha版のTurbopackは関数レベルのキャッシュをメモリ内に保存していますが、将来的にはこれをファイルシステムに保存して永続化する計画のようです。
ファイルシステムに保存することに成功したら、次はリモートキャッシュへの永続化を実装するそうです。
将来的にはVercel Remote Cacheを使用してチーム内で関数レベルのキャッシュを共有することができるようになります。
リモートキャッシュができるということは、無駄に長時間かかるproduction buildもパフォーマンスが大幅に向上する可能性があるということを意味します。
もしかしたら大量にコンテンツがあるSSG時のビルドもパフォーマンスが大幅に向上する可能性があるかもしれませんね。
(Jamstackのような構成のwebアプリだった場合コンテンツをTurbopackが知るためにAPIを叩く必要があるわけですが、本気でSSG時のビルドパフォーマンスを大幅に向上するとなったら、変更がないコンテンツを取得しないようにしなければならないと思います。そうなると変更されたコンテンツを列挙したエンドポイントを用意しなければならないわけで...ちょっとめんどくさそうですね)
まとめ
この記事ではNext.js 13の新機能のTurbopackの仕組みをまとめ、Turbopackを簡単に試してみました。
Viteの10倍速いなんて宣伝されていたので、もうViteが廃れるのか...と最初は思っていましたが、そんなことはないと思います。小規模なアプリにおいては本当に誤差の範囲ですし、大規模なアプリにおいても数秒程度の差しかないのでうまく共存できると思います。
ただ、Next.jsがwebpackを脱却できたのは非常に喜ばしいことだと個人的に思いました。これからもVercelに依存する生活を送れそうです。
じゃ、終わり。