- 技術 💻
webpackとは?使い方とViteとの違い|3つのビルドを実測比較
結論
webpackは、JavaScript・CSS・画像などたくさんのファイルを、依存関係(どのファイルがどれを読み込むか)をたどって少数のファイルにまとめる「バンドラー」です。Viteは開発中はまとめずに配り、本番だけまとめる点が違います。
この記事でわかること
- webpackの使い方(インストール→設定→ビルド→開発サーバー)
- webpack・Vite・esbuildで同じアプリをビルドした時間とサイズの実測
- Module not found・You may need an appropriate loader などのエラー原文と直し方
webpackは、入口のファイル(エントリー)から import をたどって「依存関係のグラフ」を作り、ブラウザに配る形のファイル(バンドル)に書き出します。
flowchart TD
E[index.js] --> A[greet.js<br>lodash-es<br>style.css]
E -.->|あとで| H[chart.js]
A --> B[(main.js)]
H --> S[(別ファイル)]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| webpackとは | JSやCSSを依存関係どおりに束ねるバンドラー | webpackとは |
| webpackの使い方 | npm install -D webpack webpack-cli → npx webpack |
webpackの使い方 |
| webpack.config.jsの書き方 | entry・output・mode・module.rules・plugins の5つが骨組み | webpack.config.jsの書き方 |
| loaderとpluginの違い | loaderはファイル1つずつの変換、pluginはビルド全体への処理 | loaderとpluginの使い方 |
| webpack vite 違い | Viteは開発中にまとめない。手元の本番ビルドは約5倍速かった | webpackとViteの違い |
| css-loaderは今も要るか | 5.109以降はローダーなしでCSSを扱える。5.110で非推奨に | 5.109以降の書き方 |
| Module not found | ファイル名・大文字小文字・パッケージの入れ忘れを確かめる | よくあるエラー |
| You may need an appropriate loader | JSX・画像・Sass などに対応する設定が無い | よくあるエラー |
確認環境:Ubuntu 24.04.5 LTS(2 vCPU・メモリ 8GB)、Node.js 22.23.4・npm 10.9.9 の上で、作業用のフォルダーに npm で webpack 5.112.2・webpack-cli 7.2.3・webpack-dev-server 6.1.0・html-webpack-plugin 5.6.8・css-loader 7.1.5・style-loader 4.0.0・mini-css-extract-plugin 2.10.2・babel-loader 10.1.1(@babel/core 8.0.9)・terser-webpack-plugin 5.6.1、比較用に Vite 8.3.5 と esbuild 0.28.2 を入れました。同じ小さなアプリ(lodash-es と dayjs を使う4ファイル)と、スクリプトで作った1,000モジュールのアプリを3つの道具でビルドし、時間(5回の中央値)・出力のサイズ・ファイル構成を測りました。開発サーバーは 127.0.0.1 の自分のポートで起動し、curl で応答を確かめています。ブラウザでの表示は確かめていません。webpack 5.109〜5.112 の変更点は webpack 公式ブログと公式ドキュメント、手元の webpack のソースコード(lib/config/defaults.js)で、Vite の仕組みは Vite 公式ドキュメントで確かめました。Windows・Mac での操作は扱っていません。図は mermaid で描画を確かめています(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「webpackとは?使い方とViteとの違い|3つのビルドを実測比較」
webpackとは(バンドラーの役割とバンドルが要る理由)
この節では、webpackが何をする道具なのかと、なぜWeb開発でバンドラーが使われるのかが分かります。
webpackは、JavaScriptアプリのための「静的モジュールバンドラー」です。 公式ドキュメントは、webpackが1つ以上のエントリーから依存関係のグラフを作り、必要なモジュールを1つ以上のバンドル(配信用の静的ファイル)にまとめる、と説明しています。
scriptタグを並べる時代からバンドラーへ
バンドラーが生まれた理由は、JavaScriptを複数のファイルに分けたときの困りごとです。
- scriptタグを並べる:
<script src="tax.js">と<script src="price.js">を書く順番を間違えると動かない。関数が全部グローバルに出るので名前がぶつかる。 - モジュール(import / export):ファイルごとに必要なものを
importし、依存関係がコードに書かれる。いまのブラウザは<script type="module">でそのまま読める。 - バンドラー:モジュールのまま配ると、ファイルの数だけ通信が起き、npm のパッケージ(
import dayjs from 'dayjs'のような書き方)はブラウザが場所を解決できない。バンドラーはこれを事前に解決し、少数のファイルにまとめる。
手元の小さなアプリでも、Vite が本番ビルドで変換したモジュールは lodash-es などを含めて649個ありました。
webpackがまとめてくれるもの
JavaScript
ESM(import)も CommonJS(require)も解決し、1つのファイルにまとめる。使っていない export は本番で消す。
tree shaking
CSS
JS から import './style.css' と書いた CSS を、別ファイルに書き出すか、style タグとして差し込む。
5.109以降は標準で扱える
画像・フォント
import した画像をハッシュ付きの名前で出力し、URL を JS に渡す。
asset modules
分割と圧縮
あとで使うコードを別ファイルにし、本番では空白や変数名を縮めて小さくする。
code splitting・minify
webpackが出力したコードは、ES5 に対応したブラウザで動きます(公式。IE8 以前は対象外)。import() を使うときは Promise が要り、以前のブラウザに対応するにはポリフィルを先に読み込みます。import / export の基本は、関連記事「JavaScript import/export入門|ESMとCommonJSの違い」で扱っています。
webpackの使い方:インストールと最初のビルド
この節では、空のフォルダーから webpack を入れて、最初のバンドルを作るまでの手順が分かります。
webpack 本体と、コマンドを動かす webpack-cli の2つを、プロジェクトの開発用の依存(-D)として入れます。 グローバルに入れる(-g)と、プロジェクトごとに版をそろえられないため、公式の手順もプロジェクトに入れています。
Node.js を用意する
webpack 5 本体は Node.js 10.13.0 以上ですが、webpack-cli 7.2.3 は 20.9.0 以上、webpack-dev-server 6 は 22.15.0 以上を求めます(各 package.json の engines)。Node.js 22 の LTS 以降がそろえやすい版です。
プロジェクトを作る
mkdir webpack-demo && cd webpack-demo のあと npm init -y で package.json を作る。
webpack と webpack-cli を入れる
npm install -D webpack webpack-cli。手元では100個のパッケージが7秒で入った。
src/index.js を書く
設定ファイルが無いときの入口は ./src/index.js、出力は ./dist/main.js です(公式の既定値)。
ビルドする
npx webpack で dist/main.js ができる。package.json の scripts に "build": "webpack" を足せば npm run build で動く。
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install -D webpack webpack-cli
mkdir src
echo "console.log('Hello webpack')" > src/index.js
npx webpack
node dist/main.js
手元で入った版です。
$ npx webpack --version
webpack: ^5.112.2 → 5.112.2
webpack-cli: ^7.2.3 → 7.2.3
公式の Getting Started には、雛形を作る npx create-webpack-app webpack-demo もあります。中身を理解するなら、手で1つずつ足していくほうが分かりやすいです。npm のコマンドと package.json の scripts は、関連記事「npm コマンド 一覧|package.json 書き方と実行結果・npm 12対応」で扱っています。
ビルドの出力の読み方
このあとの節で作るアプリ(CSS・lodash-es・dayjs・遅延読み込みのグラフ部品)を本番モードでビルドした出力です。
$ npx webpack --mode production
assets by path *.js 12.1 KiB
asset main.144ea5b9.js 11.9 KiB [emitted] [immutable] [minimized] (name: main) 1 related asset
asset 689.170c009a.js 263 bytes [emitted] [immutable] [minimized] 1 related asset
asset index.html 278 bytes [emitted] [minimized]
asset main.b6af5c4a.css 137 bytes [emitted] [immutable] (name: main) 1 related asset
Entrypoint main 12 KiB (51.4 KiB) = main.b6af5c4a.css 137 bytes main.144ea5b9.js 11.9 KiB 2 auxiliary assets
orphan modules 34.3 KiB (javascript) 883 bytes (runtime) [orphan] 24 modules
runtime modules 6.77 KiB 11 modules
cacheable modules 21.6 KiB (javascript) 91 bytes (css/mini-extract)
modules by path ./src/*.js 14.6 KiB
./src/index.js + 15 modules 14.5 KiB [built] [code generated]
./src/chart.js 150 bytes [built] [code generated]
./node_modules/dayjs/dayjs.min.js 6.99 KiB [built] [code generated]
css ./node_modules/css-loader/dist/cjs.js!./src/style.css 91 bytes [built] [code generated]
webpack 5.112.2 compiled successfully in 1459 ms
| 表示 | 意味 |
|---|---|
asset main.144ea5b9.js 11.9 KiB |
書き出したファイルと大きさ。144ea5b9 は中身から作ったハッシュ |
[minimized] |
圧縮(空白・変数名の短縮)済み |
[immutable] |
ハッシュ付きなので、長くキャッシュしてよいファイル |
1 related asset |
対応するソースマップ(.map)がある |
./src/index.js + 15 modules |
16個のモジュールを1つの関数にまとめた(モジュールの連結) |
orphan modules |
連結されて単独では残らなかったモジュール |
webpack.config.jsの書き方(entry・output・mode)
この節では、設定ファイル webpack.config.js の骨組みと、各項目が何を決めるのかが分かります。
設定ファイルは「どこから読み(entry)」「どこへ書き(output)」「どう変換し(module.rules)」「全体に何をするか(plugins)」「どのモードか(mode)」を書くファイルです。 手元で使った設定をそのまま載せます。
// webpack.config.js(CommonJS で書いた例)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = (env, argv) => {
const isProd = argv.mode === 'production';
return {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProd ? '[name].[contenthash:8].js' : '[name].js',
clean: true, // ビルドの前に dist を空にする
},
module: {
rules: [
{ test: /\.css$/i, use: [isProd ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader'] },
{ test: /\.m?js$/, exclude: /node_modules/, use: 'babel-loader' },
],
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
...(isProd ? [new MiniCssExtractPlugin({ filename: '[name].[contenthash:8].css' })] : []),
],
devtool: isProd ? 'source-map' : 'eval-cheap-module-source-map',
devServer: { host: '127.0.0.1', port: 47701 },
};
};
| 項目 | 決めること | 既定値 |
|---|---|---|
| entry | 依存関係をたどり始めるファイル。複数書ける | ./src/index.js |
| output.path・filename | 書き出す場所と名前。[name]・[contenthash] が使える |
./dist/main.js |
| module.rules | どの拡張子にどの loader を使うか | なし |
| plugins | HTML の生成・CSS の書き出しなど全体への処理 | なし |
| mode | development・production・none | production(警告が出る) |
| devtool | ソースマップの種類 | mode による |
- 設定を 関数 で書くと、2つ目の引数(argv)で
--modeの値を受け取れます。上の例は、本番だけ CSS を別ファイルにし、ファイル名にハッシュを付けています。 - package.json に
"type": "module"があると、webpack.config.js は ES モジュールとして読まれます。そのときはimport path from 'node:path'とexport defaultで書くか、ファイル名を webpack.config.cjs にします(書き間違えたときのエラーは後の節)。 - 設定ファイルの場所を変えるときは
npx webpack -c 別の設定.js(--configの短縮)です。
loaderとpluginの使い方(CSS・Babel・画像・HTML)
この節では、loader と plugin の違いと、よく使うものを入れたときの動きが分かります。
loader は「1つのファイルを、webpack が読める JavaScript のモジュールに変換する係」、plugin は「ビルド全体に対して、HTML を作る・CSS を書き出す・圧縮するといった仕事をする係」です。
loader(module.rules に書く)
test で拡張子を決め、use で loader を指定
use の配列は右(下)から順に動く
例:css-loader・babel-loader・ts-loader
plugin(plugins に new で書く)
ビルドの途中のあちこちに処理を差し込む
出力そのものを増やせる(index.html など)
例:HtmlWebpackPlugin・MiniCssExtractPlugin
| 入れたもの | 種類 | 手元での結果 |
|---|---|---|
| css-loader+style-loader | loader | CSS を JS の中に入れ、実行時に style タグで差し込む(開発向き) |
| MiniCssExtractPlugin | loader+plugin | main.b6af5c4a.css(137バイト)を別ファイルで出力 |
| babel-loader+@babel/preset-env | loader | targets に合わせて構文を変換。大きいアプリではビルドが約1.4秒遅くなった |
| HtmlWebpackPlugin | plugin | index.html に script と link をハッシュ付きの名前で差し込んだ |
type: 'asset/resource' |
組み込み | logo.png を 6153b9651180a6cf2c5a.png として出力(loader 不要) |
HtmlWebpackPlugin が作った index.html です。ハッシュの付いたファイル名を自動で書き込むので、手で書き換えずに済みます。
<!doctype html><html lang=ja><head><meta charset=utf-8><title>webpack demo</title><script defer src=main.144ea5b9.js></script><link href=main.b6af5c4a.css rel=stylesheet></head><body><p id=app></p><input id=q placeholder=検索> <button id=chart>グラフ</button></body></html>
画像は loader を入れなくても、webpack 5 の組み込みの asset modules で扱えます。
module: {
rules: [
{ test: /\.png$/i, type: 'asset/resource' }, // ファイルとして出力し URL を返す
// 'asset/inline' なら data: URL、'asset' なら大きさで自動的に切り替え
],
},
asset main.js 686 bytes [emitted] [minimized] (name: main)
asset 6153b9651180a6cf2c5a.png 12 bytes [emitted] [immutable] [from: logo.png] (auxiliary name: main)
loader の並び順
use: ['css-loader', 'style-loader']。右から動くので、style-loader の結果を css-loader が読むことになり、正しく動かないuse: ['style-loader', 'css-loader']。先に css-loader が CSS を読み、その結果を style-loader が差し込む配列は「最後に動くもの」を先頭に書く
webpack 5.109以降の書き方:ローダーなしでCSS・HTML・TypeScript
この節では、2026年の webpack 5 で標準になった機能と、css-loader などの扱いが分かります。
webpack 5.109.0 から、CSS・HTML・TypeScript・非同期の WebAssembly の組み込みの対応が「auto」になり、同じファイルに loader の設定が無ければ自動で有効になります。 公式ブログ(Webpack 5.109、2026年7月24日)によると、TypeScript は Node.js 22.6 以上で型を取り除く(type stripping)形で動きます。さらに 5.110(2026年8月29日)で、css-loader・style-loader・mini-css-extract-plugin は組み込みの CSS 対応に置き換える形で 非推奨(deprecated) になりました。どれも引き続き動きます。
手元でも、ローダーの設定が無い状態で CSS と TypeScript をそのまま読めました。
$ E=./e2.js npx webpack -c err/webpack.config.js # import './style.css' だけ。loader の設定なし
asset main.js 796 bytes [emitted] (name: main)
asset main.css 178 bytes [compared for emit] (name: main)
css ./style.css 91 bytes [built] [code generated]
webpack 5.112.2 compiled successfully in 138 ms
$ E=./e7.ts npx webpack -c err/webpack.config.js # type U = { n: number } を含む .ts
webpack 5.112.2 compiled successfully in 177 ms
$ node err/out/main.js
1
手元の webpack 5.112.2 のソース(lib/config/defaults.js)でも、experiments.css の既定は "auto" で、.css に一致するルールがあれば組み込みの対応を止める作りになっていました。前の版の書き方(css-loader の設定)がある設定ファイルは、そのまま前と同じ動きになります。
HTML を入口にした6行の設定
5.108 で HTML ファイルを entry にできるようになりました。HtmlWebpackPlugin・css-loader・MiniCssExtractPlugin を使わない設定で、同じアプリをビルドしました。
// webpack.native.config.js
const path = require('path');
module.exports = {
mode: 'production',
entry: './native/index.html', // script type="module" src="../src/index.js" を含む HTML
output: { path: path.resolve(__dirname, 'dist-native'), clean: true },
};
assets by path *.js 12 KiB
asset index.js 11.8 KiB [emitted] [minimized] (name: native-index-0)
asset 764.js 222 bytes [emitted] [minimized]
asset index.html 264 bytes [emitted] [minimized] (auxiliary name: main)
asset index.css 75 bytes [emitted] [minimized] (name: native-index-0)
./native/index.html 266 bytes [built] [code generated]
css ./src/style.css 91 bytes [built] [code generated]
webpack 5.112.2 compiled successfully in 1067 ms
- 組み込みの対応は、公式のドキュメントで「まだ experimental(試験的)」とされ、webpack 6 で既定になる見込みとされています。style-loader の
insertなど、組み込みに同じ機能が無い設定を使っているなら、loader を残します。 - 移行用に、公式は codemod(
npx codemod @webpack/css-plugins-to-native-css)を案内しています。手元では実行していません。 - 型の検査(型が合っているかの確認)は、組み込みの TypeScript 対応では行われません。型を取り除いて JS にするだけなので、
tsc --noEmitを別に動かします。tsconfig の設定は、関連記事「tsconfig設定一覧|TypeScript 7対応の推奨値とテンプレート」で扱っています。
modeの違いとtree shaking・ソースマップ(devtool)
この節では、development と production で出力がどう変わるかと、ソースマップの種類ごとの大きさが分かります。
mode を production にすると、圧縮・使っていないコードの削除(tree shaking)・モジュールの連結が有効になります。 同じアプリで比べると、出力の main.js は次のようになりました。
| development | production | |
|---|---|---|
| main.js の大きさ | 130,342バイト(eval のソースマップ込み) | 12,175バイト |
| ビルド時間(5回の中央値) | 1,116ms | 1,732ms |
| ファイル名 | main.js・src_chart_js.js | main.144ea5b9.js・689.170c009a.js |
使っていない関数 unused |
残る | 消えた |
| 向いている使い方 | 読みやすさ・再ビルドの速さ | 配信するファイル |
greet.js に書いた export const unused = () => 'tree shaking で消える関数' は、どこからも使っていません。production の出力を「tree shaking」の文字列で検索すると0件、development では1件見つかりました。tree shaking は import / export(ESM)で書いたコードに効きます。lodash ではなく lodash-es を使ったのはこのためです。
devtool(ソースマップ)の種類と大きさ
ソースマップは、圧縮した JS のエラーの位置を、元のソースの行に戻すための対応表です。devtool の値ごとに、style-loader を使う構成で main.js と main.js.map の大きさを測りました(単位はバイト)。
| devtool | dev の main.js | prod の main.js | prod の .map |
|---|---|---|---|
| false | 63,794 | 16,439 | なし |
| eval | 68,406 | 41,307 | なし |
| source-map | 64,522 | 16,997 | 71,717 |
| hidden-source-map | 64,489 | 16,964 | 71,717 |
| eval-cheap-module-source-map | 130,350 | 96,726 | なし(JS の中) |
| inline-source-map | 136,538 | 112,653 | なし(JS の中) |
- eval 系を本番に使うと、production でも main.js が大きく(41KB〜97KB)なりました。 公式の表でも eval 系はすべて本番向きではない(production の列が no)とされています。
- 本番の推奨は、公式では
false(最も速い)かsource-map(質の高いマップ)です。source-mapは JS の末尾にsourceMappingURL=main.js.mapの1行が付き、hidden-source-mapは付きません。 hidden-source-mapは、エラー収集の道具で使うためのもので、公式は .map をWebサーバーに置かないように書いています。元のコードを見せたくないときはnosources-source-map(ソースの中身を含めない)も選べます。
コード分割とキャッシュ(dynamic import・splitChunks)
この節では、1つの大きなファイルを分けて、最初に読む量を減らし、更新のたびのダウンロードを減らす方法が分かります。
コード分割には「あとで使う部品を import() で分ける」と「npm のパッケージを別ファイルに分ける(splitChunks)」の2つがあります。
import() で遅延読み込みにする
// グラフのボタンを押したときに初めて chart.js を取りに行く
document.getElementById('chart').addEventListener('click', async () => {
const { drawChart } = await import('./chart.js');
drawChart(app);
});
これだけで、chart.js は別ファイル(production では 689.170c009a.js、263バイト)になりました。設定の追加はいりません。
splitChunks で npm のパッケージを分ける
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', enforce: true },
},
},
runtimeChunk: 'single', // webpack の実行時コードを1つに分ける
},
assets by path *.js 16.3 KiB
asset vendors.302ddceb.js 12.4 KiB [emitted] [immutable] [minimized] (name: vendors) (id hint: vendors)
asset runtime.b9dba05e.js 2.97 KiB [emitted] [immutable] [minimized] (name: runtime)
asset main.81ae3e02.js 994 bytes [emitted] [immutable] [minimized] (name: main)
asset index.html 328 bytes [emitted] [minimized]
このあと greet.js の文字列を1か所だけ変えてビルドし直すと、ハッシュが変わったのは main(81ae3e02 → f48dd777)だけで、vendors と runtime は同じ名前のままでした。 ブラウザは vendors をキャッシュから使えるので、更新のたびに読み直すのは約1KBで済みます。
chunks: 'all'だけでは分かれませんでした。splitChunks には「production では 20,000 バイトより小さい塊は分けない」(ソースの minSize の既定値)などの条件があり、手元のライブラリ(約12KB)はそれより小さかったためです。enforce: trueで条件を外しています。- 何がどれだけの大きさかは、webpack-bundle-analyzer で見られます。
npx webpack --json=stats.jsonの結果をwebpack-bundle-analyzer stats.json dist -m static -Oに渡すと、サーバーを立てずに HTML のレポート(手元では約315KB)が作られました。
webpack-dev-serverで開発する(HMR)
この節では、保存するたびに自動でビルドして画面に反映する、開発サーバーの使い方が分かります。
webpack-dev-server は、ビルドの結果をメモリに置いて配る開発用のサーバーです。 ファイルを保存すると変わった部分だけをビルドし直し、HMR(Hot Module Replacement:再読み込みせずにモジュールを差し替える仕組み)で画面に反映します。
npm install -D webpack-dev-server
npx webpack serve --mode development # 設定の devServer: { host: '127.0.0.1', port: 47701 }
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://127.0.0.1:47701/
<i> [webpack-dev-server] Content not from webpack is served from '/…/public' directory
asset main.js 666 KiB [emitted] (name: main)
asset src_chart_js.js 1.75 KiB [emitted]
asset index.html 247 bytes [emitted]
| 測ったこと | 結果 |
|---|---|
| 起動してから最初のビルドが終わるまで | 1,789ms |
| curl で取った main.js | 200・681,914バイト(HMR の仕組みを含むため大きい) |
| dist フォルダー | 書き換わらなかった(メモリから配っている) |
| greet.js を保存したあとのビルド | webpack の表示で41ms。変わった1モジュールだけ built、48個は cached |
- webpack-dev-server 6 は Node.js 22.15.0 以上と webpack 5.101.0 以上が必要です(公式ブログ)。SockJS などが取り除かれました。
- 開発サーバーは
host: '127.0.0.1'を明示し、自分の PC の中だけで開けるようにしておきます。 - 止めるときは、起動したターミナルで Ctrl+C を押します。止め忘れると、次の起動でポートが使えないエラーになります(よくあるエラーの節)。
webpackとViteの違い(開発サーバーの仕組み)
この節では、webpack と Vite が、開発中と本番でそれぞれ何をしているかの違いが分かります。
いちばんの違いは開発中です。webpack は開発中もアプリ全体をまとめてから配り、Vite はまとめずに、ブラウザが求めたファイルだけをその場で変換して配ります。 Vite の公式ドキュメント(Why Vite)は、ネイティブの ES モジュールでソースを配り、めったに変わらない依存パッケージだけを事前にまとめておく(pre-bundling)ことで、アプリの大きさに関係なく起動がほぼ一瞬になる、と説明しています。
flowchart TD
S[開発サーバーを起動] --> Q{どちら?}
Q -->|webpack| W[全体をビルド]
W --> W2[main.js 666KB]
Q -->|Vite| V[依存だけ事前に]
V --> V2[1ファイルずつ変換]
Vite の開発サーバーを同じアプリで起動し、curl で /src/index.js を取ると、まとめられていない、import の行き先だけ書き換えた index.js(3,915バイト)が返りました。
$ npx vite # vite.config.mjs の server: { host: '127.0.0.1', port: 47702 }
VITE v8.3.5 ready in 192 ms
➜ Local: http://127.0.0.1:47702/
$ curl -s http://127.0.0.1:47702/src/index.js | head -4
const dayjs = !__vite__cjsImport2_dayjs.__esModule ? __vite__cjsImport2_dayjs : __vite__cjsImport2_dayjs.default;import "/src/style.css";
import { debounce } from "/node_modules/.vite/deps/lodash-es.js?v=2fccd9f6";
import __vite__cjsImport2_dayjs from "/node_modules/.vite/deps/dayjs.js?v=c73f3736";
import { greet } from "/src/greet.js";
npm のパッケージ(lodash-es・dayjs)は /node_modules/.vite/deps/ に事前にまとめたものを指し、自分のコード(greet.js)は1ファイルずつ配られています。CommonJS で書かれた dayjs は、ESM として読めるように包まれていました。
| 比べる点 | webpack 5.112 | Vite 8.3 |
|---|---|---|
| 開発中の配り方 | 全体をまとめてメモリから配る | まとめずに ESM のまま配る |
| 開発サーバーの起動(手元) | 1,789ms | 192ms(Vite の表示) |
| 本番のビルド | webpack 自身 | Rolldown(Rust 製。以前は Rollup) |
| 入口 | JS(5.108以降は HTML も可) | index.html |
| 開発サーバー・HMR | webpack-dev-server を別に入れる | 最初から入っている |
| TypeScript・CSS | 5.109以降は auto で組み込み(以前は loader) | 最初から扱える(型の検査はしない) |
| 設定の量(手元) | loader 構成で27行、組み込み構成で6行 | 5行(ポートの指定だけ) |
| 拡張の仕組み | loader・plugin(数が多い) | Rollup 形式の plugin |
Vite の公式ドキュメントによると、Vite は以前、開発に esbuild・本番に Rollup を使っていましたが、いまは Rust 製の Rolldown で両方をまかなっています。手元の Vite 8.3.5 の依存にも rolldown が入っていました。vite.config の書き方は、関連記事「vite.configの書き方|用途別コピペ一覧とVite 8の変更点」で扱っています。
webpack・Vite・esbuildのビルド比較(時間・サイズの実測)
この節では、同じアプリを3つの道具で本番ビルドしたときの時間・出力の大きさ・できあがるファイルの違いが、実測で分かります。
小さなアプリ(4ファイル+lodash-es・dayjs)と、スクリプトで作った1,000モジュールのアプリを、それぞれ5回ずつビルドした中央値です。 2 vCPU の仮想マシンで、ほかの処理も動いていたため、数百ミリ秒の揺れがあります。順位の目安として読んでください。
小さなアプリ
| 道具と設定 | 時間 | JS の合計 | 主な JS の gzip 後 | HTML |
|---|---|---|---|---|
| webpack(loader 構成・Babel あり) | 1,732ms | 12,438バイト | 5,326バイト | 作る(plugin) |
| webpack(組み込み構成・6行) | 1,368ms | 12,321バイト | 5,247バイト | 作る(HTML の入口) |
| webpack(キャッシュあり・2回目以降) | 761ms | 同じ | 同じ | 作る |
| Vite 8.3.5(vite build) | 347ms | 12,736バイト | 5,467バイト | 作る(入口が HTML) |
| esbuild 0.28.2 | 157ms | 11,414バイト | 4,749バイト | 作らない |
1,000モジュールのアプリ
| 道具と設定 | 時間 | 出力 | gzip 後 |
|---|---|---|---|
| webpack production(既定の terser で圧縮) | 7,300ms | 130,453バイト | 13,965バイト |
| webpack production+babel-loader | 8,666ms | 130,453バイト | 13,965バイト |
| webpack production(esbuild で圧縮) | 1,846ms | 141,978バイト | 17,890バイト |
| webpack production(圧縮しない) | 1,726ms | 226,055バイト | 24,839バイト |
| webpack development | 1,656ms | ― | ― |
| Vite 8.3.5 | 422ms | 138,139バイト | 18,138バイト |
| esbuild 0.28.2 | 131ms | 138,166バイト | 17,905バイト |
どの出力も Node.js で実行すると同じ値(total 30145500)を表示し、動きが同じであることを確かめました。
1webpack の時間の多くは圧縮だった
圧縮しないと 1.7秒、既定の terser では 7.3秒。esbuild で圧縮すると 1.8秒まで縮んだ。
2terser の出力がいちばん小さい
gzip 後で webpack+terser は約14KB、Vite・esbuild は約18KB。時間と引き換えに小さくなる。
3esbuild は HTML を作らない
速いが、index.html の生成やハッシュの差し込みは自分で用意する。アプリ全体の道具としては Vite が受け持つ範囲が広い。
「Vite は webpack の10〜100倍速い」という説明は、esbuild を使った依存の事前処理についての話として紹介されることが多いものです。手元の本番ビルドの差は、小さなアプリで約5倍、1,000モジュールで約17倍でした。開発サーバーの起動は約9倍でした。
webpackのビルドを速くする方法
この節では、webpack のまま、ビルドの時間を短くする設定が、手元の数値で分かります。
- 1ファイルシステムのキャッシュを使う
cache: { type: 'filesystem' }で、2回目以降の本番ビルドが 1,732ms → 761ms になった(キャッシュは約1MB) - 2圧縮を速い道具に変える1,000モジュールで 7,300ms → 1,846ms。代わりに gzip 後が約4KB大きくなった
- 3babel-loader が要るかを見直す新しいブラウザだけが対象なら構文の変換はほとんど要らない。手元では外すと約1.4秒短くなった
- 4loader の対象を絞る
exclude: /node_modules/で、npm のパッケージを変換しない - 5開発中は development と eval 系の devtool再ビルドは変わったモジュールだけになり、手元では41ms
圧縮を esbuild に変えた設定です。terser-webpack-plugin は webpack の中で使われていても、自分の設定で require するなら自分で入れる必要がありました(入れないときのエラーは次の節)。
// npm install -D terser-webpack-plugin esbuild
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
// …
optimization: {
minimizer: [new TerserPlugin({ minify: TerserPlugin.esbuildMinify })],
},
};
なお、手元の webpack 5.112.2 は、既定の圧縮に terser-webpack-plugin ではなく minimizer-webpack-plugin(中で terser を使う)を読み込んでいました(lib/config/defaults.js)。5.110 の公式ブログは、既定の圧縮が JS に加えて CSS と HTML も扱うようになったと書いています。
どうしても速さが足りないときの選択肢として、Rust 製で webpack の API と loader の多くに対応する Rspack があります。公式サイトは、webpack の loader と完全に互換、plugin は「ほとんど」に対応、と書いています。
webpackのよくあるエラーと直し方(原文つき)
この節では、webpack でよく出るエラーの原文と、原因・直し方が分かります。すべて手元で起こしたものです(パスは一部省いています)。
| エラーの原文(先頭) | 原因 | 直し方 |
|---|---|---|
| Module not found: Error: Can't resolve './Greet.js' | ファイル名・大文字小文字・パスの誤り | 実際のファイル名に合わせる |
| Module not found: Error: Can't resolve 'path'+BREAKING CHANGE | ブラウザ向けのコードで Node.js の標準モジュールを使った | 使わない形に直すか resolve.fallback |
| Module parse failed: Unexpected token … You may need an appropriate loader | JSX・Sass など、対応する設定が無い | babel-loader(preset-react)・sass-loader など |
| Module parse failed: Unexpected character '�' | 画像を JS として読んだ | type: 'asset/resource' |
| configuration.module has an unknown property 'loaders' | webpack 1 の頃の書き方 | module.rules と use に書き換える |
| require is not defined in ES module scope | "type": "module" の下で require を使った | import で書くか .cjs にする |
| Cannot find module 'terser-webpack-plugin' | 設定で読むパッケージを入れていない | npm install -D で入れる |
| CLI for webpack must be installed. | webpack-cli が無い | npm install -D webpack-cli |
| listen EADDRINUSE: address already in use | 開発サーバーのポートが使用中 | 前のサーバーを止めるかポートを変える |
| The 'mode' option has not been set | mode を書いていない(警告) | mode か --mode を指定 |
Module not found: Error: Can't resolve
import した ./Greet.js が無いときの原文です。webpack は、探した場所を全部書き出します。
ERROR in ./e1.js 1:0-35
Module not found: Error: Can't resolve './Greet.js' in '/…/err'
resolve './Greet.js' in '/…/err'
using description file: /…/package.json (relative path: ./err)
Field 'browser' doesn't contain a valid alias configuration
aliased from extension alias with mapping '.js' to '.js'
using description file: /…/package.json (relative path: ./err/Greet.js)
Field 'browser' doesn't contain a valid alias configuration
/…/err/Greet.js doesn't exist
aliased from extension alias with mapping '.js' to '.ts'
/…/err/Greet.ts doesn't exist
- 「doesn't exist」の行に、webpack が実際に探したパスが出ます。 ここを見ると、拡張子の補い方(.js → .ts も探している)まで分かります。
- 手元の Linux では、ファイル名の大文字小文字が違うだけで別のファイルとして扱われ、
./Greet.jsと./greet.jsは一致しませんでした。import の綴りは実際のファイル名とそろえます。 - npm のパッケージ名で出たときは、
npm installの入れ忘れか、package.json の dependencies に無いことを確かめます。
BREAKING CHANGE: webpack < 5 used to include polyfills
ERROR in ./e3.js 1:0-24
Module not found: Error: Can't resolve 'path' in '/…/err'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
- install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "path": false }
webpack 4 までは、ブラウザ向けのビルドでも path・crypto・buffer などの Node.js の標準モジュールの代わり(ポリフィル)を自動で入れていました。webpack 5 からは入れません。まず「ブラウザでそのモジュールが本当に要るか」を考え、要らなければ resolve.fallback: { "path": false }、要るなら代わりのパッケージを入れます。 エラー文の中に、両方の書き方がそのまま書かれています。
You may need an appropriate loader to handle this file type
JSX を babel の設定なしで読んだときです。
ERROR in ./e4.js 1:18
Module parse failed: Unexpected token (1:18)
File was parsed as module type 'javascript/auto'.
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> 1 | const App = () => <h1>Hello</h1>;
| ^
2 | console.log(App);
File was parsed as module type 'javascript/auto' は「JavaScript として読もうとした」という意味です。同じエラーは、画像(Unexpected character '�')と Sass(.scss)でも出ました。webpack 5.112 では、CSS と TypeScript はこのエラーにならず、JSX・画像・Sass などで出ます。
| 読んだファイル | 足す設定 |
|---|---|
| JSX(.jsx・.js の中の JSX) | babel-loader+@babel/preset-react、または TypeScript の tsx |
| 画像・フォント | type: 'asset/resource'(loader 不要) |
| Sass(.scss) | sass-loader と sass(CSS としての処理は組み込みか css-loader) |
設定ファイルのエラー(loaders・require・CLI)
webpack 1 の頃の記事にある module.loaders をそのまま書くと、設定の検査で止まります。
[webpack-cli] ✖ Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.module has an unknown property 'loaders'. These properties are valid:
object { defaultRules?, exprContextCritical?, … rules?, … }
-> Options affecting the normal modules (`NormalModuleFactory`).
Did you mean module.rules or module.rules.*.use?
package.json に "type": "module" がある場所で、require を使った設定ファイルを読んだときです。
[webpack-cli] ✖ Failed to load '/…/esmcfg/webpack.config.js' config
▶ ESM (`import`) failed:
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/…/esmcfg/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
ESM と CommonJS が混ざったときの考え方は、関連記事「Node.js ESMとCommonJSの違い|import混在の解決表」で扱っています。設定で読むパッケージを入れていないとき、webpack-cli が無いときは次のとおりでした。
[webpack-cli] ✖ Failed to load 'webpack.big-esmin.config.js' config
▶ ESM (`import`) failed:
Error: Cannot find module 'terser-webpack-plugin'
$ node node_modules/webpack/bin/webpack.js # webpack-cli を外した状態
CLI for webpack must be installed.
webpack-cli (https://github.com/webpack/webpack-cli)
We will use "npm" to install the CLI via "npm install -D webpack-cli".
Do you want to install 'webpack-cli' (yes/no):
開発サーバーのポートが使われている
[webpack-cli] ✖ Error: listen EADDRINUSE: address already in use 127.0.0.1:47701
code: 'EADDRINUSE',
errno: -98,
syscall: 'listen',
address: '127.0.0.1',
port: 47701
}
前に起動した開発サーバーが残っています。そのターミナルで Ctrl+C を押すか、devServer.port を変えます。
Vite・esbuild での同じエラー
同じ「無いファイルの import」は、Vite と esbuild では次の文言でした。道具を変えても、見る場所(どのファイルの何行目の何を解決できなかったか)は同じです。
[UNRESOLVED_IMPORT] Could not resolve './Greet.js' in verr/main.js ← Vite 8.3.5
✘ [ERROR] Could not resolve "./Greet.js" ← esbuild 0.28.2
✘ [ERROR] The JSX syntax extension is not currently enabled ← esbuild で JSX を .js に書いた
Vite では、package.json に "type" が無い場所で vite.config.js に ESM の書き方を使うと、「ESM syntax in a file loaded as CommonJS」という警告が出ました。ファイル名を vite.config.mjs にすると消えました。
webpackからViteへ移行するかの判断表
この節では、いま webpack を使っているプロジェクトを、そのまま使うか、Vite などへ移るかを決める順番が分かります。
「動いている webpack をすぐ捨てる」理由は多くありません。 5.109以降の webpack は、loader を減らした設定でも動き、キャッシュと圧縮の設定で手元の時間は半分以下になりました。次の順に確かめます。
- 1フレームワークが道具を決めているかNext.js・Angular CLI などは、その道具の推奨に従う。webpack の設定を直接いじる場面は少ない
- 2困っているのは開発中の待ち時間か開発サーバーの起動・HMR が遅いなら Vite の効果が大きい。本番ビルドだけならキャッシュと圧縮の設定から
- 3代わりの無い loader・plugin を使っているか独自の loader や Module Federation に頼っているなら、webpack か Rspack に残るほうが安全
- 4CommonJS や require.context が多いかESM 前提の Vite へ移るには、書き換えが増える
- 5小さく試せるか新しい画面や別のパッケージから Vite にし、時間と出力を比べてから広げる
| 状況 | 向いている選択 |
|---|---|
| 新しく React・Vue のアプリを作る | Vite(create vite) |
| webpack の設定が大きく、独自の loader がある | webpack のまま、または Rspack |
| webpack で本番ビルドが遅いだけ | cache・圧縮の見直し(手元で約4分の1) |
| ライブラリを1つの JS にまとめたい | esbuild か Vite のライブラリモード |
| Node.js 向けのスクリプトを1ファイルにしたい | esbuild(--platform=node) |
| 以前のブラウザへの対応が厳しく求められる | webpack+Babel(targets を細かく指定) |
Vite で React の環境を作る手順は、関連記事「React環境構築をViteで行う手順|create viteの使い方」で扱っています。
よくある質問
Q. webpackとは何ですか?簡単に言うと? A. JavaScript・CSS・画像などのファイルを、import の関係をたどって少数のファイルにまとめる道具(バンドラー)です。圧縮・使っていないコードの削除・ファイルの分割も行います。
Q. webpack と webpack-cli は両方入れる必要がありますか? A. コマンド(npx webpack)で使うなら両方入れます。webpack が本体、webpack-cli がコマンドの部分です。webpack-cli が無いと「CLI for webpack must be installed.」と表示され、入れるか聞かれました。
Q. webpack と Vite はどちらを使えばよいですか? A. 新しく作るアプリなら、設定が少なく開発サーバーが速い Vite が選ばれることが多いです。手元では開発サーバーの起動が約9倍、本番ビルドが約5〜17倍速くなりました。既存の webpack の設定に独自の loader などがあるなら、webpack のまま設定を見直すのも現実的です。
Q. webpack はもう使われていないのですか? A. 開発は続いており、2026年も 5.105〜5.112 が出て、CSS・HTML・TypeScript の組み込みの対応や、性能の警告などが加わりました。公式のロードマップは webpack 6 への準備を挙げています。
Q. css-loader と style-loader はまだ入れるべきですか? A. webpack 5.109 以降は、CSS 用の loader の設定が無ければ組み込みの対応で CSS を扱えます。5.110 で css-loader・style-loader・mini-css-extract-plugin は非推奨になりました。既存の設定はそのまま動くので、急いで外さなくても動きは変わりません。
Q. webpack で TypeScript を使うには ts-loader が要りますか?
A. webpack 5.109 以降と Node.js 22.6 以上なら、.ts 用のルールが無いときは組み込みの型の取り除きで読めます(手元でも確認)。型の検査は行わないので、tsc --noEmit などを別に動かします。
Q. npx webpack と npm run build の違いは何ですか?
A. 中身は同じです。package.json の scripts に "build": "webpack" と書くと、npm run build で同じビルドが動きます。追加の引数は npm run build -- --mode development のように -- の後ろに書きます(公式の Getting Started)。
Q. 本番にソースマップ(.map)を置いてもよいですか?
A. 置くと、ブラウザの開発ツールで元のソースが見えます。見せたくないなら devtool: false、エラー収集だけに使うなら hidden-source-map にして .map を Web サーバーに置かない、という使い分けが公式に書かれています。
Q. Vite は TypeScript の型の誤りを見つけてくれますか?
A. 見つけません。Vite の公式ドキュメントは、.ts ファイルは変換するだけで型の検査はしない、型の検査はエディターやビルドの手順に任せる、と書いています。webpack の組み込みの TypeScript 対応も同じで、型の検査は tsc --noEmit などで別に行います。
まとめ
- webpack は、entry から import をたどって依存関係のグラフを作り、JS・CSS・画像を少数のファイルにまとめるバンドラー。
npm install -D webpack webpack-cliとnpx webpackで始められる。 - 設定の骨組みは entry・output・module.rules(loader)・plugins・mode。loader はファイル1つずつの変換、plugin はビルド全体への処理。
- webpack 5.109 以降は、loader の設定が無ければ CSS・HTML・TypeScript を組み込みで扱う。5.110 で css-loader などは非推奨になったが、前の設定はそのまま動く。
- 同じアプリの本番ビルドは、webpack 1,732ms・Vite 347ms・esbuild 157ms。1,000モジュールでは webpack の時間の大半が圧縮で、esbuild で圧縮すると 7.3秒が 1.8秒になった。
- エラーは、Module not found なら「doesn't exist」の行のパス、You may need an appropriate loader なら読んだファイルの種類を見る。Vite・esbuild でも見る場所は同じ。
次に読むとよい記事です。Vite の設定は関連記事「vite.configの書き方|用途別コピペ一覧とVite 8の変更点」で、import の書き方は関連記事「JavaScript import/export入門|ESMとCommonJSの違い」で扱っています。
参考・出典
確認日はいずれも 2026年10月29日です。
- webpack「Concepts」:https://webpack.js.org/concepts/
- webpack「Getting Started」:https://webpack.js.org/guides/getting-started/
- webpack「Devtool」:https://webpack.js.org/configuration/devtool/
- webpack「Native CSS」:https://webpack.js.org/guides/native-css/
- webpack Blog「Webpack 5.109」(2026年7月24日):https://webpack.js.org/blog/2026-07-24-webpack-5-109/
- webpack Blog「Webpack 5.110」(2026年8月29日):https://webpack.js.org/blog/2026-08-29-webpack-5-110
- webpack Blog(5.105〜5.112・Roadmap 2026 の一覧):https://webpack.js.org/blog/
- Vite「Why Vite」:https://vite.dev/guide/why
- Vite「Getting Started」:https://vite.dev/guide/
- Rspack 公式サイト:https://rspack.rs/
- 各パッケージの版と Node.js の要件は、npm で入れた package.json の engines で確かめました。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「webpackとは?使い方とViteとの違い|3つのビルドを実測比較」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。