[Web フロントエンド] esbuild が爆速すぎて webpack / Rollup にはもう戻れない

2020/06/16
このエントリーをはてなブックマークに追加
esbuild vs snowpack vs vite vs nollup | npm trends

TypeScript + Preact + Material UI + material-table で作っている管理画面のビルドツールを Rollup から esbuild に変えたお話です。

2021/01/07 追記

esbuild に新しく CSS ローダーやプラグイン機構が実装されたので紹介記事を書きました!

はじめに

これまでの 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 ではバンドルできないという問題がありました。

今後も、ブラウザーや 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 が世に出てきた直後から速度にかなり気を配るようになったように思います。でも遅い。遅い遅い遅い遅ーーーーーーーーーい!

Snowpackvite は、ブラウザーネイティブの 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 (ソースコード変更監視)

近いうちに実装されそうですが、現状は sane 等の watch ツールを利用しましょう。

CSS モジュール

(MVP になってますが私は個人的に CSS モジュールあんまり好きじゃないのでどうでもいいです。)

(現在のプロジェクトでは、 JS/TS からは CSS をインポートせず、 CSS のバンドルに PostCSS + postcss-import を利用しています。)

プラグインシステム

上記 Issue はちゃんと読んでませんが、とりあえず esbuild にはまだプラグインシステムがないようです。 Babel のプラグインやマクロ、 webpack の各種ローダーやプラグインなど、現在のビルドツールはプラグインを利用してカスタム動作させるのが当たり前になっています。既存プロジェクトのビルドツールを esbuild へ移行するに際して問題になるのはここじゃないかと思います。

私はビルド時に CSS ファイルになっちゃう最強の CSS-in-JS Linaria が好きなのですが、プラグインシステムがないので Linaria を使うのは難しそうです。

Vue.js には vite

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 に拡張子が必要)なので試しませんでした。)

TypeScript ビルドツールのビルド時間比較

クローンしてすぐ試せますので自分で確認したい方はどうぞ。

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

2022/06/03
拡張子に Web アプリを関連付ける File Handling API の使い方

2022/03/22
<selectmenu> タグできる子; <select> に代わるカスタマイズ可能なドロップダウンリスト

2022/03/02
Java 15 のテキストブロックを横目に C# 11 の生文字列リテラルを眺めて ECMAScript String dedent プロポーザルを想う

2021/10/13
Angularによる開発をできるだけ型安全にするためのKabukuでの取り組み

2021/09/30
さようなら、Node.js

2021/09/30
Union 型を含むオブジェクト型を代入するときに遭遇しうるTypeScript型チェックの制限について

2021/09/16
[ECMAScript] Pipe operator 論争まとめ – F# か Hack か両方か

2021/07/05
TypeScript v4.3 の機能を使って immutable ライブラリの型付けを頑張る

2021/06/25
Denoでwasmを動かすだけの話

2021/05/18
DOMMatrix: 2D / 3D 変形(アフィン変換)の行列を扱う DOM API

2021/03/29
GoのWASMがライブラリではなくアプリケーションであること

2021/03/26
Pythonプロジェクトの共通のひな形を作る

2021/03/25
インラインスタイルと Tailwind CSS と Tailwind CSS 入力補助ライブラリと Tailwind CSS in JS

2021/03/23
Serverless NEGを使ってApp Engineにカスタムドメインをワイルドカードマッピング

2021/01/07
esbuild の機能が足りないならプラグインを自作すればいいじゃない

2020/08/26
TypeScriptで関数の部分型を理解しよう

2020/03/19
[Web フロントエンド] Elm に心折れ Mint に癒しを求める

2020/02/28
さようなら、TypeScript enum

2020/02/14
受付のLooking Glassに加えたひと工夫

2020/01/28
カブクエンジニア開発合宿に行ってきました 2020冬

2020/01/30
Renovateで依存ライブラリをリノベーションしよう 〜 Bitbucket編 〜

2019/12/27
Cloud Tasks でも deferred ライブラリが使いたい

2019/12/25
*, ::before, ::after { flex: none; }

2019/12/21
Top-level awaitとDual Package Hazard

2019/12/20
Three.jsからWebGLまで行きて帰りし物語

