webpack、babel、esbuildをちゃんと理解したい。
まえがき
今まで仕事とプライベート含めて下記のような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と同じで、後ろに定義されたものから適用される。
{
"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を使った事前バンドルを行うため。
Discussion