話題のesbuildをさっくりと調査してみた

[ブログからの転載です。](https://blog.hedrall.work/posts/esbuild-2021-02-14)

今大注目のesbuildに関してさっくり調べて見ました。
ゆるめの記事です。

> 2021/12/03追記: esbuildを実用した経験を記事にしましたので、合わせてご参考ください。
> => [esbuild + React(TS) で実現する超軽量な開発環境](https://qiita.com/hedrall/items/43b6d705ab8dc78e6321)

# 調査のきっかけ

私がフロントを本格的に勉強し始めたのは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数の急激な伸びが印象的です。

ビルドツールのトレンドランキング！[こちらをご参考](https://qiita.com/rana_kualu/items/e8a0f8f5589ff082539d#%E3%83%93%E3%83%AB%E3%83%89%E3%83%84%E3%83%BC%E3%83%AB)

| 順位 | 名前 | 増加数 |
| :-- | :-: | :-: |
| 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の調査に当たっては[アーキテクチャー](https://github.com/evanw/esbuild/blob/master/docs/architecture.md)なども見てみましたが、[the-super-tiny-compiler](https://github.com/jamiebuilds/the-super-tiny-compiler)を読んだ程度では全く知識が足りずさっぱりでした、、w
そのせいか、開発は殆どEvan一人で進めている様です。

ちなみに、esbuildは2021/01時点でスター数が19kなので、本当に彗星のごとく現れたということが分かります。
さらにランキング3`vite`はesbuildに依存しています。vueはビルドプロセスが独特なので、esbuildとは別のレイヤーで対応するべきとVueのEvanとesbuildのEvanが[git上](https://github.com/evanw/esbuild/issues/75#issuecomment-623797028)で議論しています。
([kabukuさんの記事](https://www.kabuku.co.jp/developers/ultrafast-tsx-build-tool-esbuild) より。) 
4位のSnowpcakもesbuildに依存しています。

# 試してみた

create-next-appでプロジェクトを作成し、起動方法をreact-scriptからesbuildに変更してみました。
具体的にはプロジェクトのルートに下記のスクリプトを作成し、`node build.js`を実行することでビルドします。

```typescript: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を使用したことがある方であれば、なかなかシンプルで分かりやすい設定だと思います。
これだけの設定でバンドルを生成してくれます。

実際に実行してみました。

```bash
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の置き換えが難しい )
  - 議論 => https://github.com/vercel/next.js/discussions/16152
- ポストCSSの利用
- HMR

[AMEBAさんの記事もご参考](https://developers.cyberagent.co.jp/blog/archives/26336/)

HMRに関しては対応の優先順は相当低そうです。(議論 => https://github.com/evanw/esbuild/issues/97)<br/>
こちらの議論によると、HMRはそもそもビルドが遅く、検証バンドルが重いWebpack環境ではデザインの編集など一部のユースケースで有効なこともありますが、
esbuildはそもそもビルドが爆速なので需要がそこまで高くないのと、HMRは複雑で実装コストが非常に高いということがあげられる様です。

# esbuildができること

## 差分ビルド

`watch: true`をすることで差分ビルドを走らせることができます。無論超高速です。

## Base Pathの変更、Path Aliasの使用

TypeScriptを使用しているとベースパスの変更、パスエイリアスを使いたいことがあります。

```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したいことがあります。

```typescript:index.ts
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のボタンを一つ配置した状態でビルドをおこなってみました。

```typescript
import { Button } from 'antd';
```

まずはWebpack選手。下記の通りwebpcak.configを書いて実行しています。
計測には time と `speed-measure-webpack-plugin`を利用しました。

```typescript:webpack.config.js
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: [],
});
```

実行

```bash
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です。

実行！

```bash
time node build.js
```

結果

```
real	0m0.608s
```

再び、額の辺りに微かな清涼感を感じました。
esbuildの方が**約18倍高速です**

さらに [`esbuild-loader`](https://github.com/privatenumber/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](https://github.com/privatenumber/esbuild-loader/issues/13)をみても、
倍程度にしか高速化していないので、こんなものなのかもしれません。(それでも最高ではありますが。)

まとめるとこんな結果になりました。

| ビルド方法 | 時間(約) | ファイルサイズ |
| :-- | :-: | :-: |
| 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などすでに多くの新興ツールが依存している状態になり、今後の発展は非常に期待ができると思われます。<br/>
しかしながら、やっぱり本番運用まだ怖い場合は、[ABEMAさんの記事](https://developers.cyberagent.co.jp/blog/archives/26336/)でも提案されている様に、
検証系のビルド方法に一手間加えて、DXの爆上げという目的が使いやすいと思います。

将来的にエビデンスなどが揃いstableなツールとなれば、超高速CI/CDの実現など夢は広がります。

# 周辺ツールに関して(2021/9/26追記)

esbuildそのものを実戦投入しなくても、部分的にesbuildの力を活用できるツールがあるのでご紹介します。

## [esbuild-register](https://github.com/egoist/esbuild-register)

TSファイルを直接実行したい時、従来であれば `ts-node` を利用することが多かったと思いますが、実行の度に変換に時間がかかっていました。
esbuild-registerを利用すると、従来より高速にTSファイルを実行することができます。

```bash
node -r esbuild-register hoge.ts
```

実際結構早いので、ぜひお試しください。

## [esbuild-jest](https://github.com/aelbore/esbuild-jest)

JestでTypeScriptのspecを実行する場合はtransformに `ts-jest` を指定するのが一般的かと思いますが、こちらも実行の度に変換に時間がかかります。
そこで、 `ts-jest` をそのまま `esbuild-jest` に置き換えることで、esbuildによるtransformに置き換えることができます。

```js
module.exports = {
  // ...
  transform: {
    '^.+\\.(ts|tsx)$': 'esbuild-jest',
  },
};
```

私の試した範囲では、感覚的に2/3くらいの実行速度に改善しました。また、React周りのテストでエラーが発生したので、バックエンドコードのテストに利用しています。

# まとめ

esbuildはやっぱり早かった！SPAページを作成する際はあえて、nextを断念してでも導入を検討したいと思いました。

また、DenoやRomeなどのエコシステムのネクストジェネレーション？的なツールがどんどん出てきていますので、
近いうちに調査、比較を行ってみたいと思います。

長文になりましたが、最後まで読んでくださった方は、ありがとうございました。