2019/12/18
Three.jsに入門+手を検出してAR.jsと組み合わせてみた

2019/12/04
WebXR AR Paint その2

2019/11/06
GraphQLの入門書を翻訳しました

2019/09/20
Kabuku Connect 即時見積機能のバックエンド開発

2019/08/14
Maker Faire Tokyo 2019でARゲームを出展しました

2019/07/25
夏休みだョ!WebAssembly Proposal全員集合!!

2019/07/08
鵜呑みにしないで! —— 書籍『クリーンアーキテクチャ』所感 ≪null 篇≫

2019/07/03
W3C Workshop on Web Games参加レポート

2019/06/28
TypeScriptでObject.assign()に正しい型をつける

2019/06/25
カブクエンジニア開発合宿に行ってきました 2019夏

2019/06/21
Hola! KubeCon Europe 2019の参加レポート

2019/06/19
Clean Resume きれいな環境できれいな履歴書を作成する

2019/05/20
[Web フロントエンド] 状態更新ロジックをフレームワークから独立させる

2019/04/16
C++のenable_shared_from_thisを使う

2019/04/12
OpenAPI 3 ファーストな Web アプリケーション開発(Python で API 編)

2019/04/08
WebGLでレイマーチングを使ったCSGを実現する

2019/03/29
その1 Jetson TX2でk3s(枯山水)を動かしてみた

2019/04/02
『エンジニア採用最前線』に感化されて2週間でエンジニア主導の求人票更新フローを構築した話

2019/03/27
任意のブラウザ上でJestで書いたテストを実行する

2019/02/08
TypeScript で “radian” と “degree” を間違えないようにする

2019/02/05
Python3でGoogle Cloud ML Engineをローカルで動作する方法

2019/01/18
SIGGRAPH Asia 2018 参加レポート

2019/01/08
お正月だョ!ECMAScript Proposal全員集合!!

2019/01/08
カブクエンジニア開発合宿に行ってきました 2018秋

2018/12/25
OpenAPI 3 ファーストな Web アプリケーション開発(環境編)

2018/12/23
いまMLKitカスタムモデル(TF Lite)は使えるのか

2018/12/21
[IoT] Docker on JetsonでMQTTを使ってCloud IoT Coreと通信する

2018/12/11
TypeScriptで実現する型安全な多言語対応(Angularを例に)

2018/12/05
GASでCompute Engineの時間に応じた自動停止/起動ツールを作成する 〜GASで簡単に好きなGoogle APIを叩く方法〜

2018/12/02
single quotes な Black を vendoring して packaging

2018/11/14
3次元データに2次元データの深層学習の技術(Inception V3, ResNet)を適用

2018/11/04
Node Knockout 2018 に参戦しました

2018/10/24
SIGGRAPH 2018参加レポート-後編(VR/AR)

2018/10/11
Angular 4アプリケーションをAngular 6に移行する

2018/10/05
SIGGRAPH 2018参加レポート-特別編(VR@50)

2018/10/03
Three.jsでVRしたい

2018/10/02
SIGGRAPH 2018参加レポート-前編

2018/09/27
ズーム可能なSVGを実装する方法の解説

2018/09/25
Kerasを用いた複数入力モデル精度向上のためのTips

2018/09/21
競技プログラミングの勉強会を開催している話

2018/09/19
Ladder Netwoksによる半教師あり学習

2018/08/10
「Maker Faire Tokyo 2018」に出展しました

2018/08/02
Kerasを用いた複数時系列データを1つの深層学習モデルで学習させる方法

2018/07/26
Apollo GraphQLでWebサービスを開発してわかったこと

2018/07/19
【深層学習】時系列データに対する1次元畳み込み層の出力を可視化

2018/07/11
きたない requirements.txt から Pipenv への移行

2018/06/26
CSS Houdiniを味見する

2018/06/25
不確実性を考慮した時系列データ予測

2018/06/20
Google Colaboratory を自分のマシンで走らせる

2018/06/18
Go言語でWebAssembly

2018/06/15
カブクエンジニア開発合宿に行ってきました 2018春

2018/06/08
2018 年の tree shaking

2018/06/07
隠れマルコフモデル 入門

2018/05/30
DASKによる探索的データ分析(EDA)

