[Web フロントエンド] esbuild が爆速すぎて webpack / Rollup にはもう戻れない
TypeScript + Preact + Material UI + material-table で作っている管理画面のビルドツールを Rollup から esbuild に変えたお話です。
2021/01/07 追記
esbuild に新しく CSS ローダーやプラグイン機構が実装されたので紹介記事を書きました!
- esbuild の機能が足りないならプラグインを自作すればいいじゃない
https://www.kabuku.co.jp/developers/create-your-own-esbuild-plugin
はじめに
これまでの JavaScript ビルドツールと私
以前は私も定石通り「アプリには webpack、ライブラリには Rollup」をビルドに利用していましたが、近年はアプリのビルドにも Rollup を利用することが多くなり、 webpack を利用することはなくなりました。 webpack / Rollup / FuseBox / Parcel それぞれの出力コードを見比べたことがあるのですが、最も無駄のないキレイなコードを吐いたのが Rollup でした。
Parcel は環境構築がラクなのですが、 Windows で watch の動作がかなり不安定で何度も止めては再起動した記憶があり、プロダクトでは使ったことがありません。 FuseBox は比較にしか使ったことがありません。 Browserify のことは知りません。 Rome には期待していますが使えるのは当分先だと想像しています。
私がぶつかった Rollup の問題
Rollup は TypeScript との連携がずっと悩みでした。昔の公式の rollup-plugin-typescript は長い間 TypeScript 1.x しかサポートしておらず、 rollup-plugin-typescript2, rollup-plugin-typescript3 と乱立して、どれを使えばいいのか分からん状態が続いていました。公式が @rollup/plugin-typescript に名前を変えて 2.x が出たとき少しまともになりましたが、依然として rollup-plugin-typescript2 の方が安定している気がします。私は @rollup/plugin-typescript@4, @rollup/plugin-typescript@3, @rollup/plugin-typescript@2, rollup-plugin-typescript2 と順に試して、動いた時点でそのプラグインを使う、という非常に残念な運用をしたりします。
2020/06/17 編集
もう 1 点、これはもう解決されそうですが、 Acorn (Rollup や webpack が利用している JavaScript パーサー)が長い間 Optional Chaining に対応せず放置しており、 ES 2019 以下のコードにトランスパイルしないと Rollup や webpack ではバンドルできないという問題がありました。
add Optional Chaining by mysticatea · Pull Request #891 · acornjs/acorn
https://github.com/acornjs/acorn/pull/891
今後も、ブラウザーや Node.js は ECMAScript の新しい機能に対応しているのに Acorn が対応しないために webpack や Rollup では使えない、といった事態がありそうで心配です。
We’re waiting on the ESTree format to stabilize, and this’ll be able to get merged as soon as that happens.
https://github.com/acornjs/acorn/pull/891#issuecomment-612674733
Acorn の Optional Chaining 対応は ESTree のフォーマット の安定化を待っていたようです。背景も知らずに問題扱いしてしまい大変失礼いたしました。
2020/06/17 編集ここまで
モジュールバンドラーは遅い
モジュールバンドラーは遅いです。 webpack も Rollup も Parcel もみんな、もうとにかく遅い。 Parcel は webpack の設定の複雑さとビルドの遅さに応える形で現れましたよね。 webpack も Parcel が世に出てきた直後から速度にかなり気を配るようになったように思います。でも遅い。遅い遅い遅い遅ーーーーーーーーーい!
Snowpack や vite は、ブラウザーネイティブの import を利用することで、スクリプトの依存ツリー全体をバンドルすることなく提供できる開発ツールです。これらが作られたのはモジュールバンドラーがあまりにも遅いからでしょう。(vite は Vue.js で有名な Evan You さんによるプロダクトで、プラグインなしで Vue.js をサポートしており、プロダクション用に Rollup でバンドルする build コマンドがあります。 Snowpack には webpack や Parcel でバンドルするプラグインが用意されています。)
現在のプロジェクトの話
冒頭で述べたように、 TypeScript + Preact + Material UI + material-table で小さなアプリを作っています。 Rollup で watch 中の再ビルドに 10 秒ぐらいかかっていました。デザインをちょこっと変えて確認するのに 10 秒もかかるのはストレスでした(私の気が短いのかもしれません)。
Nollup も試したのですが、ブラウザーでページを見ても何も表示されず、コンソールにエラーが吐かれていました。 Nollup のソースコード上での問題箇所は分かるものの、私では手も足も出ませんでした。
esbuild
esbuild は Go 言語で書かれた JavaScript および TypeScript のビルドツールです。 esbuild 単体でトランスパイル + バンドル + ミニファイできます。 JSX / TSX もサポートされています。そしてめっちゃくちゃ速いという触れ込みです。最初から速度を意識して無駄がないように書かれており、構文解析・出力・ソースマップ生成は並列化され、ネイティブコードで動作します。公式の README では three.js のビルドが Rollup + terser より 100 倍速い と謳っています。
100 倍!? まさか!!
でも仮に esbuild の作者が自分に有利な条件でベンチマークを取っていたとして、実際には 100 倍ではなく 20 倍しか速度が出ないとしても… それでも十分速すぎる! …ということで使ってみることにしました。
設定というほどの設定もなく、エントリポイントを渡すだけですぐに使えました。そして 10 秒かかっていたビルド時間は… 0.2 秒程度 になりました。本当にめっちゃくちゃ速いです。
もう Rollup には戻れない!!
esbuild の課題
esbuild には(まだ)足りないものがあります。
TypeScript の型チェック
Babel と同様、型情報は単に捨てられます。別途 noEmit で tsc しましょう。
watch (ソースコード変更監視)
- [MVP] Watch mode · Issue #21 · evanw/esbuild
https://github.com/evanw/esbuild/issues/21
近いうちに実装されそうですが、現状は sane 等の watch ツールを利用しましょう。
CSS モジュール
- [MVP] Bundle CSS modules · Issue #20 · evanw/esbuild
https://github.com/evanw/esbuild/issues/20
(MVP になってますが私は個人的に CSS モジュールあんまり好きじゃないのでどうでもいいです。)
(現在のプロジェクトでは、 JS/TS からは CSS をインポートせず、 CSS のバンドルに PostCSS + postcss-import を利用しています。)
プラグインシステム
- Support the esbuild plug-in system? · Issue #111 · evanw/esbuild
https://github.com/evanw/esbuild/issues/111
上記 Issue はちゃんと読んでませんが、とりあえず esbuild にはまだプラグインシステムがないようです。 Babel のプラグインやマクロ、 webpack の各種ローダーやプラグインなど、現在のビルドツールはプラグインを利用してカスタム動作させるのが当たり前になっています。既存プロジェクトのビルドツールを esbuild へ移行するに際して問題になるのはここじゃないかと思います。
私はビルド時に CSS ファイルになっちゃう最強の CSS-in-JS Linaria が好きなのですが、プラグインシステムがないので Linaria を使うのは難しそうです。
Vue.js には vite
- Vue.js support · Issue #75 · evanw/esbuild
https://github.com/evanw/esbuild/issues/75
esbuild 自体では Vue.js をサポートしません。興味ある方は Evan You さんの最初のコメント 以降を DeepL で翻訳しましょう。すでに vite は TypeScript から JavaScript へのトランスパイルに esbuild を利用しているそうです。
比較環境を作った
TypeScript (TSX) で Preact (preact/compat) + Material UI を利用するソースコードを esbuild, webpack, Parcel, Rollup, Snowpack でそれぞれビルドできる環境を作りました。あとで設定を見返して参考にする目的もあります。
Rollup については TypeScript のトランスパイルに利用するプラグインを @rollup/plugin-typescript, @rollup/plugin-babel, @rollup/plugin-sucrase, rollup-plugin-esbuild から選べるようになっています(Rollup にはトランスパイルに esbuild を利用する rollup-plugin-esbuild プラグインがあるんです!)。
(Parcel 2 (nightly), vite, Nollup も含めたかったのですが、いずれもうまく動作させられずハマりそうだったので諦めました。 Deno はフロントエンドに使えるかどうか分からないのですが、少なくともソースコード側に変更が必要(import に拡張子が必要)なので試しませんでした。)
- luncheon/typescript-build-tools-comparison: TypeScript (TSX) Build Tools Comparison
https://github.com/luncheon/typescript-build-tools-comparison
クローンしてすぐ試せますので自分で確認したい方はどうぞ。
git clone https://github.com/luncheon/typescript-build-tools-comparison.git
cd typescript-build-tools-comparison
npm ci
npm run install
npm run build
まとめ
esbuild はとにかく速い JavaScript / TypeScript ビルドツールです。 devDependencies がプラグインだらけにならないのもうれしい! Snowpack や vite を使えば開発中の再コンパイル時間の問題は大きく改善すると思いますが、開発中はバンドルしないわけですから、開発中とプロダクションビルドとで全く同じ挙動をするわけではありませんよね。パフォーマンス問題やビルドツールの不具合に起因する問題などの発見が遅れるんじゃないかと心配になります。 esbuild なら大丈夫、毎回プロダクションビルドするだけです!
ビルド時間に疲れている方はお試しあれ!
(ちなみに私は これからもライブラリには Rollup を使います。出力コードが小さくなるので。 Rollup + TypeScript + terser でも 1 秒程度でビルドできるような小さなライブラリ開発にはファイルサイズを重視して Rollup + TypeScript + terser を、それ以外には esbuild を使っています。)
ではでは null でしたー
その他の記事
Other Articles
関連職種
Recruit