今大注目のesbuildに関してさっくり調べて見ました。
ゆるめの記事です。
2021/12/03追記: esbuildを実用した経験を記事にしましたので、合わせてご参考ください。
=> esbuild + React(TS) で実現する超軽量な開発環境
調査のきっかけ
私がフロントを本格的に勉強し始めたのは2016-2017年のAngular2が出たころです。モダンフロントエンド開発の選択肢が増え、
PWAが提唱されるなどWEBフロントエンドが一気にリッチ化すると同時にカオス化した時期だったと思います。
つまり、本当に面白い状況だったのですが最近になってその状況も落ち着いてきたと感じている方も少なくないと思います。
昔よくあった、React vs Angular vs Vue論争の熱は冷めたし、
SSRだけでペラペラだった(ドキュメントもダサかったw)Next.jsも2019~2020年の大規模機能拡張でフルスタックフレームワークとして堂々たるツールに成長し、今では画像配信サーバを内臓し、画像の最適化まで面倒をみてくれます。
この背景として、先立って機能拡充が進んでいたNuxt.jsからのいいとこ取りができたところも大きいと思います。
私はAnguler, Nuxt, Nextを業務で使用したことがありますが、どのライブラリやフレームワークを使っても大体のことはできるし、
お互いを比較した時に、突出した優位性などはほとんどないと思っています。(個人的にreactが書いていて一番しっくりくるという好き嫌いくらいの話です。)
この成熟したフロントエンド環境の最後の大課題といっていいのがコンパイラとバンドラのパフォーマンスといってもいいと思います!
2020年は上述の背景の中、SPA系のプロジェクトの注目度も依然高い状況ですが、新たにビルドツール系のstar数の急激な伸びが印象的です。
ビルドツールのトレンドランキング!こちらをご参考
順位 | 名前 | 増加数 |
---|---|---|
1 | esbuild | +16.6k |
2 | Rome | +14.2k |
3 | Vite | +14.1k |
4 | Snowpack | +10.1k |
5 | Webpack | +4.5k |
う〜ん、とはいえみなさん、ビルドツールってなんか地味な印象ありませんか?私はそうでした。
ビルドツールといえば、フロントオタクがwebpackをゴリゴリチューニングしたり、rollupなどの色々ツールがあるもいまいち根本的にビジネスメリットがあるわけでもないし、
下手に手を出すと技に溺れやすい世界だし(公式ドキュメントにシンプルに保ちなさい的なこと書いてあるよね!)、第一最適化はフレームワークに任せればいいじゃん!っと考えていました。
しかし、今回注目したいesbuildはまさにフロントエンドの次の時代を作ってくれる様な、「すごい」ツールであるとキラキラとした期待を寄せてしまいます。
esbuildとは
webpackなどに変わるフロントエンドのコード変換+バンドルツールで、なんとWebpackと比較して10-100倍速度が早いそうです。
これが本当だとすると、フロントの開発効率は根本的に変わってくると思います。
当然の話ですが、現在のWebpackを使った各フレームワークのビルドはめちゃめちゃ遅いです。
nextやnuxtなどフレームワークで初期プロジェクトを構築し、デザインフレームワークを一つでも挿そうものなら、既に数十秒のビルド時間を覚悟する必要があります。
開発効率の向上のためのdev serverは、バンドルサイズが非常に重くブラウザのパフォーマンスが非常に厳しいものがあります。
また、HMRも実装が複雑らしくうまく反映されないことがあったり、SSRと相性が悪かったりと有効なシチュエーションが限られている現状です。
特に本番系でしか出ないエラーに遭遇した際は本当に泣くしかありませんw 数分(数十分)かけてビルドして、修正して、もう一回ビルドという経験をされている方も少なくないと思います。
この様に中規模以上のフロントエンド開発経験者であれば、「ビルドの遅さ」との戦いは開発プロセスの根本的な課題であることが実感としてあり、解決可能であれば正にエポックメイキングで、夢の様な話だなぁと感じます。
(本当かなぁ?)
esbuildの調査に当たってはアーキテクチャーなども見てみましたが、the-super-tiny-compilerを読んだ程度では全く知識が足りずさっぱりでした、、w
そのせいか、開発は殆どEvan一人で進めている様です。
ちなみに、esbuildは2021/01時点でスター数が19kなので、本当に彗星のごとく現れたということが分かります。
さらにランキング3vite
はesbuildに依存しています。vueはビルドプロセスが独特なので、esbuildとは別のレイヤーで対応するべきとVueのEvanとesbuildのEvanがgit上で議論しています。
(kabukuさんの記事 より。)
4位のSnowpcakもesbuildに依存しています。
試してみた
create-next-appでプロジェクトを作成し、起動方法をreact-scriptからesbuildに変更してみました。
具体的にはプロジェクトのルートに下記のスクリプトを作成し、node build.js
を実行することでビルドします。
const watch = process.env.WATCH === 'true';
console.log( process.env.WATCH, watch );
require('esbuild').build({
entryPoints: ['src/index.tsx'],
bundle: true,
outfile: 'dist/out.js',
define: {
"process.env.NODE_ENV": '"development"'
},
loader: {
'.svg': 'dataurl'
},
minify: false,
sourcemap: true,
target: ['chrome70', 'firefox57', 'safari11', 'edge16'],
watch,
}).catch(() => process.exit(1))
webpackを使用したことがある方であれば、なかなかシンプルで分かりやすい設定だと思います。
これだけの設定でバンドルを生成してくれます。
実際に実行してみました。
time node build.js
> real 0m0.282s
これはっっ、本当に早い!
初期状態のプロジェクトでもdev-serverを立ち上げるまでに数秒は待たされる所なので、春の日差しの様な清々しさを感じます。
esbuildできないこと
esbuildはまだ実験段階のプロダクトであるということと、パフォーマンスのため出来るだけシンプルな機能群に絞っているということもあり、出来ないこともはっきりしています。
私が確認した限りですと、下記はesbuildで実行することが出来ません。
- es5への変換
- CSS Modules
- Code Splitting と import() による lazy loading
- next.jsでの利用 ( next-babel-loaderの置き換えが難しい )
- ポストCSSの利用
- HMR
HMRに関しては対応の優先順は相当低そうです。(議論 => https://github.com/evanw/esbuild/issues/97)
こちらの議論によると、HMRはそもそもビルドが遅く、検証バンドルが重いWebpack環境ではデザインの編集など一部のユースケースで有効なこともありますが、
esbuildはそもそもビルドが爆速なので需要がそこまで高くないのと、HMRは複雑で実装コストが非常に高いということがあげられる様です。
esbuildができること
差分ビルド
watch: true
をすることで差分ビルドを走らせることができます。無論超高速です。
Base Pathの変更、Path Aliasの使用
TypeScriptを使用しているとベースパスの変更、パスエイリアスを使いたいことがあります。
// tsconfig.json
{
"baseUrl": "./src/",
"paths": {
"@/*": ["./*"]
},
}
// index.tsx
import App from 'components/App';
// or
import App from '@/components/App';
これは、自動的にパスを解釈してくれる様です。webpackだと、resolve.aliasを調整しないといけなかったりするので便利ですね。
CSS import
よくプロジェクトのルートでcssをimportしたいことがあります。
import App from 'assets/styles/global.css';
この場合、index.tsxをバンドルすると、自動的にimportしているcssを全てまとめて、一つのcssファイルとして書き出してくれます。
今回のビルド設定だと、dist/out.css
が生成されます。
デザインライブラリの利用
material-uiと、私が最近贔屓にしているantdを読み込んでみましたが普通に使えました。
antdはcssを別途読み込む必要があり、公式ページのgetting startedではcssファイル内にimportを記述する方法が記載されていますが、
うまく行かないので、index.tsxでimportしてあげる様にすると動きました。
本当に早いのか
antdのボタンを一つ配置した状態でビルドをおこなってみました。
import { Button } from 'antd';
まずはWebpack選手。下記の通りwebpcak.configを書いて実行しています。
計測には time と speed-measure-webpack-plugin
を利用しました。
const path = require( 'path' );
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
mode: 'development',
entry: path.resolve( __dirname, 'src/index.tsx' ),
output: {
path: path.resolve( __dirname, 'dist' ),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(jsx|tsx|ts|js)$/,
exclude: /node_modules/,
loader: 'ts-loader'
},
{
test: /\.(svg)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
{
test: /\.css$/,
loaders: ['style-loader', 'css-loader'],
},
]
},
resolve: {
extensions: ['.js', '.jsx', '.tsx', '.ts'],
alias: {
'@': path.resolve( __dirname, './src' ),
},
modules: [
path.resolve( __dirname, './src' ),
path.resolve( __dirname, './node_modules' ),
]
},
plugins: [],
});
実行
time npx webpack --config webpack.config.js
結果
SMP ⏱
General output time took 8.73 secs
SMP ⏱ Loaders
ts-loader took 5.24 secs
module count = 4
modules with no loaders took 2.99 secs
module count = 1030
css-loader took 0.449 secs
module count = 3
url-loader took 0.066 secs
module count = 1
style-loader, and
css-loader took 0.037 secs
module count = 3
real 0m10.665s
10秒ちょっとなので、そんなもんかなぁという感じですね。
続いてesbuildです。
実行!
time node build.js
結果
real 0m0.608s
再び、額の辺りに微かな清涼感を感じました。
esbuildの方が約18倍高速です
さらに esbuild-loader
なるものがあって、
webpcakのbabel-laoderやts-loaderを置き換える様にして使用することできます。
esbuild-loaderではcss importの解決ができない様で、ビルドを通すことができなかったのですが途中までのログはこんな感じです。
SMP ⏱
General output time took 4.055 secs
SMP ⏱ Plugins
ESBuildPlugin took 0.01 secs
SMP ⏱ Loaders
esbuild-loader took 3.67 secs
module count = 1033
modules with no loaders took 0.019 secs
module count = 3
あれっ?esbuild-loaderの所で3.7秒も使ってますね。実際にesbuild-loaderのissueをみても、
倍程度にしか高速化していないので、こんなものなのかもしれません。(それでも最高ではありますが。)
まとめるとこんな結果になりました。
ビルド方法 | 時間(約) | ファイルサイズ |
---|---|---|
Webpack(dev) | 10.6秒 | 6.92Mb |
Webpcak(prd) | 18.2秒 | 2.16Mb |
esbuild(dev) | 0.6秒 | 1.1Mb + 710kb |
esbuild(prd) | 0.6秒 | 420kb + 720kb |
esbuildの場合、jsとcssは別バンドルになるので、ファイルサイズはjs, cssの順で記載しています。
これを見ると、本番ビルドではなんと30倍も高速であることが分かります!またファイルサイズもいずれもWebpackより小さくなっている様で、
開発時のブラウザの負荷も低くなると思われます。
実践投入に関して
まだesbuildは実験段階に位置付けられていますが、vite, snowpackなどすでに多くの新興ツールが依存している状態になり、今後の発展は非常に期待ができると思われます。
しかしながら、やっぱり本番運用まだ怖い場合は、ABEMAさんの記事でも提案されている様に、
検証系のビルド方法に一手間加えて、DXの爆上げという目的が使いやすいと思います。
将来的にエビデンスなどが揃いstableなツールとなれば、超高速CI/CDの実現など夢は広がります。
周辺ツールに関して(2021/9/26追記)
esbuildそのものを実戦投入しなくても、部分的にesbuildの力を活用できるツールがあるのでご紹介します。
esbuild-register
TSファイルを直接実行したい時、従来であれば ts-node
を利用することが多かったと思いますが、実行の度に変換に時間がかかっていました。
esbuild-registerを利用すると、従来より高速にTSファイルを実行することができます。
node -r esbuild-register hoge.ts
実際結構早いので、ぜひお試しください。
esbuild-jest
JestでTypeScriptのspecを実行する場合はtransformに ts-jest
を指定するのが一般的かと思いますが、こちらも実行の度に変換に時間がかかります。
そこで、 ts-jest
をそのまま esbuild-jest
に置き換えることで、esbuildによるtransformに置き換えることができます。
module.exports = {
// ...
transform: {
'^.+\\.(ts|tsx)$': 'esbuild-jest',
},
};
私の試した範囲では、感覚的に2/3くらいの実行速度に改善しました。また、React周りのテストでエラーが発生したので、バックエンドコードのテストに利用しています。
まとめ
esbuildはやっぱり早かった!SPAページを作成する際はあえて、nextを断念してでも導入を検討したいと思いました。
また、DenoやRomeなどのエコシステムのネクストジェネレーション?的なツールがどんどん出てきていますので、
近いうちに調査、比較を行ってみたいと思います。
長文になりましたが、最後まで読んでくださった方は、ありがとうございました。