webpack、babel、esbuildをちゃんと理解したい。

2023/02/25に公開

まえがき

今まで仕事とプライベート含めて下記のようなJSフレームワークを利用してきた。
 ・Create React App
 ・Nuxt.js
 ・Vite
 ・SvelteKit

✅これらのJSフレームワークは、ビルドするための必要な設定がFW内部で一通り行われている
  ため、FWを使えばすぐに開発をしてビルドできる
ものである。

このように便利なものであるから、FW内部について正直ちゃんと理解してなかったし、実業務でここらへんをがっつり触ることがなかったから必要もなかった。

少しでも「俺は知ってるぜ(´_ゝ`)」感を味わいたいので、本稿は下記の疑問を解消するための勉強記事です。

👉FW内部で誰が何のビルド/変換処理を担っているのか → webpack/babelとは?
👉ここ2,3年で台頭してきたesbuildは何がいいのか。

Special Thanks

👉@babel/preset-env (Babel 7)をハンズオンでちゃんと理解する
→ babelの役割(ES5-friendlyコード変換)がよくわかる記事。
「IEでPromiseが動くわけ無いだろ」らへんがおもろかったw

👉webpackとBabelの基本を理解する(2) ―Babel編―
→ babelの基本的な使い方が分かった。
@babel/preset-envが何者か、よく理解できた。ESLintの共有設定に似てる。

👉@babel/preset-reactがどういう構成になっているのか気になったので調べたメモ
@babel/preset-reactの中身/役割がよく理解できた。

👉@babel/preset-typescriptを使ってTypeScriptを変換する
→ TS→JS変換はtscだけでなくbabel(@babel/preset-typescript→@babel/plugin-transform-typescript)で出来ることが分かった。ただし「型チェック」「型定義ファイルの作成」はtscしかできないものだから、引き続きtscを使う必要があるということ。

👉webpackとBabelの基本を理解する(3) ―webpackとBabel編―

webpack

webpackでバンドルする理由

✅少し前までのブラウザはES5対応であったため、ES6導入のimport/export構文(ESModules)を使うことができなかった。
 ↓
👉import/export構文を使わない形「1つのmain.jsにまとめる」「index.htmlでscript文を羅列する」のようにすることで、ES5でも動くような形に変換(バンドル)していた。

🔴しかし最近の主要ブラウザは<script type="module">とすることで、ES6導入のimport/export構文を利用できるようになっている。

ES6導入のimport/export構文をブラウザ上で使えるようになった今、必ずしもwebpackで1つのmain.jsにまとめる必要はない。
🔴Vite(esbuild)ではESModulesを利用する形でバンドルするようになっている👍

Viteで「いや、それでもES5互換のあるようにバンドルしたい!」という場合は@vitejs/plugin-legacyプラグインを利用すればいいが、俺らが知る主要ブラウザは全てESModulesに対応しているので、その必要もない?

Vite's default browser support baseline is Native ESM, native ESM dynamic import, and import.meta.

webpackはバンドルする前にプラグイン(Loader)経由で色々やる。

1つのファイルにまとめる(バンドルする)ことが、webpackの主な目的。
webpackはバンドルする前にバンドル対象に対して変換処理を適用できる仕組み(Loader)を提供している。

バンドルする前に適用したい変換処理とは何か?
 👉TypeScript → JavaScript へ変換。
 👉JSX(.jsx/.tsx) → JavaScript へ変換。
 👉ES6+コード(Promise/Template Literal) → ES5-friendlyコード へ変換。
 👉Polyfill追加

↑にあげた変換処理はすべてbabelが担う変換処理である。
つまり✅バンドルする前にバベりたい✅ということ。

※Polyfill:ある機能に対応していないブラウザに対して同じ互換実装を提供するライブラリ

babel

babelとは

✅JavaScriptのコンパイラ/トランスパイラ。
✅主な目的はES6+コード(Promise/Template Literal)をES5-friendlyコードへの変換
✅上記に加えて、下記の変換処理も行うことが可能。
 👉TypeScript → JavaScript へ変換。
 👉JSX(.jsx/.tsx) → JavaScript へ変換。
 👉Polyfill追加

✅設定ファイル.babelrcに何も定義しない状態だと、何も変換されない。
 👉ESLintと同じで、デフォルト設定だと何も起きない。

✅プリセット(Babelが変換処理を行う際に利用するプラグインのコレクション)をpresetsに定義することで、すぐに変換内容の設定を適用することができる。
 👉ESLintと同じで、一通りまとまっているもの(Preset)を読み込むだけですぐ使える。
 👉ESLintと同じで、後ろに定義されたものから適用される。

.babelrc(typescript → react → env の順で変換処理が実行される)
{
  "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]
}

よく使うPreset

Preset名 役割
@babel/preset-env ✅指定バージョンのECMAScriptに互換性のある形に変換する。
✅デフォルトで「ES5」互換のJavaScriptに変換する。
@babel/preset-react React JSX構文をJavaScriptに変換する。
@babel/preset-typescript

@babel/preset-env

✅出力したいECMAScriptのバージョンを指定するためのプリセット。
✅サポートしたい範囲(ブラウザバージョン/ブラウザシェア/サポートの可否/etc)を指定する。
✅指定内容に応じて適切なバージョンのJSに変換されるようにしてくれる。
特に何も指定しない場合は、一律es5の構文に変換される。

@babel/preset-react

✅React JSXをJavaScriptに変換するためのプリセット。
✅このプリセットは下記3つのプラグインを常に追加する。

プラグイン名 役割
@babel/plugin-syntax-jsx JSX構文の解釈だけをしている。
@babel/plugin-transform-react-jsx JSX構文をJavaScriptに変換する。
@babel/plugin-transform-react-display-name displayNameをClassに追加する。

@babel/preset-typescript

✅TypeScript → JavaScriptに変換するプリセット。
✅このプリセットは@babel/plugin-transform-typescriptに依存している。

@babel/plugin-transform-typescript

✅トランスパイルのみ。
✅型チェックしないので、他のやつにやってもらう必要あり。

webpack,babel関連ライブラリの早見表

✅TypeScriptコンパイラ(tsc)は、Polyfill以外は一通りできる印象。
 👉Polyfillは@babel/preset-env × core-jsに任せる。

✅下記はそれぞれ役割をもっているから、お互い依存しているような感じ?
 fork-ts-checker-webpack-plugin「おれ、TypeScriptの型チェックやります。」
 @babel/preset-typescript「おれ、TS → JSに変換します。」
 @babel/preset-env「おれ、ES5変換とPolyfillやります。」
 @babel/preset-react「おれ、JSX構文変換します。」

型チェック TS→JS TSX/JSX→JS ES構文変換 Polyfill CRA利用
tsc 〇(jsx) 〇(target)
fork-ts-checker-webpack-plugin
@babel/preset-typescript
@babel/preset-env
@babel/preset-react

利用例

①webpack ✕ ts-loader ✕ babel-loader(@babel/preset-env)

ts-loader経由でTypeScriptコンパイラを中心に使う。

WHAT TO DO HOW TO DO
型チェック ts-loader(tsc)※tsconfig.json読む。
TS → JS ts-loader(tsc)※tsconfig.json読む。
TSX,JSX → JS ts-loader(tsc)※tsconfig.json読む。
ES6+ → ES5 ts-loader(tsc) でもできるし @babel/preset-envでも。
Polyfill差替 @babel/preset-env × core-js

②webpack ✕ babel-loader(色々)【CreateReactApp、Next.js】

✅TypeScriptコンパイラは基本使わない。
※型チェックのfork-ts-checker-webpack-pluginの内部で使われているかも?
✅基本babelのPresetを利用することで完結させる。

WHAT TO DO HOW TO DO
型チェック fork-ts-checker-webpack-plugin※tsconfig.json読む。
TS → JS @babel/preset-typescript※tsconfig.json読まない✕
TSX,JSX → JS @babel/preset-react
ES6+ → ES5 @babel/preset-env
Polyfill差替 @babel/preset-env × core-js

③esbuild × babel(@babel/preset-env)【Vite】

✅変換系はesbuildが担当する。
✅esbuildはTS型チェックしないので、TypeScriptコンパイラにお願いする。
✅ES5変換、Polyfillも、Viteのプラグイン経由でbabelさんにお願いする。

WHAT TO DO HOW TO DO
型チェック × esbuildは型チェックしない 〇tsc --noEmitする。
TS → JS 〇esbuildのTypeScript Loader
TSX,JSX → JS 〇esbuildのJSX Loader
ES6+ → ES5 × esbuildはES5変換未完全 〇viteのプラグイン"@vitejs/plugin-legacy"を使う。
Polyfill差替 〇viteのプラグイン"@vitejs/plugin-legacy"を使う。

babelの責務は"構文"=Promise等を差し替えるPolyfillはデフォルトでは行ってくれない。
👉@babel/preset-envでPoliyfillするやり方(デフォルトではPolyfillしない)

esbuild

✅esbuildは、各Content(JS/TS/JSX/JSON/etc)に対して変換処理を担うLoaderが用意されており、各Loaderはesbuildに「JSファイルはこんな感じで変換してくれ」と伝える感じ。

以下のLoaderがDefaultで動作する。

INPUT Loader What loader does
JavaScript JavaScript Loader ModernなJSの書き方に最適化/変換してくれる。
TypeScript TypeScript Loader TS->JSへ変換。型確認しないのでtsc --noEmitで確認。
JSX JSX Loader jsx,tsxを変換。
JSON JSON Loader ビルド時にJSONファイルをJavaScriptオブジェクトとして解決。
CSS CSS Loader CSSファイルをbundleできる。
Text Text Loader ビルド時にTextファイルを文字列として解決。

その他、以下の変換処理を行いたい場合は別途Plugin等の設定を行う。

変換処理 内容
ES6+ → ES5 × esbuildはES5変換未完全 〇viteのプラグイン"@vitejs/plugin-legacy"を使う。
Polyfill差替 〇viteのプラグイン"@vitejs/plugin-legacy"を使う。

@vitejs/plugin-legacyは、内部で@babel/preset-envを使ってES5構文変換、Polyfillをしている。

esbuild、なぜ速い?

△ ESModulesを利用するため、一部しかバンドルしないから。
〇 Go言語で書かれている=ネイティブコードであるから。
  従来のbundlerはJSで書かれているため、JavaScript VMが読み込むプロセスがあるが、
  esbuildはネイティブコードなのですぐに実行できる。
〇 並行処理で利用可能なCPUコアを使った高速処理。
〇開発時のバンドルのみesbuildを使った事前バンドルを行うため。

開発時/本番ビルド時(webpack/vite) のコンパイル/バンドルの流れ

CRA(React/webpack) × JavaScript × ブラウザ 【開発時/本番ビルド時】

CRA(React/webpack) × TypeScript × ブラウザ 【開発時/本番ビルド時】

Vite(React/rollup) × JavaScript/TypeScript × ブラウザ 【開発時】

Vite(React/rollup) × JavaScript/TypeScript × ブラウザ 【本番ビルド時】

Discussion