2018/05/10
TensorFlowをソースからビルドする方法とその効果

2018/04/23
EGLとOpenGLを使用するコードのビルド方法〜libGLからlibOpenGLへ

2018/04/23
技術書典4にサークル参加してきました

2018/04/13
Python で Cura をバッチ実行するためには

2018/04/04
ARCoreで3Dプリント風エフェクトを実現する〜呪文による積層造形映像制作の舞台裏〜

2018/04/02
深層学習を用いた時系列データにおける異常検知

2018/04/01
音声ユーザーインターフェースを用いた新方式積層造形装置の提案

2018/03/31
Container builderでコンテナイメージをBuildしてSlackで結果を受け取る開発スタイルが捗る

2018/03/23
ngUpgrade を使って AngularJS から Angular に移行

2018/03/14
Three.jsのパフォーマンスTips

2018/02/14
C++17の新機能を試す〜その1「3次元版hypot」

2018/01/17
時系列データにおける異常検知

2018/01/11
異常検知の基礎

2018/01/09
three.ar.jsを使ったスマホAR入門

2017/12/17
Python OpenAPIライブラリ bravado-core の発展的な使い方

2017/12/15
WebAssembly(wat)を手書きする

2017/12/14
AngularJS を Angular に移行: ng-annotate 相当の機能を TypeScrpt ファイルに適用

2017/12/08
Android Thingsで4足ロボットを作る ~ Android ThingsとPCA9685でサーボ制御)

2017/12/06
Raspberry PIとDialogflow & Google Cloud Platformを利用した、3Dプリンターボット(仮)の開発 (概要編)

2017/11/20
カブクエンジニア開発合宿に行ってきました 2017秋

2017/10/19
Android Thingsを使って3Dプリント戦車を作ろう ① ハードウェア準備編

2017/10/13
第2回 魁!! GPUクラスタ on GKE ~PodからGPUを使う編~

2017/10/05
第1回 魁!! GPUクラスタ on GKE ~GPUクラスタ構築編~

2017/09/13
「Maker Faire Tokyo 2017」に出展しました。

2017/09/11
PyConJP2017に参加しました

2017/09/08
bravado-coreによるOpenAPIを利用したPythonアプリケーション開発

2017/08/23
OpenAPIのご紹介

2017/08/18
EuroPython2017で2名登壇しました。

2017/07/26
3DプリンターでLチカ

2017/07/03
Three.js r86で何が変わったのか

2017/06/21
3次元データへの深層学習の適用

2017/06/01
カブクエンジニア開発合宿に行ってきました 2017春

2017/05/08
Three.js r85で何が変わったのか

2017/04/10
GCPのGPUインスタンスでレンダリングを高速化

2017/02/07
Three.js r84で何が変わったのか

2017/01/27
Google App EngineのFlexible EnvironmentにTmpfsを導入する

2016/12/21
Three.js r83で何が変わったのか

2016/12/02
Three.jsでのクリッピング平面の利用

2016/11/08
Three.js r82で何が変わったのか

2016/12/17
SIGGRAPH 2016 レポート

2016/11/02
カブクエンジニア開発合宿に行ってきました 2016秋

2016/10/28
PyConJP2016 行きました

2016/10/17
EuroPython2016で登壇しました

2016/10/13
Angular 2.0.0ファイナルへのアップグレード

2016/10/04
Three.js r81で何が変わったのか

2016/09/14
カブクのエンジニアインターンシッププログラムについての詩

2016/09/05
カブクのエンジニアインターンとして3ヶ月でやった事 〜高橋知成の場合〜

2016/08/30
Three.js r80で何が変わったのか

2016/07/15
Three.js r79で何が変わったのか

2016/06/02
Vulkanを試してみた

2016/05/20
MakerGoの作り方

2016/05/08
TensorFlow on DockerでGPUを使えるようにする方法

2016/04/27
Blenderの3DデータをMinecraftに送りこむ

2016/04/20
Tensorflowを使ったDeep LearningにおけるGPU性能調査

→
←

関連職種

Recruit

→
←

お客様のご要望に「Kabuku」はお応えいたします。
ぜひお気軽にご相談ください。

お電話でも受け付けております
03-6380-2750
営業時間:09:30~18:00
※土日祝は除く