webpack の基本的な使い方

以下は webpack のインストール方法や webpack.config.js の設定方法、Loaders ローダー、Plugins プラグイン、splitChunks、webpack-dev-server、v5 で導入された Asset Modules などの基本的な使い方の覚書です。

Node.js がインストールされていることを前提にしています。

※ webpack5 (バージョン 5) 用に内容を更新しました(2021年12月31日)。

以下で使用しているパッケージのバージョン

関連ページ:

webpack Home image

作成日:2020年6月8日

webpack は JavaScript などの依存関係のあるモジュールを適切に1つのファイルにまとめてくれる開発ツールでモジュールバンドラーの1つです。

モジュールとは別のプログラムに取り込むことを前提としたある機能をひとまとめにした(関数やコンポーネントなどの機能ごとに分けた)ファイルのことです。

バンドル(bundle)は「束ねる」や「まとめる」というような意味があり、モジュールバンドラーはモジュール(機能ごとに分けたファイル)をまとめてくれるものと言うような意味になります。

webpack は JavaScript 以外にも CSS や画像ファイルなどもまとめることができます。

webpack のインストール

webpack は Node.js のパッケージマネージャ npm を使ってインストールすることができます。

通常はプロジェクトごとに管理できるようにそれぞれのディレクトリでローカルインストールを行います。

プロジェクトのファイルを保存するフォルダーを任意の場所に作成してそこに cd で移動します。以下では mkdir でフォルダを作成していますが、右クリックから「新規作成」などでも OK です。

この例ではローカル環境(MAMP)の htdocs に myProject というフォルダを作成して進めていきます。

$ mkdir myProject  return //フォルダーを任意の場所に作成

$ cd myProject  return //作成したフォルダー(ディレクトリ)に移動

npm を使ってインストールする場合、まず npm init コマンドで package.json を生成します。デフォルトのオプションで package.json を生成すれば良いので -y オプションを指定します。

$ npm init -y return //package.json をデフォルトで生成

//生成された package.json のパスと内容が表示される
Wrote to /Applications/MAMP/htdocs/myProject/package.json:

{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

続いて npm install コマンドで webpack をインストールします。

webpack 4.0以降では、コマンドライン操作用のパッケージは webpack-cli という別パッケージで提供されているので併せてインストールします。

install コマンドオプションの -D(--save-dev)は開発環境で使う(バンドルにのみ webpack を使用し、本番ビルドに webpack を含めない)場合に指定するオプションです。

バージョンを指定しないでインストールすると最新版がインストールされます。

$ npm install -D webpack webpack-cli  return //webpack と webpack-cli をインストール

//インストールが完了すると追加されたパッケージの数などが表示されます
added 119 packages, and audited 120 packages in 25s

インストールされた webpack と webpack-cli のバージョンは npx コマンドに -v(または --version )オプションを指定して webpack を実行して確認できます。

$ npx webpack -v return // バージョンを表示
webpack: 5.65.0
webpack-cli: 4.9.1
webpack-dev-server not installed

ヘルプの表示は -h(または --help )を指定します。

インストールされたパッケージは npm ls コマンドで確認できます。

オプションの -depth を指定しない場合は、インストールされた全ての依存ファイルも表示されます。

$ npm ls -depth=0  return //一番上の階層のみを表示

myproject@1.0.0 /Applications/MAMP/htdocs/myProject
├── webpack-cli@4.9.1
└── webpack@5.65.0

$ npm ls -depth=1  return //第2階層まで表示(主な依存パッケージが表示される)

myproject@1.0.0 /Applications/MAMP/htdocs/myProject
├─┬ webpack-cli@4.9.1
│ ├── @discoveryjs/json-ext@0.5.6
│ ├── @webpack-cli/configtest@1.1.0
│ ├── @webpack-cli/info@1.4.0
│ ├── @webpack-cli/serve@1.6.0
・・・中略・・・
│ ├── webpack-merge@5.8.0
│ └── webpack@5.65.0 deduped
└─┬ webpack@5.65.0
  ├── @types/eslint-scope@3.7.2
  ├── @types/estree@0.0.50
  ├── @webassemblyjs/ast@1.11.1
  ├── @webassemblyjs/wasm-edit@1.11.1
  ├── @webassemblyjs/wasm-parser@1.11.1
  ├── acorn-import-assertions@1.8.0
  ├── acorn@8.6.0
・・・中略・・・
  ├── terser-webpack-plugin@5.3.0
  ├── watchpack@2.3.1
  └── webpack-sources@3.2.2

インストールが完了すると、インストールされたパッケージは node_modules というフォルダに保存され、package.json 及び package-lock.json という npm の設定ファイルが生成されます。

$ tree -L 1  return //ツリー表示
.
├── node_modules   //npm でインストールされたパッケージのディレクトリ
├── package-lock.json  //npm の設定ファイル
└── package.json   //npm の設定ファイル

これで webpack のインストールは完了です。

webpack guides: Getting Started

webpack guides: installation

関連ページ:npm の基本的な使い方

JavaScript ファイルをバンドル

以下は webpack を使って JavaScript(モジュール)をバンドルして webpack の動作を確認する例です。

webpack のデフォルトではエントリーポイントは src/index.js、出力先は dist/main.js になります。

エントリーポイントとは依存関係を解決する上で基準となるファイルのことで、webpack はエントリーポイントで指定したファイルが依存する(エントリーポイントを起点として import している)モジュールを読み込みます。

webpack は src ディレクトリにエントリポイントの index.js ファイルがあれば設定ファイルなしでビルドを実行してモジュールをバンドルすることができます(全てデフォルトを適用)。

この例では設定ファイル webpack.config.js を使わずビルドを実行するので、エントリポイントはデフォルトの index.js として src フォルダに配置し、デフォルトの出力先のフォルダ dist に表示用の HTML ファイル index.html を配置します。

dist フォルダにはビルド時にバンドルされた JavaScript ファイル(main.js)が出力されます。

以下のようにプロジェクトのフォルダの中に dist 及び src という名前のフォルダを作成します。

更に src フォルダの中にモジュールを格納するフォルダ modules を作成します。

myProject
├── dist  //追加したフォルダ(バンドルされた JavaScript ファイルの出力先)
├── node_modules
├── package-lock.json
├── package.json
└── src  //追加したフォルダ
    └── modules  //追加したフォルダ(モジュールを格納するフォルダ)

modules フォルダの中に以下のような ES6 のモジュール(ES Modules)の記述方法(export 文)で関数をエクスポートする foo.js と bar.js を作成します。

src/modules/foo.js(モジュール)
// export 文を使って関数 greet1 を定義
export function greet1() {
  return 'Hello from Foo.';
}
src/modules/bar.js(モジュール)
// export 文を使って関数 greet2 を定義
export function greet2() {
  return 'Hello from Bar.';
}

src フォルダの中に以下のような index.js という名前のエントリポイントとなるファイルを作成します。

ES Modules の記述方法(import 文)で foo.js と bar.js から関数をインポートし、それらの実行結果を使って HTML を生成して、body 要素に追加しています。

webpack を使用する場合、モジュールのパスの拡張子(.js など)は省略可能です。ここではビルドを実行する前に表示を確認するため通常の import 文として記述していますが、通常拡張子は省略します。

src/index.js(エントリポイント)
// import 文を使って foo.js の関数 greet1 をインポート
import { greet1 } from './modules/foo.js';
// import 文を使って bar.js の関数 greet2 をインポート
import { greet2 } from './modules/bar.js';

function component() {
  //div 要素を生成
  const divElem = document.createElement('div');
  //p 要素を生成
  const p1 = document.createElement('p');
  //インポートした greet1 の実行結果を p 要素のテキストに
  p1.textContent = greet1();
  //div 要素に上記 p 要素を追加
  divElem.appendChild(p1);
  const p2 = document.createElement('p');
  p2.textContent = greet2();
  divElem.appendChild(p2);

  return divElem;
}

document.body.appendChild(component());

※ この例では ES Modules の記述方法を使っていますが、webpack は require や exports、module.exports を使う Node.js(CommonJS)の記述方法でも(どちらでも)動作します。

webpack guides: Module Methods

dist フォルダには以下のような index.html を作成します。

この時点ではエントリポイントの src/index.js を読み込む際に script タグ(8行目)に type="module"(ES Modules の記述方法)を指定していますが、JavaScript をバンドル後は type="module" を削除してバンドルされた JavaScript ファイル(main.js)を読み込むように変更します。

dist/index.html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
  </head>
  <body>
    <script src="../src/index.js" type="module"></script>
    <!--バンドル後は  <script src="main.js"></script> に変更-->
  </body>
</html>

この段階での構成は以下のようになっています。

myProject
├── dist
│   └── index.html  //追加したファイル
├── node_modules  //npm でインストールされるパッケージ(webpack 等)
├── package-lock.json
├── package.json
└── src
    ├── index.js  //追加したファイル(エントリポイント)
    └── modules
        ├── bar.js  //追加したファイル(モジュール)
        └── foo.js  //追加したファイル(モジュール)

MAMP などのローカル環境で http://localhost/myProject/dist/index.html を表示すると index.js の記述により、以下のような HTML が出力されます。

package.json

npm のパッケージに関する設定情報が記述された package.json を編集します。

main の指定はパッケージを NPM で公開しない場合は不要なので、main の行(5行目)を削除し、プロジェクトを誤って公開しないようにするため "private": true を追加します。

description にはプロジェクトの説明などを記述しても良いですし、削除しても問題ありません。また、keywords や author、license もプロジェクトを公開しない場合は削除しても問題ありません。

※注意: 以下はコメントを入れてありますが、実際には JSON ファイルにはコメントを付けることはできません。コメントを入れたまま、ビルドを実行するとエラーになります。

package.json
{
  "name": "myProject",
  "version": "1.0.0",
  "description": "",   //削除可能 (※注意)
  //"main": "index.js", //削除 (※注意)
  "private": true,  //追加(※注意)
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11"
  }
}

webpack でビルド

以下のビルドコマンドを実行して JavaScript ファイルをバンドルします。npx は npm のパッケージ(バイナリ)を実行するコマンドです。

webpack 4からは mode オプションを指定することが推奨されていますが、以下は省略しているのでデフォルトの production が適用されている旨の WARNING が表示されています。

$ npx webpack  return //webpack を実行
asset main.js 275 bytes [emitted] [minimized] (name: main)
orphan modules 210 bytes [orphan] 2 modules
./src/index.js + 2 modules 932 bytes [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.65.0 compiled with 1 warning in 184 ms

以下は mode オプションに development を指定して実行する例です。

$ npx webpack --mode=development  return //mode オプションを指定して webpack を実行
asset main.js 5.6 KiB [emitted] (name: main)
runtime modules 670 bytes 3 modules
cacheable modules 932 bytes
  ./src/index.js 722 bytes [built] [code generated]
  ./src/modules/foo.js 105 bytes [built] [code generated]
  ./src/modules/bar.js 105 bytes [built] [code generated]
webpack 5.65.0 compiled successfully in 72 ms

index.js 及び index.js でインポートしている bar.js と foo.js が統合され、dist フォルダーの中に main.js として出力されます。

myProject
├── dist
│   ├── index.html
│   └── main.js //webpack によりまとめられた JavaScript ファイル
├── node_modules
├── package-lock.json
├── package.json
└── src
    ├── index.js
    └── modules
        ├── bar.js
        └── foo.js

webpack で出力した dist フォルダー内の main.js を index.html で読みこむことで、バンドルされたコードが実行されます。

index.html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
  </head>
  <body>
    <script src="main.js"></script><!--  main.js を読み込む -->
  </body>
</html>

ブラウザで index.html にアクセスすると、読み込みを main.js に変更しても以下のように src/index.js を読み込んでいるのと同じように表示されます。

バンドルされた(まとめられた)JavaScript ファイル main.js は以下のようになっています。

以下は mode オプションを指定せずに実行した例で、デフォルトの production が適用されてコードが最適化及び圧縮(optimization.minimize)されています。

main.js
(()=>{"use strict";document.body.appendChild(function(){const e=document.createElement("div"),t=document.createElement("p");t.textContent="Hello from Foo.",e.appendChild(t);const n=document.createElement("p");return n.textContent="Hello from Bar.",e.appendChild(n),e}())})();

以下は mode オプションに development を指定して実行した場合の main.js の例です。

この例では webpack の動作を確認するために、意味のないモジュール(foo.js と bar.js)を読み込んでバンドルしていますが、実際の使用では npm でインストールしたプラグインやフレームワーク、CSS、メディアなどをインポートしてバンドルすることができます。

以下はプラグインなどを読み込んでいるエントリポイントの例です。CSS や Sass をバンドルするにはローダープラグインをインストールし、webpack.config.js で設定します。

各プラグインのインストール方法や import の記述方法などはそれぞれのプラグインのドキュメントに掲載されています。

index.js(エントリポイントの記述例)
// Bootstrap の読み込み
import bootstrap from '../node_modules/bootstrap/dist/js/bootstrap';
// カスタムスタイルシート(Sass)の読み込み
import './custom.scss';
//lazysizes と ls.unveilhooks(プラグイン)の読み込み
import 'lazysizes';
import 'lazysizes/plugins/unveilhooks/ls.unveilhooks';
//Swiper スライダープラグインの読み込み
import Swiper from 'swiper/bundle';
//Swiper styles の読み込み
import 'swiper/css/bundle';
//rellax.js パララックスプラグインの読み込み
import 'rellax';
//Luminous lightbox ライトボックスプラグインの読み込み
import { Luminous, LuminousGallery } from 'luminous-lightbox';
import 'luminous-lightbox/dist/luminous-basic.min.css';

//以下 JavaScript での定義や処理など
const sliderElems = document.querySelectorAll('.swiper:not(.thumbs-slider, .main-slider)');
if(sliderElems && sliderElems.length > 0) {
・・・省略・・・

package.json の scripts フィールド

npx コマンドで webpack を実行する以外に、package.json の scripts フィールドにコマンド(npm script)を追加しておくと npm run コマンドで webpack を実行することができます。

package.json を編集して "build": "webpack" を scripts フィールドに追加します(8行目)。※ 7行目の最後にカンマを追加します。

これで npm run build とコマンドラインで入力すると webpack が呼び出されてビルドが実行されます。

package.json
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

npm run build とコマンドラインで入力することで、scripts フィールドの "build": に指定したコマンド webpack が呼び出されてビルドが実行されます。

この場合、npm run build は npx webpack と同じことですが、scripts フィールドの "build": などの npm scripts に指定する際は npx は省略することができます。

$ npm run build  return //npx webpack と同じ

> myproject@1.0.0 build
> webpack

asset main.js 275 bytes [emitted] [minimized] (name: main)
orphan modules 210 bytes [orphan] 2 modules
./src/index.js + 2 modules 932 bytes [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.65.0 compiled with 1 warning in 164 ms

上記の場合、モードを指定していないので WARNING が表示されています。

モードは設定ファイル webpack.config.js でも指定することができますが、以下のように scripts フィールドにモードを指定したビルドコマンドを追加しておくこともできます。

以下のように設定すると、npm run build を実行すると webpack --mode production が呼び出され、npm run dev を実行すると webpack --mode development が呼び出されます。

package.json
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

以下は実行例です。

$ npm run dev  return //npx webpack --mode development と同じ

> myproject@1.0.0 dev
> webpack --mode development

asset main.js 5.6 KiB [emitted] (name: main)
runtime modules 670 bytes 3 modules
cacheable modules 932 bytes
  ./src/index.js 722 bytes [built] [code generated]
  ./src/modules/foo.js 105 bytes [built] [code generated]
  ./src/modules/bar.js 105 bytes [built] [code generated]
webpack 5.65.0 compiled successfully in 74 ms

Node の環境変数を渡す

Node.js の環境変数は process.env というオブジェクトに格納され、よく使われる環境変数に process.env.NODE_ENV があります。

node コマンドの -e(または --eval)は引数を JavaScript として評価するオプションです

$ node -e "console.log(process.env)"  return  //設定されている環境変数が表示される
{
  TERM_PROGRAM: 'Apple_Terminal',
  SHELL: '/bin/bash',
  TERM: 'xterm-256color',
  TMPDIR: '/var/folders/c2/kytqb8ls7p941834kqccm05h0000gn/T/',
  Apple_PubSub_Socket_Render: '/private/tmp/com.apple.launchd.VHiYuGqTg9/Render',
  TERM_PROGRAM_VERSION: '421.2',
  OLDPWD: '/Applications/MAMP/htdocs/sass-test',
  TERM_SESSION_ID: 'C5B29082-304A-40DB-A7CA-15501CA59C4F',
  USER: 'foo',
  SSH_AUTH_SOCK: '/private/tmp/com.apple.launchd.N6BfDMob8K/Listeners',
  PATH: '/Users/foo/sbin:/Users/foo/.nodebrew/current/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/foo/bin',
  PWD: '/Applications/MAMP/htdocs/myProject',
  LANG: 'ja_JP.UTF-8',
  XPC_FLAGS: '0x0',
  XPC_SERVICE_NAME: '0',
  SHLVL: '1',
  HOME: '/Users/foo',
  LOGNAME: 'foo',
  _: '/Users/foo/.nodebrew/current/bin/node',
  __CF_USER_TEXT_ENCODING: '0x1F5:0x1:0xE'
}

$ node -e "console.log(process.env.NODE_ENV)"  return
undefined  //process.env.NODE_ENV は定義されていない(設定していないため)

必要であれば、webpack を実行する際に、NODE_ENV にパラメータを渡すことができます。

//webpack を実行時に NODE_ENV に development を渡す場合
$ NODE_ENV=development npx webpack

//webpack を実行時に NODE_ENV に production を渡す場合
$ NODE_ENV=production npx webpack

scripts フィールドに NODE_ENV を指定したビルドコマンドを追加しておくこともできます。

package.json
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "NODE_ENV=production webpack --mode production",
    "dev": "NODE_ENV=development webpack --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}
$ npm run build  return

> myproject@1.0.0 build
> NODE_ENV=production webpack --mode production
・・・以下省略・・・

$ npm run dev  return

> myproject@1.0.0 dev
> NODE_ENV=development webpack --mode development
・・・以下省略・・・

上記のように webpack を実行する際に、NODE_ENV にパラメータ(production や development)を渡すと、webpack.config.js やエントリポイントのファイルで process.env.NODE_ENV で値を取得することができます。

webpack.config.js や index.js で設定した環境変数を取得
if (process.env.NODE_ENV === 'production') {
  console.log('production');
}else{
  console.log('devlopement');
}

関連項目:プロダクションビルドと開発モードで style-loader と使い分ける

webpack.config.js

webpack はエントリポイントの src/index.js ファイルがあれば設定ファイルを使わなくても処理を実行できますが、webpack.config.js という名前の JavaScript ファイル(設定ファイル)を用意することで webpack の処理を詳細にカスタマイズすることができます。

例えば、モードを指定しておけば実行する都度オプションを指定せずに済みますし、エントリポイントのファイルや出力先のファイル・ディレクトリも自由に設定することができます。

よく使う設定項目としては、開発モードや本番モードを指定する mode やエントリーポイントを指定する entry、出力先を指定する output があります。

webpack.config.js はプロジェクトのルートに配置します。

myProject
├── dist //出力先(ビルドディレクトリ)
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //エントリーポイント
│   └── modules
│       ├── bar.js
│       └── foo.js
└── webpack.config.js //追加

webpack.config.js は JavaScript ファイルで、module.exports オブジェクトにプロパティを定義するようになっています。

以下は entry プロパティでエントリポイントのファイルを、output プロパティで出力先を、mode プロパティでモードを指定する例です。

webpack.config.js
const path = require('path');  //path モジュールの読み込み

module.exports = {
  entry: './src/index.js',  //エントリポイント
  output: {  //出力先
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  mode: 'development',  //モード
};

上記の例の場合、エントリポイントと出力先はデフォルトと同じ設定を指定していますが(デフォルトと同じ設定をする場合は省略可能)、それぞれ独自のファイルやディレクトリを指定することができます。

複数の設定ファイル

webpack コマンドはデフォルトでは webpack.config.js があるとその設定を使用しますが、--config オプションを指定して別の設定ファイルを利用することもできます。

//webpack.config.js 以外の設定ファイルを使用する場合
npx webpack --config 設定ファイル名

または、package.json の scripts フィールドに npm script を追加する方法もあります。

以下は npm run build コマンドで prod.config.js という名前の設定ファイルを使用する例です。

package.json
"scripts": {
  "build": "webpack --config prod.config.js"
}

webpack guides: Configuration

その他の設定タイプ

設定を単一のオブジェクトとしてエクスポート(module.exports オブジェクトにプロパティを定義)する以外にも、関数としてエクスポートしたり、Promise を使うこともできます。

webpack guides: Configuration Types

createapp.dev オンライン設定ツール

createapp.dev を使って webpack.config.js の設定を生成したり、指定した構成に基づいてサンプルプロジェクトを生成してダウンロードすることができます。

entry

entry プロパティではエントリポイントを設定します。

エントリポイントとは依存関係を解決する上で基準となるファイルのことで、webpack はエントリポイントでインポートしているモジュール間の依存関係を解析します。

デフォルトのエントリポイントは ./src/index.js ですが、任意のファイルを指定することができます。

entry プロパティの指定方法(Syntax)は以下の2通りがあり、ショートハンドで指定した場合、エントリ名は main になります(デフォルト)。値は設定ファイルからの相対パスで指定します。

Single Entry Syntax(ショートハンド)
module.exports = {
  entry: '値(ファイルへのパス)',
};
Object Syntax(オブジェクト形式)
module.exports = {
  entry: {
   エントリ名(entryChunkName): '値(ファイルへのパス)',
  }
};

以下は ./path/to/my/entry ディレクトリにあるファイル file.js をエントリポイントに指定する例です。

webpack.config.js
//エントリポイントを設定(設定ファイル ./ からの相対パス)
module.exports = {
  entry: './path/to/my/entry/file.js'
};

以下は上記と同じことをオブジェクト形式で指定したものです(エントリ名のデフォルトは main)。

webpack.config.js
module.exports = {
  entry: {
    main: './path/to/my/entry/file.js'
  }
};
以下の設定はデフォルトと同じこと
//ショートハンドの場合
module.exports = {
  entry: './src/index.js'
};

//オブジェクト形式の場合
module.exports = {
  entry: {
    main: './src/index.js'
  }
};

entry や output のデフォルトの記述は省略

デフォルトのエントリーポイントは src/index.js なので上記の設定の記述は省略できます。v4 から v5 への移行ガイドの Clean up configuration には以下のような記述があります。

Consider removing defaults(デフォルトを削除することを検討してください):

  • Using entry: './src/index.js': you can omit it, that's the default.
  • Using output.path: path.resolve(__dirname, 'dist'): you can omit it, that's the default.
  • Using output.filename: '[name].js': you can omit it, that's the default.
To v5 from v4 /Clean up configuration

但し、以降のサンプルや例では明示的にエントリポイントを示すため省略していません。

複数のエントリポイント

複数のエントリポイントを指定することもできます。

以下はショートハンドの書式で配列でファイルを指定する例(multi-main entry)で、複数の依存ファイルを1つにまとめて出力する場合などに使用します。

webpack.config.js
module.exports = {
  entry: ['./src/file_1.js', './src/file_2.js'],
  output: {
    filename: 'bundle.js',  //bundle.js にまとめて出力
  },
};

以下はオブジェクト形式で2つのエントリポイントを指定する例で、より柔軟な設定が可能になります。

以下の場合、main と vendor というエントリ名で指定しているので、出力の設定で [name] という置換のテンプレート文字列を使えば、main.js、vendor.js というファイルが出力されます。

[name] にはエントリ名が入ります。

webpack.config.js
module.exports = {
  entry: {
    main: './src/app.js',
    vendor: './src/vendor.js',
  },
  output: {
    filename: '[name].js',  //main.js、vendor.js が出力される
  },
};
  

webpack guides: Entry Points

output

output はバンドルしたファイルやアセットの出力方法や出力先(ビルドディレクトリ)を設定する一連のオプションを含むプロパティです。

よく使うプロパティには、出力先のパスを指定する output.path プロパティとバンドルしたファイルの名前を設定する output.filename プロパティなどがあります。

出力先のパスは絶対パスで指定します。

出力先のパスの指定では OS によってパスが異なることを防ぐため冒頭で Node.js のビルトインモジュールの path モジュールを require() を使ってインポートして、そのメソッド path.resolve を使用しています。

webpack.config.js
const path = require('path');  //path モジュールの読み込み

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {  // output プロパティ
    path: path.resolve(__dirname, 'dist'), //出力先のディレクトリ dist の絶対パス
    filename: 'my-first-webpack.js', //出力ファイルの名前
  }
};

__dirname はこのファイルが格納されているディレクトリ(webpack.config.js が存在するディレクトリ)の絶対パスを表す Node.js で予め用意されている変数です。

path.resolve(__dirname, 'dist') は webpack.config.js が /Applications/MAMP/htdocs/myProject/webpack.config.js にある場合、 /Applications/MAMP/htdocs/myProject/dist になります。

以下でも確認できます。node コマンドの -e または --eval は引数を JavaScript として評価するオプションです(Command Line Options)。

//webpack.config.js があるディレクトリ(myProject)で実行
$ pwd  return
/Applications/MAMP/htdocs/myProject

$ node -e "console.log(__dirname)"  return
.

$ node -e "console.log(path.resolve('/foo', 'bar'))"  return
/foo/bar

$ node -e "console.log(path.resolve(__dirname))"  return
/Applications/MAMP/htdocs/myProject

$ node -e "console.log(path.resolve(__dirname, 'dist'))"  return
/Applications/MAMP/htdocs/myProject/dist

filename

filename にはファイル名を文字列で指定することができますが、[name] などの置換用のテンプレート文字列を使って記述することもできます。

[name] には entry プロパティで key (エントリ名)として指定した文字列が入ります。

以下のように entry の key に bundle を指定して、output.filename に [name].js を指定すると、バンドルされて出力されるファイル名は bundle.js になります。

webpack.config.js
const path = require('path');

module.exports = {
  entry: {
    bundle: './src/index.js',
  },
  output: {
    filename: '[name].js',  //エントリ名.js → bundle.js
    path: path.resolve(__dirname, 'dist'),
  }
};

output のデフォルトの記述は省略

デフォルトの output.path は path.resolve(__dirname, 'dist') で、デフォルトの output.filename は [name].js です。このためこれらの値を使用する場合は省略可能です。

但し、以降のサンプルでは明示的にエ出力先を示すため省略していません。

複数のエントリポイントがある場合

複数のエントリポイントがある場合でも、output.filename で指定する設定は1つなので、1つにまとめて出力すか、複数のバンドルを出力するには [name] などのテンプレート文字列を使います。

[name] には entry プロパティで key (エントリ名)として指定した文字列が入ります。

以下の場合、app.js、search.js というファイルが dist フォルダに出力されます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',  //エントリ名.js
    path: path.resolve(__dirname, 'dist'),
  }
};

以下のように [name].bundle.js と記述すると、[name] には entry プロパティで key として指定した文字列(エントリ名)が入るので、それぞれ app.bundle.js、search.bundle.js というファイルが dist フォルダ出力されます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].bundle.js',  //エントリ名.bundle.js
    path: path.resolve(__dirname, 'dist'),
  }
};

以下のように [name].[contenthash] を使うと、[contenthash] の部分にはビルドごとに生成された一意のハッシュ(Hash)が入り、app.149cec35d7dc8920c912.bundle.js のようなファイル名のファイルが dist フォルダ出力されます。

const path = require('path');

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].[contenthash].bundle.js',  //エントリ名.ハッシュ.bundle.js
    path: path.resolve(__dirname, 'dist'),
  }
};

[hash] ではなく [contenthash] を使う

テンプレート文字列の [hash] を使う場合は、代わりに [contenthash] を使います。

When using [hash] placeholder in webpack configuration, consider changing it to [contenthash]. It is not the same, but proven to be more effective.

To v5 from v4 /Clean up configuration

webpack guides: Output / output.filename

output.clean

version 5.20.0 以上 では output.clean オプションを指定できます。

webpack はビルド時にファイルを生成し、それらを dist フォルダーに配置しますが、実際にプロジェクトで使用されているかどうかは管理していません。

output.clean オプションを使うと、ビルドの前に出力先の dist フォルダーをクリーンアップ(すべてのファイルを削除)して、使用しているファイルのみが生成されるようにすることができます。

module.exports = {
  //...
  output: {
    clean: true, //ファイルを出力する前にディレクトリをクリーンアップ
  },
};

※但し、手動で作成して配置しているファイルも含めてすべて削除(クリーンアップ)されます。

keep (除外の指定)

keep オプションに削除対象外のファイルやディレクトリを正規表現で指定することができます。

module.exports = {
  //...
  output: {
    clean: {
      //出力先の ignored/dir 配下のファイルを削除しない
      keep: /ignored\/dir\//,
    },
  },
};

以下は手動で dist フォルダに index.html を作成・配置している場合に dist フォルダ内の index.html はクリーンアップの対象から除外する例です。

module.exports = {
  //...
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    clean: {
      keep: /index.html/, // index.html をキープ(削除しない)
    },
  },
};

webpack guides: Cleaning up the dist folder / output.clean

mode

mode プロパティのパラメーターを development、production、または none に設定することにより各環境に対応する最適化を有効にすることができます。デフォルトは production です。

production(デフォルト)を指定すると optimization.minimize という設定が適用されて、最適化及び圧縮(minify)されたファイルが出力されます。

最適化及び圧縮の設定は optimization プロパティでも設定することができます。

  • development:開発時向けのオプション(ソースコードが読みやすい状態で出力)
  • production:本番環境(公開時)向けのオプション(ソースコードを圧縮及び最適化)
webpack.config.js
module.exports = {
  entry: './path/to/my/entry/file.js',
  ・・・中略・・・
  mode: 'development'  //mode プロパティ
};

但し、mode を webpack.config.js で指定するとモードを変更する都度に記述を変更する必要があるため、npm scripts を使って webpack をそれぞれのモードで実行する方が便利かも知れません。

以下のように package.json を設定すると、npm run build で webpack --mode production を、npm run dev で webpack --mode development を実行できます。

package.json scripts フィールド部分の抜粋
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "webpack --mode production",
  "dev": "webpack --mode development"
}

webpack guides: Mode

watch

watch オプションを利用すると処理対象のファイルを監視してファイルが変更されると自動的にビルドを再実行(リビルド)することができます。

watch オプションを指定して webpack コマンドを実行すると watch モードになり、ビルド処理を実行した後に待機状態になり、ファイルが変更されるたびにリビルドを実行します。

また、watch モードではキャッシュが有効になり、差分ビルド(リビルド)が行われるのでビルド時間が短くなります。

watch オプションを有効にするには webpack コマンドを実行する際に --watch オプションを指定するか、webpack.config.js に watch: true を追加します。

以下はコマンドラインの引数に --watch を指定して実行する例です。

npx webpack --watch

watch モードを終了するには、control + c でプロセスを終了します。

package.json の scripts フィールドにコマンド(npm scripts)を追加する方法もあります。

以下は watch に --mode と --watch オプションを指定したコマンドを定義する例です(5行目)。

package.json scripts フィールド部分の抜粋
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "webpack --mode production",
  "dev": "webpack --mode development",
  "watch": "webpack --mode development --watch"
},

上記の場合、 npm run watch コマンドを実行します。

$ npm run watch  return //watch オプションのコマンドを実行
// npx webpack --mode=development --watch と同じこと

> myproject@1.0.0 watch
> webpack --mode development --watch

asset main.js 5.66 KiB [compared for emit] (name: main)
runtime modules 670 bytes 3 modules
cacheable modules 1.03 KiB
  ./src/index.js 837 bytes [built] [code generated]
  ./src/modules/foo.js 108 bytes [built] [code generated]
  ./src/modules/bar.js 105 bytes [built] [code generated]
webpack 5.65.0 compiled successfully in 70 ms  //起動時にビルドを実行

//foo.js を更新(リビルドされる)
asset main.js 5.66 KiB [emitted] (name: main)
runtime modules 670 bytes 3 modules
cached modules 942 bytes [cached] 2 modules
./src/modules/foo.js 105 bytes [built] [code generated]
webpack 5.65.0 compiled successfully in 16 ms

または、以下のよう webpack.config.js で watch オプションを有効にすることもできます。

webpack.config.js
module.exports = {
  ・・・中略・・・
  watch: true,  //watch オプションを有効にする
};
watchOptions

多くのファイルを監視すると、CPU やメモリの使用量が多くなる可能性があります。

watchOptions の ignored プロパティを使うと node_modules などの特定のディレクトリやファイルを監視対象から除外することができます。

webpack.config.js の watchOptions.ignored に文字列または正規表現で指定します。

webpack.config.js
module.exports = {
  //...
  watchOptions: {
    ignored: /node_modules/  //正規表現で指定(node_modules を除外)
  }
};

文字列にはワイルドカードが使え、複数指定する場合は配列で指定することができます。

webpack.config.js
module.exports = {
  //...
  watchOptions: {
    ignored: ['files/**/*.js', 'node_modules/**']   //配列とワイルドカードで指定
  }
};

webpack guides: Watch and WatchOptions

devtool ソースマップ

devtool オプションを使うと出力されるソースマップを設定することができます。デフォルトではソースマップファイルは出力されません。

devtool オプションにソースマップのタイプを指定してビルドすると、出力ファイルと同じディレクトリに出力ファイルと同じ名前で拡張子が「.js.map」のソースマップファイルが作成されます。

出力するソースマップのタイプによりビルドにかかる時間やファイルのサイズ、出力内容(形式)が異なります。以下は指定できるソースマップのタイプの一部抜粋です。(詳細は devtool

devtool タイプ production 説明
eval no 開発ビルドに推奨(build: fast / rebuild: fastest)
eval-source-map no 開発ビルドに推奨(build: slowest / rebuild: ok)
source-map yes プロダクションビルドに推奨(build: slowest / rebuild: slowest)

以下は devtool に source-map タイプ(サイズが大きく、ビルドにも時間がかかる)を指定してソースマップを出力する例です。

webpack.config.js
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  mode: 'development',
  devtool: 'source-map' //source-map タイプのソースマップを出力
};

ビルドすると dist フォルダに main.js.map というソースマップファイルが出力されます。

dist
├── index.html
├── main.js
└── main.js.map //ソースマップファイル

webpack guides: Devtool

関連項目:ソースマップの出力の制御

optimization

Webpack version 4 以上では選択したモードに応じて最適化を実行しますが、optimization プロパティでデフォルトの最適化の設定を上書き(変更)することができます。

デフォルトを変更する必要があれば設定します。

optimization.minimize

optimization.minimize はデフォルトまたは optimization.minimizer で指定されたプラグインを使って圧縮を実行するかどうかを指定するプロパティです。

production モードではデフォルトで有効(true)になっています。

例えば、モードに関わらず常に圧縮(minify)を無効にするには以下のように minimize プロパティを false に設定します。

webpack.config.js
module.exports = {
  //...
  optimization: {
    minimize: false
  }
};

webpack guides: optimization.minimize

optimization.minimizer

optimization.minimizer を設定することで、デフォルトの圧縮方法を変更することができます。minimizer プロパティには圧縮に使用するプラグイン(TerserPlugin をカスタマイズしたものや他の圧縮用プラグイン)のインスタンスの配列を指定します。

以下は minimizer に TerserPlugin をカスタマイズして設定する例です。

webpack.config.js
//プラグインの読み込み
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          // オプションは以下を参照
          //https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
        },
      }),
    ],
  },
};

Webpack v5 には、最新の terser-webpack-plugin が付属していますが、Webpack v5 以降でオプションをカスタマイズしたい場合は、terser-webpack-pluginをインストールする必要があります。

webpack guides: optimization.minimizer

webpack guides: TerserPlugin

optimization.splitChunks

複数のエントリポイントで共通のモジュールを使っている場合、それぞれのファイルに共通のモジュールをバンドルするのではなく、共通のモジュールだけ別のファイルとして出力することで全体のファイルサイズを小さくすることができます。

optimization.splitChunks は複数のエントリーポイントで利用している共通のモジュールをバンドルする際に適切に分離(chunk を split)するための設定です。

以下は SplitChunksPlugin のデフォルトの設定です。必要に応じて変更することができます。

webpack.config.js
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

以下は node_modules からインポートされる全てのモジュールを別の vendors という名前の chunk に分離して出力する optimization.splitChunks の設定例です。

webpack.config.js
module.exports = {
  //...
  optimization: {
    splitChunks: {
    // 明示的に特定のファイル(共通モジュール)を chunk に分離して出力する場合に設定
      cacheGroups: {
        // 任意の名前
        commons: {
          // node_modules 配下のモジュールを対象とする
          // パスセパレータはクロスプラットフォームに対応するため [\\/] を使用
          test: /[\\/]node_modules[\\/]/,
          // バンドルされるファイルの名前
          name: 'vendors',
          //モジュールの種類(この場合は test の条件にマッチする全てを分離)
          chunks: 'all'
        }
      }
    }
  }
};

詳細は splitChunks を御覧ください。

webpack guides: SplitChunksPlugin

webpack guides: Optimization

resolve

webpack には import を使ってモジュールをインポートする際に、指定されたモジュールを検索して該当するファイルを探す仕組み(モジュール解決)があります。

resolve オプションはモジュール解決(モジュールの import を解決する仕組み)の設定を変更します。

通常はあまり使わないかも知れませんが、必要に応じて設定します。

resolve.modules

モジュールを解決するときに検索するディレクトリを webpack に指示します。

デフォルトは node_modules です。

module.exports = {
  //...
  resolve: {
    modules: ['node_modules']
  }
};

webpack guides: resolve.modules

resolve.extentions

このオプションで指定されている拡張子のファイルは import の際に拡張子を省略することができます。

指定された拡張子を順番に解決します。複数のファイルが同じ名前を共有していて拡張子が異なる場合、webpack は配列の最初にリストされている拡張子を持つファイルを解決し、残りをスキップします。

省略する対象の拡張子を配列で指定します。

module.exports = {
  //...
  resolve: {
    extensions: ['.wasm', '.mjs', '.js', '.json']
  }
};

上記のように resolve.extensions を使って指定すると、デフォルトの配列が上書きされることになり、webpack はデフォルトの拡張子を使用してモジュールを解決しようとしなくなります。

'...' を使用してデフォルトの拡張子にアクセスできます。

module.exports = {
  //...
  resolve: {
    extensions: ['.ts', '...'],
  },
};

webpack guides: resolve.extensions

resolve.alias

resolve.alias を使ってエイリアスを作成することで、特定のモジュールをより簡単にインポートすることができます。

例えば、よく使用する src /フォルダーのエイリアスを作成するには以下のように設定することができます。

const path = require('path');

module.exports = {
  //...
  resolve: {
    alias: {
      Utilities: path.resolve(__dirname, 'src/utilities/'),
    }
  }
};

以下のように相対バスでインポートする代わりに、

import Utility from '../../utilities/utility';

以下のようにエイリアスを使って記述してインポートすることができます。

import Utility from 'Utilities/utility';

webpack guides: resolve.alias

モジュール解決

以下は webpack のモジュール解決の概要です。

webpack で import を使ってモジュールをインポートする場合、以下のような指定が可能です。

絶対パス
import '/home/me/file';
import 'C:\\Users\\me\\file';
相対パス
import '../src/file1';
import './file2';
モジュールパス
import 'module';
import 'module/lib/file';

絶対パスと相対パスの場合は単純に指定されたディレクトリを検索します。

モジュールパスの場合は、resolve.modules で設定されているディレクトリを検索します。resolve.modules のデフォルトの検索先のフォルダは node_modules です。

パスが解決されたら、続いて指定されたパスがファイルかフォルダかで以下のようになります。

パスがファイルを指している場合

ファイルに拡張子が指定されていれば、該当するファイルで解決されます。

パスに拡張子がない場合は、resolve.extentions で指定された拡張子(例 .js)を使って解決します。

もし、該当するファイルが複数ある場合は、resolve.extentions で最初にリストされている拡張子が採用されます。

パスがフォルダを指している場合

そのフォルダに package.json があれば、resolve.mainFields で指定されているフィールドを参照してファイルを解決します。resolve.mainFields のデフォルトは ['browser', 'module', 'main'] です(デフォルトは target の値により異なります)。

package.json が存在しない場合や resolve.mainFields の値が有効でない場合は、resolve.mainFiles で指定されているファイル名(デフォルトは index)を検索し、resolve.extentions で指定されている拡張子で解決します。

webpack guides: Module Resolution

Loaders ローダー

ローダー(Loader)はリソース(ソースコード)を変換したりモジュール化するためのツールです。

webpack では、ローダーを使用してファイルを事前に処理することができます。

デフォルトでは webpack は JavaScript などのモジュール化されたものしかバンドルすることができず、 CSS ファイルなどのリソースはバンドルすることができません。

ローダーを使用すると webpack は他のタイプのファイルを処理してそれらを有効なモジュールに変換してバンドルしたり、バンドルする前にモジュールに対して変換などの処理を適用することができます。

ローダーはリソース(ファイル)の種類に合わせて使い分けます。

webpack guides: Loaders (v5) / v4

ローダーを使用するには webpack.config.js を編集して設定します。ローダーの設定は module プロパティの rules プロパティで test と use の2つのプロパティを使って設定します。

webpack.config.js
module.exports = {
  //モジュールの設定(module プロパティ)
  module:{
    //rules プロパティ
    rules:[
      // ローダーの設定
      {
        test:/\.css$/,  //test プロパティ
        use:['style-loader','css-loader']   //use プロパティ
      }
    ]
  }
};
プロパティ 説明
module プロジェクト内の様々なタイプのモジュールがどのように処理されるかを設定するプロパティ
module.rules ローダーのルール(Rule)の配列 [Rule] を指定します。これらのルールはローダーをモジュールに適用したり、パーサーを変更したり、モジュールがどのように作成されるかを設定できます。
Rule.test
Condition.test
処理対象の変換するファイルを指定します。文字列の場合は絶対パスで指定します。正規表現や関数で指定することもできます(正規表現の場合は引用符は付けません)。
Rule.use 変換を行うために使用するローダーを指定します。ローダーの指定は文字列または、エントリ(UseEntry オブジェクト)の配列 [UseEntry] で指定することができます。use: [ 'style-loader' ] のように文字列を指定した場合は use: [ { loader: 'style-loader '} ] とオブジェクトでの指定のショートカット(同じ意味)になります。ローダーを複数指定してチェインすることができ、その場合、順番は最後に指定したものから適用されます。
UseEntry Rule.use プロパティで指定するエントリで、オブジェクトまたは関数で指定し、loader プロパティに文字列でローダーを指定します。文字列またはオブジェクトの options プロパティを指定することもできます。
Rule.exclude
Condition.exclude
ローダーの処理対象から外すディレクトリやファイルを指定することができます。文字列で指定する場合は絶対パスで指定します。正規表現や関数で指定することもできます。
Rule.enforce ローダーのカテゴリー('pre' または 'post')を指定します。例えば、enforce: 'pre'を指定すると enforce: 'pre'が付いていない通常のローダーより早く処理が実行されます。

babel-loader

Babel は JavaScript のコンパイラ(トランスパイラ)で、ECMAScript 2015(ES6)以上の仕様の JavaScript を互換性のある ES5 の構文に変換してくれるツールですが、Babel にはモジュールをまとめる機能がありません。

webpack と一緒に Babel を使用するにはローダー(babel-loader)を使用します。

Babel を使用するのに必要なモジュールをインストールします。

@babel/core(Babel の本体)、@babel/preset-env(Babel の環境設定)、babel-loader(ローダー)の3つのモジュールを最低限インストールする必要があります。モジュール名の先頭の @ は Scoped packages という指定方法です。

// -D(または --save-dev)オプションを指定して必要なモジュールをインストール
$ npm install -D babel-loader @babel/core @babel/preset-env  return

added 156 packages, and audited 276 packages in 43s

上記のモジュールインストール後の package.json は例えば以下のようになっています。

package.json(例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "babel-loader": "^8.2.3",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

設定ファイル webpack.config.js にローダーの設定を追加します。

test プロパティには処理対象のファイルを指定します。この例では正規表現で /\.js$/ を指定して、ファイル名が「.js」で終わるファイルを処理対象にするようにしています。「.mjs」も対象にするには /\.m?js$/ とします。

use プロパティでは変換を行うために使用するローダーを指定します。この場合は、オプションも指定するのでエントリオブジェクト(UseEntry)の形式で、loader に babel-loader を指定して、options に使用するプリセット(presets)に関する設定を指定しています。

presets プロパティに @babel/preset-env を指定することで、最新の構文を ES5 の構文に変換できます。以下の例では targets を指定していませんが、必要に応じてターゲットブラウザなどを指定することができます。

webpack.config.js
const path = require('path');

module.exports = {
  // エントリポイントの設定
  entry: './src/index.js',
  // 出力先の設定
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      // ローダーの設定
      {
        // ローダーの処理対象ファイル(拡張子 .js のファイルを対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに使用するローダーやオプションを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      }
    ]
  }
};

上記の設定ファイルを保存して webpack を実行すると、src フォルダーに配置した JavaScript ファイルがトランスパイル(ES5 の構文に変換)され、dist フォルダーにバンドルされたファイル main.js が出力されます。

$ npm run build return // webpack を実行
// または npx webpack --mode=production

> myproject@1.0.0 build
> webpack --mode production

asset main.js 324 bytes [emitted] [minimized] (name: main) 1 related asset
orphan modules 244 bytes [orphan] 2 modules
./src/index.js + 2 modules 1.05 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 444 ms

確認方法としては、例えば以下のプロジェクトのモジュールとして読み込んでいるファイル foo.js の関数をアロー関数に書き換えます。

プロジェクトのファイル構成

myProject
├── dist
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js
│   └── modules
│       ├── bar.js
│       └── foo.js //このファイルを編集して確認
└── webpack.config.js
dist/index.html(表示用ファイル)
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>
src/index.js(エントリポイント)
// import 文を使って foo.js と bar.js の関数をインポート(ファイルの拡張子は省略)
import { greet1 } from './modules/foo';
import { greet2 } from './modules/bar';

function component() {
  const divElem = document.createElement('div');
  const p1 = document.createElement('p');
  p1.textContent = greet1();
  divElem.appendChild(p1);
  const p2 = document.createElement('p');
  p2.textContent = greet2();
  divElem.appendChild(p2);
  return divElem;
}

document.body.appendChild(component());
書き換え後の src/modules/foo.js
// アロー関数を使った定義に変更
export const greet1 = ()  => 'Hello webpack, with arrow function!';

バンドルされたファイルを確認しやすいように webpack.config.js に mode: 'development', と devtool: 'source-map' を指定して webpack コマンドを実行します。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: [
                '@babel/preset-env',
              ]
            }
          }
        ]
      }
    ]
  },  //カンマが必要
  mode: 'development', // 追加
  devtool: 'source-map' // 追加
};

webpack を実行します。

$ npm run dev return //development モードでビルド
// または npx webpack --mode=development

> myProject@1.0.0 dev
> webpack --mode development

出力されるバンドルされたファイル main.js を確認すると最後の方にアロー関数が関数リテラルに、const が var に書き換えられているのが確認できます。

main.js
・・・中略・・・
/***/ "./src/modules/foo.js":
/*!****************************!*\
  !*** ./src/modules/foo.js ***!
  \****************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "greet1": () => (/* binding */ greet1)
/* harmony export */ });
// export 文を使って関数 greet1 を定義
var greet1 = function greet1() {
  return 'Hello webpack, with arrow function!';
};

/***/ })

webpack guides: babel-loader

css-loader/style-loader

webpack で CSS を扱うには、CSS を変換するローダー(css-loader)が必要です。

更に、変換した CSS を DOM に挿入するローダー(style-loader)または CSS を別ファイルとして出力するプラグイン(MiniCssExtractPlugin)も必要になります。

プロダクションビルドでは MiniCssExtractPlugin を使用することが推奨されています。

開発モード(webpack-dev-server を含む)の場合、style-loader を使用すれば CSS を DOM に挿入することで、より高速に動作します。

webpack guides: Loading CSS

ローダー 説明
css-loader CSSの @import や url() を JavaScript の import や require() に変換するモジュール
style-loader style 要素を生成して CSS を head 要素(DOM)に挿入するモジュール

以下は CSS を css-loader と style-loader を使って、output プロパティで指定した出力ファイル(main.js)に CSS をバンドルする例です。

この例ではプロジェクトの src フォルダに以下のようなスタイルシートを追加します。

src/style.css
p {
  color: green;
  background-color: yellow;
}

index.js で CSS ファイルを import

エントリポイント index.js で import 文を使って上記の CSS ファイル(style.css)を読み込みます。

src/index.js
import  './style.css';  // import 文を使って style.css を読み込む(追加)
import { greet1 } from './modules/foo';
import { greet2 } from './modules/bar';

function component() {
  const divElem = document.createElement('div');
  const p1 = document.createElement('p');
  p1.textContent = greet1();
  divElem.appendChild(p1);
  const p2 = document.createElement('p');
  p2.textContent = greet2();
  divElem.appendChild(p2);
  return divElem;
}
document.body.appendChild(component());
ファイル構成
myProject
├── dist
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //import 文を使って style.css を読み込む
│   ├── modules
│   │   ├── bar.js
│   │   └── foo.js
│   └── style.css  //追加したスタイルシート
└── webpack.config.js

その他のファイルは今までの例と同じものを使用しています。

src/modules/foo.js(index.js で import)
export const greet1 = ()  => 'Hello webpack, with arrow function!';
src/modules/bar.js(index.js で import)
export function greet2() {
  return 'Hello from Bar.';
}
dist/index.html(バンドルされた main.js を読み込みブラウザで表示するファイル)
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>

以下をプロジェクトのディレクトリで実行して css-loader と style-loader のモジュールをインストールします。

// -D(または --save-dev)オプションを指定してモジュールをインストール
$ npm install -D css-loader style-loader  return

added 17 packages, and audited 293 packages in 2s

上記実行後の package.json は、css-loader と style-loader が追加され以下のようになります(16〜18行目は Babel 用のモジュールで、CSS のローダーとは関係ありません)。

package.json(例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

webpack.config.js に設定を追加

設定ファイル webpack.config.js に CSS 用のローダーの設定を追加します。

test プロパティには処理対象のファイルを指定します。以下の例では、正規表現で /\.css$/i と指定して、ファイル名が「.css」や「.CSS」で終わるファイルを処理対象にするようにしています(15行目)。

use プロパティでは、変換を行うために使用するローダー(style-loader と css-loader)を指定します。

※ use プロパティにはローダーを複数指定してチェインすることができ、指定したローダーは後ろから順番(css-loader → style-loader の順)に適用されます(17行目)。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js', // エントリポイントの設定
  output: { // 出力先の設定
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    // ローダーの設定
    rules: [
      //CSS 用のローダー
      {
        //拡張子 .css や .CSS を対象
        test: /\.css$/i,
        //使用するローダーを指定
        use: ['style-loader', 'css-loader']
      },
      // Babel 用のローダー
      {
        // 拡張子 .js を対象
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに対するローダーを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
    ]
  },
};

この例ではローダーのオプションを指定しないのでローダーの指定は文字列で指定しています(17行目)。

use: [ 'style-loader' ] のように文字列を指定した場合は use: [ { loader: 'style-loader '} ] とオブジェクトで指定したのと同じ意味になります。

use: ['style-loader', 'css-loader']

//上記と以下は同じこと(オプションの指定がある場合はこちらを使用)
use: [
  {
    loader: 'style-loader',
    //options: {オプションをオブジェクトで指定}
  },
  {
    loader: 'css-loader',
  }
]

npm run dev で webpack を実行します。

$ npm run dev  return // webpack を実行
// または npx webpack --mode=development

> myproject@1.0.0 dev
> webpack --mode development

asset main.js 25 KiB [emitted] (name: main)
runtime modules 937 bytes 4 modules
cacheable modules 10.4 KiB
  modules by path ./node_modules/ 8.07 KiB
  modules by path ./node_modules/style-loader/dist/runtime/*.js 5.75 KiB 6 modules
  modules by path ./node_modules/css-loader/dist/runtime/*.js 2.33 KiB
  ・・・中略・・・
webpack 5.65.0 compiled successfully in 737 ms

ブラウザで index.html を開いて再読み込みすると p 要素へ設定したスタイルが適用されているのが確認できます。また、開発ツールで見ると style 要素が head 内に挿入されているのが確認できます。

index.html で読み込んでいる main.js(バンドルされた JavaScript)により style 要素が head 内に挿入されてスタイルが適用されます。

dist/index.html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
  </head>
  <body>
    <script src="main.js"></script>
    <!-- main.js により style 要素が head 内に挿入される -->
  </body>
</html>

webpack guides: css-loader

webpack guides: style-loader

CSS のソースマップ

css-loader には CSS のソースマップを有効にするオプション sourceMap があります。

sourceMap のデフォルトの値(有効かどうかの真偽値)は devtool の設定に依存します。devtool のオプションに eval または false 以外を指定していれば CSS のソースマップも有効です。

CSS のソースマップをローダーの設定で有効にするには sourceMap オプションに true を指定します。

前述の例ではローダーの指定は文字列で指定しましたが、オプションを指定する場合はオブジェクトで、loader に使用するローダー、options に sourceMap のオプションを指定します(20〜26行目)。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js', // エントリポイントの設定
  output: { // 出力先の設定
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    // ローダーの設定
    rules: [
      //CSS 関連のローダーの設定
      {
        //拡張子 .css や .CSS を対象
        test: /\.css$/i,
        //ローダーを指定
        use: [
          // CSS を出力するローダー
          'style-loader',
          {
            // CSS を変換するローダー
            loader: 'css-loader',
            options: {
              sourceMap: true, // ソースマップを有効に
            }
          }
        ]
      },
      // Babel 用のローダー
      {
        //拡張子 .js を対象
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに対するローダーを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
    ]
  },
  // devtool に eval または false 以外を指定しても有効になります
  //devtool: 'source-map'
};

webpack.config.js を変更後 npm run dev などで webpack を実行してビルドするとソースマップが有効になります。

CSS の画像

スタイルシートで背景画像(background-image)を指定している等 CSS で画像を読み込む設定をしている場合、v4 までは url() や画像ファイルを扱うためのローダー(url-loader や file-loader)が必要でしたが、v5 では新しく組み込まれた Asset Modules により画像がロードされるようになっています。

例えば、前述の例の CSS を以下のように背景画像(background-image)を使うように変更し、src フォルダの中に images フォルダを作成してその中に画像を配置します。

src/style.css
p {
  color: green;
  background-color: yellow;
}
div {
  background-image: url("./images/sample.png");
  background-size: cover;
  padding: 30px;
  max-width: 640px;
}
myProject
├── dist
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   └── sample.png  //background-image で使う背景画像を配置
│   ├── index.js
│   ├── modules
│   │   ├── bar.js
│   │   └── foo.js
│   └── style.css  //background-image を設定
└── webpack.config.js

webpack.config.js で test プロパティに対象のアセットファイル(画像などのファイル)の拡張子を指定し、type プロパティを設定します(29,30行目)。

この例では type に asset を指定しているので、デフォルトでは画像のサイズが 8096 バイト(?)未満の場合は Data URLs(Base64)として埋め込まれ、サイズが大きい場合は画像は出力先にコピーされます。

webpack guides: css-loader / Assets

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js', // エントリポイントの設定
  output: { // 出力先の設定
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    // ローダーの設定
    rules: [
      //CSS 用のローダー
      {
        test: /\.css$/i,  //拡張子 .css や .CSS を対象
        //使用するローダーを指定
        use: [
          'style-loader', // CSS を出力するローダー
          {
            loader: 'css-loader', // CSS を JavaScript に変換するローダー
            options: {
              sourceMap: true, // ソースマップを有効に
            },
          },
        ],
      },
      //Asset Modules
      {
        //対象のアセットファイルの拡張子を指定
        test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/i,
        type: 'asset'  //type を指定
      },
      // Babel 用のローダー
      {
        // 拡張子 .js を対象
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに対するローダーを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
    ]
  },
};

npm run dev などで webpack を実行します。

$ npm run dev  return // webpack を実行

> myproject@1.0.0 dev
> webpack --mode development
・・・中略・・・
  modules by path ./src/ 3.73 KiB
    modules by path ./src/*.css 2.71 KiB 2 modules
    modules by path ./src/modules/*.js 244 bytes
      ./src/modules/foo.js 139 bytes [built] [code generated]
      ./src/modules/bar.js 105 bytes [built] [code generated]
    ./src/index.js 804 bytes [built] [code generated]
./src/images/sample.png 42 bytes (javascript) 47.4 KiB (asset) [built] [code generated]
webpack 5.65.0 compiled successfully in 1024 ms

version5 の場合は Asset Modules によりデフォルトでは出力先のフォルダ(dist フォルダ)にハッシュ値を使った名前の画像が出力されます(この例の場合、type に asset を指定しているので、画像ファイルのサイズが小さい場合は Data URLs として埋め込まれます)。

出力先のフォルダやファイル名、ファイルサイズの閾値などは Asset Modules の設定で変更することができます。

myProject
├── dist
│   ├── 0ee8757acddf12e2c1f7.png  //ビルドでコピーされて出力される画像ファイル
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   └── sample.png  //background-image で使う背景画像のファイル
│   ├── index.js
│   ├── modules
│   │   ├── bar.js
│   │   └── foo.js
│   └── style.css
└── webpack.config.js

画像を webpack の処理の対象外にする

css-loader の url オプションで URL の解決(CSS 関数の url() の処理)を無効にすることができます。

url オプションを無効にした場合、出力される CSS での画像のパスは表示用の HTML(index.html)からのパスになるので、スタイルシートで読み込む画像は出力先の dist 配下に配置します。

myProject
├── dist
│   ├── images
│   │   └── sample.png  //background-image で使う背景画像を配置
│   ├── index.html
│   └── main.js
├── package-lock.json
├── package.json
├── src
│   ├── index.js
│   ├── modules
│   │   └── foo.js
│   └── style.css
└── webpack.config.js

webpack.config.js を編集して css-loader の url オプションに false を設定(23行目)して URL の解決を無効にすると、src/style.css の画像の読み込みの url() メソッドは変換されずに出力されます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js', // エントリポイントの設定
  output: { // 出力先の設定
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    // ローダーの設定
    rules: [
      //CSS 用のローダー
      {
        test: /\.css$/i,  //拡張子 .css や .CSS を対象
        //使用するローダーを指定
        use: [
          // CSS を出力するローダー
          'style-loader',
          {
            // CSS を変換するローダー
            loader: 'css-loader',
            options: {
              url: false, //URL の解決を無効に
              sourceMap: true, // ソースマップを有効に
            }
          }
        ]
      },
      // Babel 用のローダー
      {
        // 拡張子 .js を対象
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに対するローダーを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
    ]
  },
};

sass-loader

webpack は sass-loader を使って Sass を CSS へ変換する処理を追加することができます。

以下の例では、Sass でタイルを定義して、sass-loader を使って CSS に変換しています。以下が大まかなローダーの処理の流れです。

  1. sass-loader を使って Sass を CSS へ変換
  2. css-loader で CSS を JavaScript に変換
  3. style-loader で JavaScript から style 要素を生成して CSS を head 要素に挿入

index.html では出力される(バンドルされた) main.js を読み込むことでスタイルが適用されます。

前述の例の CSS(style.css)を、Sass の記述に書き換えて style.scss という名前(拡張子 .scss)で保存します。この例の場合、元の CSS(style.css)は不要なので削除します。

src/style.scss(Sass ファイル)
/* Sass 変数で色と背景色を定義 */
$color: blue;
$bg_color: pink;

p {
  color: $color;
  background-color: $bg_color;
}

エントリポイント index.js で import 文を使って上記の Sass ファイルを読み込む記述に変更します。

index.js
// import 文を使って style.scss(Sass ファイル)を読み込む
import  './style.scss';
import { greet1 } from './modules/foo';
import { greet2 } from './modules/bar';

function component() {
  const divElem = document.createElement('div');
  const p1 = document.createElement('p');
  p1.textContent = greet1();
  divElem.appendChild(p1);
  const p2 = document.createElement('p');
  p2.textContent = greet2();
  divElem.appendChild(p2);
  return divElem;
}
document.body.appendChild(component());

その他のファイルは今までの例と同じものを使用しています。

ファイル構成
myProject
├── dist
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //import 文を使って style.scss(Sass)を読み込む
│   ├── modules
│   │   ├── bar.js
│   │   └── foo.js
│   └── style.scss  //追加した Sass ファイル(元の CSS は削除)
└── webpack.config.js
src/modules/foo.js(index.js で import)
export const greet1 = ()  => 'Hello webpack, with arrow function!';
src/modules/bar.js(index.js で import)
export function greet2() {
  return 'Hello from Bar.';
}
dist/index.html(バンドルされた main.js を読み込みブラウザで表示するファイル)
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>

Sass ファイルを CSS に変換する sass-loader と Sass のモジュールをプロジェクトに追加でインストールします(style-loader と css-loader はインストールしてある前提です)。

モジュール 説明
sass-loader Sass を CSS へ変換するためのモジュール(ローダー)
sass Sass をコンパイルするためのモジュール。Dart Sass のモジュールを使用しています。LibSass を使う場合は sass の代わりに node-sass をインストールします。

以下をプロジェクトのディレクトリで実行します。

$ npm install -D sass-loader sass  return // パッケージをインストール

added 19 packages, and audited 312 packages in 1s

上記パッケージのインストール後の package.json は、node-sass と sass-loader が追加され以下のようになります。16〜18行目は Babel 用のモジュールで Sass や CSS のローダーとは関係ありません。

package.json(例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "sass": "^1.45.1",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

設定ファイル webpack.config.js を編集します。

test プロパティには処理対象のファイルを指定します。この例では正規表現で /\.(scss|sass|css)$/i と指定して、拡張子が .scss や.sass、css のファイルを処理対象にするようにしています。.css を対象としない場合は /\.s[ac]ss$/i, などとします。

use プロパティでは変換を行うために使用するローダーを指定します。use プロパティにはローダーを複数指定してチェインすることができ、指定したローダーが後ろから順番(sass-loader → css-loader → style-loader の順)に適用されます。

sass-loader を最後に追加したのは css-loader で CSS を JavaScript に変換する前に sass-loader で Sass を CSS に変換する必要があるためです。

webpack.config.js
module.exports = {
  entry: './src/index.js', // エントリポイントの設定
  output: { // 出力先の設定
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    // ローダーの設定
    rules: [
      {
        //CSS & SASS 用のローダー
        test: /\.(scss|sass|css)$/i, //拡張子 .scss、.sass、css を対象
        //使用するローダーを指定
        use: [
          // CSS を出力するローダー
          'style-loader',
          // CSS を JavaScript に変換するローダー
          'css-loader',
          // Sass をコンパイルするローダー
          'sass-loader',
        ],
      },
      {
        // Babel 用のローダー(拡張子 .js を対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに対するローダーを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
    ]
  },
};

webpack.config.js を変更後 npm run dev 等で webpack を実行します。

$ npm run dev  return // webpack を実行
// または npx webpack --mode=development

> myproject@1.0.0 dev
> webpack --mode development

asset main.js 25.5 KiB [emitted] (name: main)
・・・中略・・・
      ./src/style.scss 1.19 KiB [built] [code generated]
      ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/style.scss 475 bytes [built] [code generated]
    modules by path ./src/modules/*.js 244 bytes
      ./src/modules/foo.js 139 bytes [built] [code generated]
      ./src/modules/bar.js 105 bytes [built] [code generated]
    ./src/index.js 501 bytes [built] [code generated]
webpack 5.65.0 compiled successfully in 856 ms

ブラウザで index.html を開いて再読み込みすると Sass で p 要素へ設定したスタイルが確認できます。また、開発ツールで見ると style 要素が head 内に挿入されているのが確認できます。

index.html で読み込んでいるバンドルされた JavaScript main.js によりスタイルが挿入されています。

以下は css-loader と sass-loader のソースマップの出力を有効にし、Sass のアウトプットスタイルを compressed に指定する例です。

use での指定を文字列(css-loader、sass-loader)からオブジェクトでの記述に変更してオプション options を指定しています。

const path = require('path');

module.exports = {
  entry: './src/index.js', // エントリポイントの設定
  output: { // 出力先の設定
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    // ローダーの設定
    rules: [
      {
        //CSS & SASS 用のローダー
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        //使用するローダーを指定
        use: [
          'style-loader', // CSS を出力するローダー
          {
            loader: 'css-loader', // CSS を JavaScript に変換するローダー
            options: {
              sourceMap: true, // ソースマップを有効に
            },
          },
          {
            loader: 'sass-loader', // Sass をコンパイルするローダー
            options: {
              sourceMap: true, // ソースマップを有効に
              sassOptions: {
                outputStyle: 'compressed', // アウトプットスタイルの指定
              },
            }
          }
        ],
      },
      {
        // Babel 用のローダー(拡張子 .js を対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに対するローダーを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
    ]
  },
};

webpack.config.js を変更後 npm run dev 等で webpack を実行するとソースマップが有効になり、Sass のアウトプットスタイルが compressed(以下の場合、余計なスペースが削除されている)になります。

fibers

fibres というパッケージを使用することでコンパイルの速度を速くすることができる(sass-loader)ようですが、fibres のページには「廃止の注意-このプロジェクトの作者は、可能であればその使用を避けることをお勧めします」とあり、また、「node.js の v16.0.0 以降と互換性がありません」と書かれています。

webpack guides: sass-loader

関連ページ:webpack を使って Sass をコンパイルする方法

postcss-loader

PostCSS のプラグインを利用するには postcss-loader と postcss をインストールします。

$ npm install -D postcss-loader postcss  return

added 15 packages, and audited 328 packages in 692ms

webpack guides: postcss-loader

PostCSS Preset Env

PostCSS Preset Env は CSS の新しい仕様を後方互換性を持って変換したり、CSS プロパティに自動でベンダープレフィックスを付与することができるプラグインです。

PostCSS Preset Env には Autoprefixer が含まれているので、PostCSS Preset Env をインストールした場合は別途 Autoprefixer をインストールする必要はありません。

PostCSS Preset Env を利用するには postcss-loader と postcss に加えて postcss-preset-env をインストールします。

$ npm install -D postcss-preset-env  return

added 35 packages, and audited 363 packages in 2s
package.json(例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "postcss": "^8.4.5",
    "postcss-loader": "^6.2.1",
    "postcss-preset-env": "^7.2.0",
    "sass": "^1.45.1",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

webpack.config.js に設定を追加します。

css-loader と sass-loader の間に postcss-loader を追加します(css-loader や style-loader の前に使用しますが、sass などの他のプリプロセッサローダーの後に使用します)。

css-loader の設定では importLoaders オプションを追加して css-loader の前に適用されるローダーの数(2)を設定します(この設定は将来的には変更になる可能性があるようです)。

PostCSS の設定では、plugins に PostCSS Preset Env などのオプションを設定することができます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js', // エントリポイントの設定
  output: { // 出力先の設定
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    // ローダーの設定
    rules: [
      {
        //CSS & SASS 用のローダー
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        //使用するローダーを指定
        use: [
          // CSS を head 要素に挿入するローダー
          'style-loader',
          // CSS ローダー
          {
            loader: "css-loader",
            options: {
              // postcss-loader と sass-loader の場合は2を指定
              importLoaders: 2,
              // 0 => no loaders (default);
              // 1 => postcss-loader;
              // 2 => postcss-loader, sass-loader
              sourceMap: true,
            },
          },
          // PostCSS の設定
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      //必要に応じてオプションを指定
                      //stage: 0,
                      //browsers: 'last 2 versions',
                      //autoprefixer のオプション
                      //autoprefixer: { grid: true }
                    },
                  ],
                ],
              },
            },
          },
          // Sass をコンパイルするローダー
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true, // ソースマップを有効に
            }
          }
        ],
      },
      {
        // Babel 用のローダー(拡張子 .js を対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに対するローダーを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
    ]
  },
};

PostCSS Preset Env の使い方は PostCSS Preset Env を御覧ください。

Asset Modules

version 4 までは画像などのアセットファイルを読み込むには url-loader や file-loader などの追加のローダーをインストールして設定する必要がありましたが、version 5 からは Asset Modules が導入され、デフォルトで(ローダーを追加せずに)画像などのアセットファイルを扱えるようになっています。

そのため、url-loader や file-loader、raw-loader は version 5 では非推奨になっていて、代わりに Asset Modules を使うことが推奨されています。

url-loader や file-loader はローダーなので webpack の設定では use でローダーを指定してオプションを設定しますが、Asset Modules では type を指定して設定します。

Asset Module (type) 説明 webpack 4 の場合
asset/resource ファイルを生成(コピー)して出力 file-loader
asset/inline ファイルを Data URLs(Base64)に変換してバンドル url-loader
asset/source ファイルをテキスト(String)としてインポート raw-loader
asset サイズによりファイルを Data URLs に変換または生成 url-loader と limit

Asset Modules の機能を利用するには、webpack.config.js の module プロパティの rules で設定します。test にはローダーの設定同様、処理対象のファイルの拡張子を正規表現で指定し、type に上記のいずれかのタイプを指定します。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      ・・・
      //Asset Modules の設定
      {
        //対象とするアセットファイルの拡張子を正規表現で指定
        test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/i,
        //いずれかの type を指定
        type: 'asset/resource'
      },
      ・・・
    ]
  },
};

※ もし、file-loader、url-loader、raw-loader を webpack5 の AssetModule と一緒に使用する場合は、アセットファイルの重複が発生しないようにアセットのモジュールタイプ(type)を javascript/auto に設定して AssetModule がアセットを再度処理するのを停止します。

webpack.config.js
module.exports = {
  module: {
   rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader', //version 5 では非推奨
            options: {
              limit: 8192,
            }
          },
        ],
       type: 'javascript/auto' //Asset Module がアセットを再度処理するのを停止
      },
   ]
  },
}

Asset Modules を設定しない場合

Asset Modules を設定せずに、例えば CSS 関連のローダーを設定してあれば、画像ファイルなどは asset/resource を指定したのと同じようにコピーしてビルドディレクトリに出力されます(version 4 ではビルド時にエラーになりましたが)。ドキュメントには Asset Modules を設定しない場合について特に記載されていないようなので(?)、明示的に Asset Modules を設定した方が良いかと思います。

webpack guides: Asset Modules

asset/resource

type に asset/resource を指定すると、画像などのアセットファイルをコピーしてビルドディレクトリ(出力先に指定しているディレクトリ)に出力します。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.png/,  //対象とするアセットファイルの拡張子
        type: 'asset/resource'  // asset/resource を指定して画像をコピーして出力
      }
    ]
  },
};

エントリポイントで画像 /images/main.png をインポートして変数 mainImage に代入すると、ビルドする際にその画像が処理されて出力ディレクトリにコピーされ、変数 mainImage には処理後のその画像の URL が含まれます。

src/index.js
//画像をインポート
import mainImage from './images/main.png';

function component() {
  const divElem = document.createElement('div');
  const img = document.createElement('img');
  img.src = mainImage;  //インポートした画像を img 要素の src 属性に指定
  divElem.appendChild(img);  //div 要素に img 要素を追加
  return divElem;
}
document.body.appendChild(component());
$ npm run dev  return // webpack を実行

> myproject@1.0.0 dev
> webpack --mode development

asset main.js 5.53 KiB [compared for emit] (name: main)
asset 6162a314fe5f1082b847.png 2.36 KiB [emitted] [immutable] [from: src/images/main.png] (auxiliary name: main)
runtime modules 1.33 KiB 3 modules
cacheable modules 854 bytes (javascript) 2.36 KiB (asset)
  ./src/index.js 812 bytes [built] [code generated]
  ./src/images/main.png 42 bytes (javascript) 2.36 KiB (asset) [built] [code generated]
webpack 5.65.0 compiled successfully in 72 ms

出力先フォルダに main.png がコピーされ、デフォルトではハッシュ値を使った名前で出力されます。

myProject
├── dist
│   ├── 6162a314fe5f1082b847.png  //ビルドでコピーされて出力される画像ファイル
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   └── main.png  //インポートする画像を配置
│   └── index.js
└── webpack.config.js
dist/index.html
<html>
<head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
  </head>
  <body>
    <script src="main.js"></script>
  <div>
    <!--コピーされた画像-->
    <img src="http://localhost/myProject/dist/6162a314fe5f1082b847.png">
  </div>
</body></html>

CSS の画像 css-loader

css-loader を使用する場合も、CSS 内の背景画像の url('./images/main.png') などに対して同様のプロセスが発生します。

以下は CSS 用のローダーも設定してある場合の例です。この例では Asset Modules の設定の test プロパティでは png に加えて jpeg などの拡張子も追加しています。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //モジュールの設定
  module: {
    rules: [
      //CSS 及び SASS 用のローダー
      {
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        //使用するローダーを指定
        use: [
          'style-loader', // CSS を出力するローダー
          {
            loader: 'css-loader', // CSS を JavaScript に変換するローダー
            options: {
              sourceMap: true, // ソースマップを有効に
            },
          },
          {
            loader: 'sass-loader', // Sass をコンパイルするローダー
            options: {
              sourceMap: true, // ソースマップを有効に
              sassOptions: {
                outputStyle: 'compressed', // アウトプットスタイルの指定
              },
            }
          }
        ],
      },
      //Asset Modules の設定
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i, //対象とするアセットファイルの拡張子
        type: 'asset/resource'  // asset/resource を指定して画像をコピーして出力
      },
    ]
  },
};
src/index.js
//CSS をインポート
import  './style.css';

const divElem = document.createElement('div');
document.body.appendChild(divElem);
src/style.css
div {
  background-image: url("./images/main.png");  /*背景画像を設定*/
  background-repeat: no-repeat;
  width: 600px;
  height: 400px;
}

ビルドを実行すると、前述の例同様、出力先フォルダに main.png がコピーされ、ハッシュ値を使った名前で出力されます。

$ npm run dev  return // webpack を実行

> myproject@1.0.0 dev
> webpack --mode development

  ・・・中略・・・
  modules by path ./src/ 3.25 KiB
    ./src/index.js 748 bytes [built] [code generated]
    ./src/style.css 1.3 KiB [built] [code generated]
    ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./src/style.css 1.22 KiB [built] [code generated]
./src/images/main.png 42 bytes (javascript) 2.36 KiB (asset) [built] [code generated]
webpack 5.65.0 compiled successfully in 557 ms
myProject
├── dist
│   ├── 6162a314fe5f1082b847.png  //ビルドでコピーされて出力される画像ファイル
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   └── main.png  //背景画像に使用するファイルを配置
│   ├── index.js
│   └── style.css
└── webpack.config.js
dist/index.html
<html>
<head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
  </head>
  <body>
    <script src="main.js"></script>
    <div></div><!-- 背景画像が指定された div 要素 -->
</body>
</html>
出力先の変更

デフォルトではファイル名はFile-level のテンプレート文字列 [hash][ext][query] が適用されて output プロパティで指定した出力先フォルダ(ビルドディレクトリ)に出力されます。

  • [hash]:ハッシュ値
  • [ext] :.(ドット)から始まる拡張子
  • [query] :? から始まるクエリ文字(付与されていれば)

出力先やファイル名は、output プロパティの assetModuleFilename オプションや rules プロパティの generator.filename オプションで変更することができます。

output.assetModuleFilename

以下は output プロパティの assetModuleFilename(9行目)に出力先とファイル名を指定する例です。

以下ではファイル名にデフォルトの [hash] の代わりに [name] を指定しているので、 [name] には画像のファイル名(拡張子を除く)、[ext] には拡張子、[query] にはクエリ文字(もしあれば)が入ります。

また、images/ を指定しているので、output の path で指定した dist ディレクトリの中の images ディレクトリ(dist/images)に出力されます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    //Asset Modules の出力先の指定
    assetModuleFilename: 'images/[name][ext][query]'
  },
  //モジュールの設定
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              sassOptions: {
                outputStyle: 'compressed',
              },
            }
          }
        ],
      },
      //Asset Modules の設定
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i, //対象とするアセットファイルの拡張子
        type: 'asset/resource'  // 画像をコピーして出力
      },
    ]
  },
};

例えば、前述の例で webpack.config.js を上記のように変更すると、以下のように出力先のフォルダ dist の中の images フォルダにファイル名と拡張子を使った名前で出力されます。

myProject
├── dist
│   ├── images
│   │   └── main.png  //ビルドでコピーされて出力される画像ファイル
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   └── main.png  //背景画像に使用するファイルを配置
│   ├── index.js
│   └── style.css
└── webpack.config.js

rules.generator.filename

Rule.generator.filename は output.assetModuleFilename と同じですが、type が sset/resource または asset でのみ有効です。

以下は rules の generator プロパティに filename を指定(37〜40行目)する例で、前述の例と同じ結果になります。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //モジュールの設定
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              sassOptions: {
                outputStyle: 'compressed',
              },
            }
          }
        ],
      },
      //Asset Modules の設定
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i, //対象とする画像ファイルの拡張子
        type: 'asset/resource',  // 画像をコピーして出力
        generator: {
          //出力先を指定(images フォルダにファイル名と拡張子で出力)
          filename: 'images/[name][ext][query]'
        }
      },
    ]
  },
};

webpack guides: Custom output filename

フォントのロード

Asset Modules はフォントを含むあらゆる種類のファイルに使用でき、ロードしたファイルを取得して出力先に指定したビルドディレクトリに出力します。

以下は src/fonts フォルダに保存したフォントファイルを使用する例で、フォントファイル用の Asset Modules の設定を追加しています(画像とフォントをまとめて設定することもできます)。

以下の設定の場合、画像は assetModuleFilename で指定した images フォルダに、フォントは generator の filename で指定した fonts フォルダに出力されます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    //images フォルダにファイル名と拡張子で出力
    assetModuleFilename: 'images/[name][ext][query]'
  },
  //モジュールの設定
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/i,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            }
          }
        ],
      },
      //Asset Modules の設定(画像ファイル)
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i, //対象とする画像ファイルの拡張子
        type: 'asset/resource',  // 画像をコピーして出力
      },
      //Asset Modules の設定(フォントファイル)
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i, //対象とするフォントファイルの拡張子
        type: 'asset/resource', // フォントをコピーして出力
        generator: {
          //出力先を指定(fonts フォルダにファイル名と拡張子で出力)
          filename: 'fonts/[name][ext][query]'
        }
      },
    ]
  },
};

フォントファイル を src/fonts フォルダに配置します。

myProject
├── dist
│   ├── fonts
│   │   └── Rock3D-Regular.ttf  //ビルドでコピーされて出力されるフォントファイル
│   ├── images
│   │   └── main.png  //ビルドでコピーされて出力される画像ファイル
│   ├── index.html
│   └── main.js  //ビルド時にバンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── fonts
│   │   └── Rock3D-Regular.ttf  //@font-face で指定するフォントファイルを配置
│   ├── images
│   │   └── main.png  //背景画像に使用するファイルを配置
│   ├── index.js
│   └── style.css
└── webpack.config.js

Asset Modules を設定し、フォントファイルを配置したら、CSS で @font-face を使ってフォントの設定をします。src の url(...) は画像の場合と同様、ビルド時に webpack によって取得されます。

src/style.css
@font-face {
  font-family: 'MyFont';
  font-style: normal;
  font-weight: 400;
  src: url('./fonts/Rock3D-Regular.ttf') format('truetype');
}

div {
  font-size: 60px;
  color: yellow;
  font-family: 'MyFont'; /* 上記で設定したフォントを指定 */
  text-align: center;
  background-image: url("./images/main.png");
  background-repeat: no-repeat;
  width: 600px;
  height: 400px;
}

index.js では div 要素にテキストを設定します。

src/index.js
import  './style.css';

const divElem = document.createElement('div');
divElem.textContent = "Hello!";  //div 要素にテキストを設定
document.body.appendChild(divElem);

ビルドを実行します。

$ npm run dev  return // webpack を実行

> myproject@1.0.0 dev
> webpack --mode development

asset fonts/Rock3D-Regular.ttf 434 KiB [compared for emit] [from: src/fonts/Rock3D-Regular.ttf] (auxiliary name: main)
asset main.js 32 KiB [compared for emit] (name: main)
asset images/main.png 2.36 KiB [compared for emit] [from: src/images/main.png] (auxiliary name: main)
runtime modules 2.39 KiB 7 modules
・・・中略・・・
asset modules 84 bytes (javascript) 436 KiB (asset)
  ./src/fonts/Rock3D-Regular.ttf 42 bytes (javascript) 434 KiB (asset) [built] [code generated]
  ./src/images/main.png 42 bytes (javascript) 2.36 KiB (asset) [built] [code generated]
webpack 5.65.0 compiled successfully in 558 ms

ブラウザで index.html を開いて再読み込みすると、指定したフォントで div 要素の文字(Hello!)表示されているのが確認できます。

webpack guides: Loading Fonts

asset/inline

type に asset/inline を指定すると、画像などのアセットファイルを Data URLs(Base64)に変換してバンドルします。

画像をバンドルすることでリクエスト回数を減らすことができますが、大きなサイズの画像はデータが大きくなり過ぎてかえって逆効果になる場合もあります。

Base64 でエンコードされたバイナリデータの最終サイズは、元のデータサイズの1.37倍になるようです。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext][query]'
  },
  //モジュールの設定
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/i,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            }
          }
        ],
      },
      //Asset Modules の設定(画像ファイル)
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        // asset/inline を指定して画像を Data URLs(Base64)に変換
        type: 'asset/inline',
      },
    ]
  },
};
myProject
├── dist
│   ├── index.html
│   └── main.js
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   └── main.png  //背景画像に使用するファイル → Data URLs(Base64)に変換される
│   ├── index.js
│   └── style.css
└── webpack.config.js
src/index.js
import  './style.css';

const divElem = document.createElement('div');
divElem.textContent = "Hello!";
document.body.appendChild(divElem);
src/style.css
div {
  background-image: url("./images/main.png");
  background-repeat: no-repeat;
  width: 600px;
  height: 400px;
  color: yellow;
  text-align: center;
  font-size: 60px;
}
$ npm run dev  return // webpack を実行

・・・中略・・・
  modules by path ./src/ 3.33 KiB
    ./src/index.js 780 bytes [built] [code generated]
    ./src/style.css 1.3 KiB [built] [code generated]
    ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./src/style.css 1.26 KiB [built] [code generated]
./src/images/main.png 3.2 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 372 ms

ブラウザで index.html を開いてインスペクタを見ると、画像(main.png)が url(data:image/png;base64,iVBORw0KGgoAAAA ...) のように base64 でエンコードされて埋め込まれているのが確認できます。

webpack guides: Inlining assets

asset/source

type に asset/source を指定すると、ファイルをテキスト(String)としてインポートすることができます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext][query]'
  },
  //モジュールの設定
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/i,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            }
          }
        ],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      //Asset Modules の設定(テキストファイル)
      {
        //対象とするアセットファイルの拡張子に .txt を指定
        test: /\.txt/,
        // asset/source を指定
        type: 'asset/source',
      },
    ]
  },
};
myProject
├── dist
│   ├── index.html
│   └── main.js
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   └── main.png
│   ├── index.js
│   ├── example.txt  //テキストファイル
│   └── style.css
└── webpack.config.js
src/example.txt
Hello ! This is sample text.
src/index.js
import exampleText from './example.txt';  //テキストファイルをインポート
import  './style.css';

const divElem = document.createElement('div');
divElem.textContent = exampleText;   //読み込んだテキストを textContent に設定
document.body.appendChild(divElem);
src/style.css
div {
  background-image: url("./images/main.png");
  background-repeat: no-repeat;
  width: 600px;
  height: 400px;
  color: yellow;
  text-align: center;
  font-size: 60px;
}
$ npm run dev  return // webpack を実行

・・・中略・・・
  ./src/example.txt 40 bytes [built] [code generated]
  ./src/images/main.png 42 bytes (javascript) 2.36 KiB (asset) [built] [code generated]
webpack 5.65.0 compiled successfully in 376 ms

ブラウザで index.html を開いて再読み込みすると読み込んだテキストが表示されているのが確認できます。

webpack guides: Source assets

asset(General asset type)

type に asset を指定すると、webpack はファイルのサイズにより asset/resource と asset/inline のどちらかを自動的に選択します。

サイズが 8kb 未満のファイルは、inline として扱われ、それ以外の場合は resource として扱われます。

この条件(サイズの閾値)を変更するには、parser.dataUrlCondition.maxSize オプションを設定します。

以下は maxSize に 2 * 1024 を指定して、画像のファイルサイズが 2kb 未満の場合は Data URLs に変換し、2kb 以上の場合はファイルをコピーして assetModuleFilename で指定したファイル名で出力する例です。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext][query]'
  },
  //モジュールの設定
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/i,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            }
          }
        ],
      },
      //Asset Modules の設定
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset',  //ファイルサイズにより Data URLs に変換または生成
        parser: {
          dataUrlCondition: {
            maxSize: 2 * 1024 // 2kb 未満の場合は Data URLs に変換
          }
        }
      },
    ]
  },
};

この例では 2kb 未満と 2kb 以上の2つの画像を使用します。

myProject
├── dist
│   ├── index.html
│   ├── main.png  //ビルドでコピーされて出力される画像ファイル
│   └── main.js
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   ├── bg.png  //2kb 未満 → Data URLs(Base64)に変換される
│   │   └── main.png  //2kb 以上→ コピーされて出力される
│   ├── index.js
│   └── style.css
└── webpack.config.js

src/index.js
import  './style.css';

const divElem = document.createElement('div');
const p = document.createElement('p');
p.textContent = 'Hello! This is sample text.';
divElem.appendChild(p);
document.body.appendChild(divElem);
src/style.css
div {
  background-image: url("./images/main.png"); /* 2kb 以上の画像 */
  background-repeat: no-repeat;
  width: 600px;
  height: 400px;
}

p {
  background-image: url("./images/bg.png"); /* 2kb 未満の画像 */
  background-repeat: repeat;
  color: yellow;
  font-size: 30px;
  text-align: center;
  width: 500px;
  height: 100px;
  margin: auto;
  padding-top: 75px;
}
$ npm run dev  return // webpack を実行

・・・中略・・・
asset main.js 33.4 KiB [emitted] (name: main)
asset images/main.png 2.36 KiB [compared for emit] [from: src/images/main.png] (auxiliary name: main)
・・・中略・・・
  ./src/images/main.png 42 bytes (javascript) 2.36 KiB (asset) [built] [code generated]
  ./src/images/bg.png 1.86 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 375 ms

ブラウザで index.html を開いて再読み込みすると、2kb 未満の画像(bg.png)は Data URLs(Base64)に変換され、2kb 以上の画像(main.png)はコピーされて出力されているのが確認できます。

webpack guides: General asset type

Plugins プラグイン

プラグインは webpack のバックボーンのようなもので、 Webpack 自体もプラグインシステム上に構築されています。また、プラグインは、ローダーが実行できないことを実行するためのものでもあります。

webpack guides: Concept/Plugins

プラグインを使用するには、webpack.config.js で require() を使ってプラグインを読み込み、plugins プロパティの配列に追加します。

プラグインの指定では、 new 演算子でプラグインのインスタンスを生成します。ほとんどのプラグインは引数にオプションを指定することができます。

MiniCssExtractPlugin

style-loader は CSS を head 要素(DOM)に挿入しますが、MiniCssExtractPlugin(mini-css-extract-plugin)を使うと CSS を抽出して別ファイルとして出力することができます。

以下は MiniCssExtractPlugin を使って CSS を別ファイルとして出力する例です。この例では Sass を CSS に変換後別ファイルとして抽出しています。

ファイル構成は以下のようになっています。

ファイル構成
myProject
├── dist
│   ├── index.html  //ブラウザで表示するファイル(script タグで main.js を読み込む)
│   └── main.js //ビルドされて出力されるファイル
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //import 文を使って style.scss を読み込む
│   └── style.scss   //Sass
└── webpack.config.js

MiniCssExtractPlugin は CSS を JavaScript から抽出して別ファイルとして出力するので、index.html では link タグを使って出力される CSS(style.css)を読み込みます。

この時点では style.css は生成されていないので、ブラウザで表示してもスタイルは適用されていません。

dist/index.html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My First webpack</title>
    <link rel="stylesheet" href="style.css"><!-- 追加 -->
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>
src/index.js
import  './style.scss';  //Sass をインポート

const divElem = document.createElement('div');
const p = document.createElement('p');
p.textContent = 'Hello! This is sample text.';
divElem.appendChild(p);
document.body.appendChild(divElem);
src/style.scss(Sass ファイル)
$color: #5CD1F1;
$bg_color: #FAD4D4;

div {
  background-color: $bg_color;
  width: 600px;
  height: 400px;
}
p {
  color:  $color;
}

MiniCssExtractPlugin(mini-css-extract-plugin)のインストール

この例では既に css-loader と sass-loader はインストール済みなので、mini-css-extract-plugin を追加でインストールします。

$ npm install -D mini-css-extract-plugin  return

added 9 packages, and audited 321 packages in 2s

上記実行後の package.json は、mini-css-extract-plugin が追加されます(20行目)。

package.json(例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "mini-css-extract-plugin": "^2.4.5",
    "sass": "^1.45.1",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

設定ファイル webpack.config.js を編集します。

require() を使ってプラグイン MiniCssExtractPlugin(mini-css-extract-plugin)を読み込みます。

plugins プロパティに new 演算子で MiniCssExtractPlugin プラグインのインスタンスを生成し、引数に filename プロパティで出力される CSS のファイル名を指定します。

引数の filename プロパティ(ファイル名)の指定を省略した場合はデフォルトの '[name].css' が適用され、entry で key(エントリ名)として指定した文字列が [name] に使用されます。

ローダーには style-loader の代わりに MiniCssExtractPlugin.loader を指定し、CSS を抽出して出力するようにします。以下が大まかなローダーの処理の流れです。

  1. sass-loader を使って Sass を CSS へ変換
  2. css-loader で CSS を JavaScript に変換
  3. MiniCssExtractPlugin.loader で CSS を 抽出して別ファイルとして出力
webpack.config.js
const path = require('path');
// require() を使って MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext][query]' //Asset Modules(必要に応じて)
  },
  //プラグインの設定(plugins プロパティの配列に追加)
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'style.css',  //ファイル名を指定
    }),
  ],
  //モジュールの設定
  module: {
    rules: [
      //CSS 及び SASS 用のローダー
      {
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        //使用するローダーを指定
        use: [
          // CSSファイルを抽出するように MiniCssExtractPlugin のローダーを指定
          MiniCssExtractPlugin.loader,
          //'style-loader', //この場合は必要ないので削除
          // CSS を JavaScript に変換するローダー(ソースマップを有効に)
          { loader: 'css-loader', options: { sourceMap: true } },
          // Sass をコンパイルするローダー(ソースマップを有効に)
          { loader: 'sass-loader', options: { sourceMap: true } },
        ],
      },
      //Asset Modules の設定(必要に応じて)
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource'  // 画像をコピーしてビルドディレクトリに出力
      },
    ]
  },
};

webpack.config.js を設定後 npm run dev 等で webpack を実行します。

$ npm run dev  return // webpack を実行

> myproject@1.0.0 dev
> webpack --mode development

asset main.js 4.08 KiB [emitted] (name: main)
asset style.css 595 bytes [emitted] (name: main)
Entrypoint main 4.67 KiB = style.css 595 bytes main.js 4.08 KiB
orphan modules 3.76 KiB (javascript) 937 bytes (runtime) [orphan] 7 modules
runtime modules 274 bytes 1 module
cacheable modules 933 bytes (javascript) 94 bytes (css/mini-extract)
  ./src/index.js 883 bytes [built] [code generated]
  ./src/style.scss 50 bytes [built] [code generated]
  css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./src/style.scss 94 bytes [built] [code generated]
webpack 5.65.0 compiled successfully in 500 ms

問題なく実行されれば、Sass から CSS に変換された style.css が dist フォルダに出力されます。ブラウザで index.html を確認するとスタイルが適用されているはずです。

プロダクションビルドと開発モードで style-loader と使い分ける

以下はプロダクションビルドでは MiniCssExtractPlugin を使用し、開発モード(webpack-dev-server を含む)では、style-loader を使用する設定の例です。

この方法を使えば、プロダクションビルドの場合はバンドルから CSS を抽出して個別の CSS ファイルを作成し、開発モード(webpack-dev-server を含む)の場合は style-loader を使用して CSS を DOM に挿入することで高速に動作します(Recommend)。

※ 但し、style-loader と mini-css-extract-plugin を同時に使用することはできません。

5行目で変数 devMode を宣言し、process.env.NODE_ENV の値が production でない場合は true が格納されるようにしています。

16行目は devMode が true の場合は空の配列を、false の場合は MiniCssExtractPlugin を設定します。

27行目は devMode が true の場合は style-loader を、false の場合は MiniCssExtractPlugin をローダーとして使用します。

そしてビルドを実行する際に、プロダクションビルドの場合はパラメータ(NODE_ENV=production)を渡して webpack を実行し、MiniCssExtractPlugin を使用します。

webpack.config.js
const path = require('path');
// MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 変数 devMode に開発モードの場合は true、プロダクションの場合は false を代入
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext][query]' //Asset Modules(必要に応じて)
  },

  //プラグインの設定(devMode が false の場合は MiniCssExtractPlugin を設定)
  plugins: [].concat(devMode ? [] : [new MiniCssExtractPlugin({filename: 'style.css'})]),

  //モジュールの設定
  module: {
    rules: [
      //CSS 及び SASS 用のローダー
      {
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        //使用するローダーを指定
        use: [
          //devModeがtrueの場合はstyle-loaderを、falseの場合はMiniCssExtractPluginを使用
          devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
          // CSS を JavaScript に変換するローダー(ソースマップを有効に)
          { loader: 'css-loader', options: { sourceMap: true } },
          // Sass をコンパイルするローダー(ソースマップを有効に)
          { loader: 'sass-loader', options: { sourceMap: true } },
        ],
      },
      //Asset Modules の設定(必要に応じて)
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource'  // 画像をコピーしてビルドディレクトリに出力
      },
    ]
  },
};

NODE_ENV=production を渡して webpack を実行すると、MiniCssExtractPlugin が使われ、CSS を抽出して別ファイルとして出力します。パラメータを渡さない場合は style-loader を使用して CSS を DOM に挿入します。以下の場合、パラメータ NODE_ENV=production を渡しているので、モードはどちらでも MiniCssExtractPlugin が使われます。

// パラメータを渡して webpack を実行
$ NODE_ENV=production npx webpack --mode production return

asset main.js 214 bytes [emitted] [minimized] (name: main) 1 related asset
asset style.css 108 bytes [emitted] (name: main) 1 related asset
・・・中略・・・
webpack 5.65.0 compiled successfully in 449 ms

以下のように package.json の scripts フィールドに NODE_ENV を指定したビルドコマンドを追加しておくこともできます。

package.json 抜粋
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "NODE_ENV=production webpack --mode production",
    "dev": "NODE_ENV=development webpack --mode development",
    "watch": "webpack --mode development --watch"
  },

関連項目:Node の環境変数を渡す

CSS ファイルを entry として指定

必要であれば、エントリポイント(src/index.js)で CSS や Sass ファイルを読み込む(インポートする)代わりに、entry として指定することもできます。

src/index.js
//import  './style.scss';  //インポートせずに entry として指定
const divElem = document.createElement('div');
const p = document.createElement('p');
p.textContent = 'Hello! This is sample text.';
divElem.appendChild(p);
document.body.appendChild(divElem);
webpack.config.js
const path = require('path');
// MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  //entry として CSS (Sass) も指定
  entry: ['./src/index.js', './src/style.scss'],
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext][query]' //Asset Modules(必要に応じて)
  },
  //プラグインの設定(plugins プロパティの配列に追加)
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'style.css',  //ファイル名を指定
    }),
  ],
  //モジュールの設定
  module: {
    ・・・省略(前述の例と同じ)・・・
  },
};

webpack guides: MiniCssExtractPlugin

CssMinimizerWebpackPlugin

CSS は JavaScript ファイルとは異なり、モードを production に指定しても圧縮(minify)されません。CSS を圧縮して出力するには css-minimizer-webpack-plugin というプラグインを使用します。

※ v4 で使用していた optimize-css-assets-webpack-plugin は v5 では使えません(非推奨)。

css-minimizer-webpack-plugin を使用するには、まずプラグインをインストールします。

$ npm install -D css-minimizer-webpack-plugin  return

added 62 packages, and audited 383 packages in 4s

上記のモジュールインストール後の package.json は、css-minimizer-webpack-plugin が追加され(20行目)以下のようになります。

package.json (例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "css-minimizer-webpack-plugin": "^3.3.1",
    "mini-css-extract-plugin": "^2.4.5",
    "sass": "^1.45.1",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

設定ファイル webpack.config.js を編集します。

インストールしたプラグイン css-minimizer-webpack-plugin を require() で読み込みます(4行目)。

optimization プロパティの minimizer プロパティで読み込んだプラグインを new 演算子で初期化する際にオプションを指定します。この例では parallel オプション(ビルド速度を向上)を設定しています。

optimization.minimizer を設定することで、デフォルトの圧縮方法を変更することができます。

minimizer プロパティを指定するとデフォルトの JavaScript の圧縮にも影響があるため、23行目の `...` は JavaScript の圧縮に使用する既存(デフォルト)の設定を適用するための記述です(省略するとプロダクションビルドの際に JavaScript が圧縮されません)。

21行目の optimization.minimize では true を指定すると、モードに関わらず常に圧縮(minify)します。デフォルトは production モード(プロダクションビルド)の場合のみ圧縮します。

webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// css-minimizer-webpack-plugin を読み込み変数 CssMinimizerPlugin に代入
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext][query]' //Asset Modules(必要に応じて)
  },
  //プラグインの設定
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'style.css',
    }),
  ],
  //圧縮(minify)の設定
  optimization: {
    //minimize: true,  //モードに関わらず常に圧縮を有効にする場合は指定
    minimizer: [
      `...`,  //JavaScript の圧縮を有効に(デフォルトの圧縮の設定を適用)
      new CssMinimizerPlugin({
        parallel: true,  //ビルド速度を向上
      }),
    ],
  },
  //モジュールの設定
  module: {
    rules: [
      //CSS 及び SASS 用のローダー
      {
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        //使用するローダーを指定
        use: [
          // CSSファイルを抽出するように MiniCssExtractPlugin のローダーを指定
          MiniCssExtractPlugin.loader,
          // CSS を JavaScript に変換するローダー(ソースマップを有効に)
          { loader: 'css-loader', options: { sourceMap: true } },
          // Sass をコンパイルするローダー(ソースマップを有効に)
          { loader: 'sass-loader', options: { sourceMap: true } },
        ],
      },
      //Asset Modules の設定(必要に応じて)
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource'  // 画像をコピーしてビルドディレクトリに出力
      },
    ]
  },
};

webpack.config.js を変更後 npm run build(または npx webpack --mode=production)で webpack を実行すると、圧縮(minify)された CSS が出力されます。

$ npm run build  return // webpack を production モードで実行
//または npx webpack --mode=production

> myproject@1.0.0 build
> webpack --mode production

asset main.js 214 bytes [emitted] [minimized] (name: main) 1 related asset
asset style.css 107 bytes [emitted] [minimized] (name: main) 1 related asset
・・・中略・・・
webpack 5.65.0 compiled successfully in 1098 mse

npm run dev(または npx webpack --mode=development)を実行すると CSS とバンドルされて出力される main.js は圧縮されません。development でも圧縮するには上記 webpack.config.js の21行目のコメントを外します。

HtmlWebpackPlugin

HtmlWebpackPlugin は webpack でバンドルされた JavaScript や CSS を表示する HTML を自動的に生成するプラグインです。生成される HTML にはバンドルされた JavaScritp や CSS を読み込む script や link タグが自動的に埋め込まれています。

オプションで webpack で生成される JavaScript や CSS のファイル名の最後にハッシュを追加することができ、簡単にブラウザキャッシュの更新ができます。

また、title や meta タグなどを設定したり、独自の HTML をテンプレートに指定することもできます。

以下は HtmlWebpackPlugin を使って自動的に HTML ファイルを生成する例です。

以下がファイル構成です。

ファイル構成
myProject
├── dist
│   ├── //index.html ★ビルド時に HtmlWebpackPlugin で自動的に生成される
│   └── //main.js ★ビルド時に出力されるバンドルされたファイル
│   └── //style.css  ★ビルド時に MiniCssExtractPlugin で抽出されて出力される CSS
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //エントリポイント(import で style.scss と foo.js を読み込む)
│   ├── modules
│   │   └── foo.js  //モジュール
│   └── style.scss   //Sass
└── webpack.config.js

HtmlWebpackPlugin をインストールします。

$ npm install -D html-webpack-plugin  return //インストール

added 22 packages, and audited 405 packages in 3s

上記実行後の package.json は、html-webpack-plugin が追加され(21行目)ます。

package.json(例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "css-minimizer-webpack-plugin": "^3.3.1",
    "html-webpack-plugin": "^5.5.0",
    "mini-css-extract-plugin": "^2.4.5",
    "sass": "^1.45.1",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

設定ファイル webpack.config.js を編集し、HtmlWebpackPlugin プラグインの設定を追加します。

require() を使ってプラグイン html-webpack-plugin を読み込みます(5行目)。

plugins プロパティに new 演算子で HtmlWebpackPlugin プラグインのインスタンスを生成します。必要に応じて引数にオプションを指定することができます(20行目)。

webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
//html-webpack-plugin の読み込み
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext][query]' //Asset Modules(必要に応じて)
  },
  //プラグインの設定
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'style.css',
    }),
    //HtmlWebpackPlugin プラグイン(インスタンスを生成)
    new HtmlWebpackPlugin(),
  ],
  //圧縮(minify)の設定(必要に応じて)
  optimization: {
    minimizer: [
      `...`,
      new CssMinimizerPlugin({
        parallel: true,
      }),
    ],
  },
  //モジュールの設定
  module: {
    rules: [
      //CSS 及び SASS 用のローダー(必要に応じて)
      {
        test: /\.(scss|sass|css)$/i,  //拡張子 .scss、.sass、css を対象
        //使用するローダーを指定
        use: [ MiniCssExtractPlugin.loader, 'css-loader','sass-loader'],
      },
      // Babel 用のローダー(必要に応じて)
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: { presets: [ '@babel/preset-env' ] }
          }
        ]
      },
      //Asset Modules の設定(必要に応じて)
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource'
      },
    ]
  },
  //ソースマップファイルの出力(必要に応じて)
  devtool: 'source-map'
};

webpack.config.js を変更後 npm run dev で webpack を実行します。

html-webpack-plugin
$ npm run dev  return // webpack を実行
// または npx webpack --mode=development

> myproject@1.0.0 dev
> webpack --mode development

asset main.js 3.91 KiB [emitted] (name: main) 1 related asset
asset style.css 505 bytes [emitted] (name: main) 1 related asset
asset index.html 271 bytes [emitted]
・・・中略・・・
webpack 5.65.0 compiled successfully in 1061 ms 

webpack を実行すると、output プロパティで指定したビルドィレクトリに以下のような index.html が自動的に生成され、バンドルされた JavaScript ファイル(main.js)が自動的に読み込まれています。

この例では MiniCssExtractPlugin を使用しているので、抽出されて出力された CSS(style.css)が link タグで自動的に読み込まれています。style-loader を使用している場合は、link タグは生成されず、CSS は main.js により head 要素に挿入されます。

dist/index.html (適当に改行しています)
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script defer src="main.js"></script>
    <link href="style.css" rel="stylesheet"><!--MiniCssExtractPluginで抽出されたCSS-->
  </head>
  <body></body>
</html>
  • 複数のエントリポイントがある場合、それらは全て生成された HTML ファイルの script タグで読み込まれます。
  • Webpack の出力に CSS アセットがある場合(例:MiniCssExtractPlugin で抽出された CSS)、それらは HTML ファイルの link タグで読み込まれます。
オプション

webpack.config.js の plugins プロパティでオプションを指定することができます。以下は指定できるオプションの一部です。

指定できるオプションの一部抜粋
オプション名 デフォルト値(型) 説明
title Webpack App
(文字列)
HTML の <title> 要素の値
filename 'index.html'
(文字列)
生成する HTML ファイル名(パス)
template ''(文字列) テンプレートとして使用する HTML ファイルのパスを指定。デフォルトでは src/index.ejs が存在すればそのファイルを使用(template option
favicon ''(文字列) ファビコンのパスを指定
meta {} (オブジェクト) 指定した meta タグを HTML に挿入。
minify モードによる
(真偽値)
production の場合は true でミニファイされ、development の場合は false でミニファイされない
hash false(真偽値) true の場合、含まれている全てのスクリプトと CSS ファイルに一意の webpack コンパイルハッシュを追加します。ブラウザのキャッシュを更新できます。
chunks ?(文字列) 特定の chunk のみを含む場合に指定。例:chunks: ['app']。複数ある場合はカンマ区切りで指定。
excludeChunks ''(文字列) 特定の chunk を除外する場合に指定。例:chunks: ['app']。複数ある場合はカンマ区切りで指定。

html-webpack-plugin document: Options

オプションは new HtmlWebpackPlugin() の引数に指定します。

plugins: [
  new HtmlWebpackPlugin({
    オプション名:値,
    オプション名:値,
    ・・・
  }),
],

filename と title の設定

以下は生成される HTML のファイル名(filename)を html/index.html、title を 'My Project X' に設定する例です。

webpack.config.js 一部抜粋
plugins: [
  //title と filename オプションを指定
  new HtmlWebpackPlugin({
    title: 'My Project X',
    filename: 'html/index.html'
  }),
],

webpack コマンドを実行すると、この例の場合、filename に 'html/index.htmldist' を指定しているので、ビルドディレクトリ内に html フォルダが自動的に作成され、その中に index.html という HTML ファイルが生成されます。

myProject
├── dist
│   ├── html
│   │   └── index.html  //生成された HTML
│   └── main.js
│   └── style.css
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //エントリポイント
│   ├── modules
│   │   └── foo.js
│   └── style.scss
└── webpack.config.js 

また、生成される HTML ファイルでは link 及び script タグの読み込みのファイルのパスも更新されます。

dist/html/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My Project X</title><!-- titleプロパティで指定したタイトル -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script defer src="../main.js"></script>
    <link href="../style.css" rel="stylesheet">
  </head>
  <body></body>
</html>

meta の設定

以下は生成される HTML のファイルに meta タグを設定する例です。それぞれの meta タグをオブジェクトの配列で指定できます。

webpack.config.js 一部抜粋
plugins: [
  new HtmlWebpackPlugin({
    title: 'My Project X',
    filename: 'html/index.html',
    //meta オプションを指定
    meta: [
      {'http-equiv': 'X-UA-Compatible', content: 'IE=edge'},
      {'name': 'description', content: 'My First HtmlWebpackPlugin'}
    ]
  }),
],

以下のように指定した meta タグが追加されます。

dist/html/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My Project X</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- 追加 -->
    <meta name="description" content="My First HtmlWebpackPlugin"><!-- 追加 -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script defer src="../main.js"></script>
    <link href="../style.css" rel="stylesheet">
  </head>
  <body></body>
</html>

hash の設定

オプションに hash: true を指定すると、webpack で生成される JavaScript や CSS のファイル名の最後に ? に続けてハッシュ(hash)を追加することができます。

webpack.config.js 一部抜粋
plugins: [
  new HtmlWebpackPlugin({
    title: 'My Project X',
    filename: 'html/index.html',
    meta: [
      {'http-equiv': 'X-UA-Compatible', content: 'IE=edge'},
      {'name': 'description', content: 'My First HtmlWebpackPlugin'}
    ],
    //ハッシュの値をファイル名に追加
    hash: true
  }),
],

以下のようにファイル名の最後に webpack が生成するハッシュの値が付加されます。

ビルドする際に差分がない場合は、ハッシュの値は変わりません。

dist/html/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My Project X</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="description" content="My First HtmlWebpackPlugin">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- webpack が生成するハッシュの値が末尾に追加される -->
    <script defer src="../main.js?1900e080aa697aa1972b"></script>
    <link href="../style.css?1900e080aa697aa1972b" rel="stylesheet">
  </head>
  <body></body>
</html>

template の設定

自動的に生成される HTML(default template)の代わりに独自のテンプレートを用意して使用することもできます。デフォルトでは lodash の記述方法が使えます(lodash template)。

また、webpack.config.js で設定したオプションの値を「htmlWebpackPlugin.options.オプション名 」でテンプレートで参照することができます。

独自のテンプレートを使用するには webpack.config.js のオプション template でテンプレートとして使用する HTML ファイルのパスを指定します。

以下は独自のテンプレートを作成して使用する例です。

src ディレクトリ内に template ディレクトリを新たに作成して、その中にテンプレート用のファイル index.html を作成します。

ファイル構成
myProject
├── dist
│   ├── html
│   │   └── index.html //自動的に生成されるファイル(filename:'html/index.html'により)
│   ├── main.js
│   └── style.css
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //エントリポイント
│   ├── modules
│   │   └── foo.js
│   ├── style.scss
│   └── template  // 追加したディレクトリ
│       └── index.html // テンプレートファイル
└── webpack.config.js

この例ではテンプレートファイルで使用するためのオプション h1 を設定しています。この値はテンプレートで htmlWebpackPlugin.options.h1 で参照することができます。

webpack.config.js 一部抜粋
plugins: [
    new HtmlWebpackPlugin({
      title: 'My Template X',
      filename: 'html/index.html',
      hash: true,
      // テンプレートで使用するファイルのパスを指定
      template: 'src/template/index.html',
      // テンプレートで使用する変数 h1 を設定
      h1: 'Heading Title H1'
    }),
  ],

以下は作成したテンプレート src/template/index.html です。

webpack が出力する JavaScritp や CSS は自動的に読み込まれるので記述する必要はありません。

<%= htmlWebpackPlugin.options.title %> は、options.title の値(上記設定の3行目の title の値)を、<%= htmlWebpackPlugin.options.h1 %> は options.h1 の値(上記設定の9行目の h1 の値)を出力する記述です。

src/template/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!--設定したオプションの値を「htmlWebpackPlugin.options.オプション名 」で参照-->
    <title><%= htmlWebpackPlugin.options.title %></title>
    <meta name="description" content="My First HtmlWebpackPlugin Template">
  </head>
  <body>
    <!--設定したオプションの値を「htmlWebpackPlugin.options.オプション名 」で参照-->
    <h1><%= htmlWebpackPlugin.options.h1 %></h1>
  </body>
</html>

webpack コマンドを実行してコンパイルすると、以下のような HTML ファイル(html/index.html)が生成されます。

dist/html/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>My Template X</title>
    <meta name="description" content="My First HtmlWebpackPlugin Template">
    <script defer src="../main.js?7df25e7bf62acc4d3042"></script>
    <link href="../style.css?7df25e7bf62acc4d3042" rel="stylesheet">
  </head>
  <body>
    <h1>Heading Title H1</h1>
  </body>
</html>
複数のエントリポイント

複数のエントリポイントがある場合、webpack によりバンドルされた JavaScritp は全て HtmlWebpackPlugin により生成される1つの HTML ファイルの script タグに含まれます。

例えば、以下のように2つのエントリポイントがある場合、webpack により14行目の output で指定した dist/js に2つの JavaScritp(one.bundle.js と two.bundle.js)が生成されます。

また、HtmlWebpackPlugin により20行目の filename で指定した ../index.html (1つ上の階層)に1つの HTML ファイル(index.html)が生成され、その script タグで one.bundle.js と two.bundle.js が読み込まれます。

webpack.config.js
const path = require('path');
//html-webpack-plugin の読み込み
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  //2つのエントリポイントを設定
  entry: {
    one: './src/one.js',
    two: './src/two.js',
  },
  //出力先の設定
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist/js'),
  },
  //プラグインの設定
  plugins: [
    //HtmlWebpackPlugin プラグインの設定
    new HtmlWebpackPlugin({
      filename: '../index.html'
    }),
  ],
};
ビルド後のファイル構成
myProject
├── dist
│   ├── index.html  //HtmlWebpackPlugin により生成される HTML
│   └── js
│       ├── one.bundle.js  //ビルドで生成
│       └── two.bundle.js  //ビルドで生成
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── modules  //one.js と two.js でインポートするモジュール
│   │   ├── bar.js
│   │   └── foo.js
│   ├── one.js  //エントリポイント
│   └── two.js  //エントリポイント
└── webpack.config.js

以下は上記の場合に、HtmlWebpackPlugin により出力される index.html の例です。2つのバンドルされた JavaScritp が script タグで読み込まれています。

dist/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 2つの JavaScritp が読み込まれる -->
    <script defer src="js/one.bundle.js"></script>
    <script defer src="js/two.bundle.js"></script>
  </head>
  <body></body>
</html>

複数の HTML を生成

複数の HTML ファイルを生成するには、plugins の配列でプラグインを複数回宣言します。

webpack guides: Generating Multiple HTML Files

以下は、chunks オプションを使って、2つの HTML を生成する例です。

また、出力先は13行目の output プロパティにより dist/js に指定されているため、プラグインの設定で個々に filename で path.resolve() を使って dist/html に変更しています。

webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  //2つのエントリポイントを設定
  entry: {
    one: './src/one.js',
    two: './src/two.js',
  },
  //出力先の設定
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist/js'),
  },
  //プラグインの設定(plugins の配列)
  plugins: [
    //one.bundle.js のみを読み込む HTML
    new HtmlWebpackPlugin({
      chunks: ['one'],
      filename: path.resolve(__dirname, 'dist/html/one.html'),
    }),
    //two.bundle.js のみを読み込む HTML
    new HtmlWebpackPlugin({
      chunks: ['two'],
      filename: path.resolve(__dirname, 'dist/html/two.html'),
    })
  ],
};

上記の場合以下のような2つの HTML が生成されます。

dist/html/one.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 指定した1つの JavaScritp が読み込まれる -->
    <script defer src="../js/one.bundle.js"></script>
  </head>
  <body></body>
</html>
dist/html/two.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 指定した1つの JavaScritp が読み込まれる -->
    <script defer src="../js/two.bundle.js"></script>
  </head>
  <body></body>
</html>
ビルド後のファイル構成
myProject
├── dist
│   ├── html
│   │   ├── one.html //HtmlWebpackPlugin により生成される HTML
│   │   └── two.html //HtmlWebpackPlugin により生成される HTML
│   └── js
│       ├── one.bundle.js  //ビルドで生成
│       └── two.bundle.js  //ビルドで生成
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── modules  //one.js と two.js でインポートするモジュール
│   │   ├── bar.js
│   │   └── foo.js
│   ├── one.js  //エントリポイント
│   └── two.js  //エントリポイント
└── webpack.config.js

webpack guides: HtmlWebpackPlugin

splitChunks

複数のエントリポイントで共通のモジュールを使っている場合、その共通のモジュールをそれぞれのファイルにバンドルすると、全体のファイルサイズが大きくなってしまいます。

optimization プロパティの splitChunks プロパティは、複数のエントリポイントで共通のモジュールを使っている場合に、その共通のモジュールだけを別のファイル(チャンク/chunk)として分離するための設定です(split:分割するという意味なので split Chunks はチャンクを分割するというような意味)。

webpack の version 4 以上では、この機能を使うための SplitChunksPlugin が提供されていてます。

以下は2つのエントリポイントで共通のモジュール(jquery)を使用する例です。

以下の例では myProjectSC という名前のディレクトリを作成し、webpack をインストールします。

プロジェクトのディレクトリ(myProjectSC)に移動して、共通のモジュールとして使う jquery をインストールします。本番ビルドに含めるので、install コマンドオプションを指定しないで実行します。

jQuery のインストール
$ npm install jquery  return

added 1 package, and audited 121 packages in 1s

また、表示用の HTML を自動的に生成するために HtmlWebpackPlugin もインストールします。

$ npm install -D html-webpack-plugin  return

added 31 packages, and audited 152 packages in 6ss

package.json の "main": "index.js" の行を削除し、"private": true を追加して scripts フィールドに webpack を実行する npm script を設定しておきます。この時点での package.json は以下のようになっています。

package.json(例)
{
  "name": "myprojectsc",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  },
  "dependencies": {
    "jquery": "^3.6.0"
  }
}

以下のような構成を用意します。

ファイル構成
myProjectSC
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── modules
│   │   ├── bar.js
│   │   └── foo.js
│   ├── one.js
│   └── two.js
└── webpack.config.js

以下の JavaScript ファイルを作成します。

src/modules/bar.js(エントリポイントで読み込むモジュール)
// export 文を使って文字列を返す関数 heading を定義
export const heading = ()  => 'This is the heading text by bar.js!';
src/modules/foo.js(エントリポイントで読み込むモジュール)
// export 文を使って文字列を関数 content を定義
export const content = ()  => 'This is the content text by foo.js!';
src/one.js(エントリポイント)
//jquery をインポート(読み込んで $ として使用)
import $ from 'jquery';
//foo.js をインポート
import { content } from './modules/foo.js';
//bar.js をインポート
import { heading } from './modules/bar.js';

const component = () => {
  //div 要素を生成
  const element = document.createElement('div');
  // インポートした関数の実行結果を使って div 要素の HTML を作成
  element.innerHTML = `<h1>ONE: ${heading()}</h1>
<p>${content()}</p>`

  return element;
}

// jQuery を使って body 要素に component() の実行結果を設定
$('body').html(component());
// jQuery を使って body 要素の色を緑色に変更
$('body').css('color', 'green');
src/two.js(エントリポイント)
//内容は one.js とほぼ同じ(違いは13行目の文字 TWO のみ)
//jquery をインポート(読み込んで $ として使用)
import $ from 'jquery';
//foo.js の関数 content をインポート
import { content } from './modules/foo.js';
//bar.js の関数 heading をインポート
import { heading } from './modules/bar.js';

const component = () => {
  //div 要素を生成
  const element = document.createElement('div');
  // インポートした関数の実行結果を使って div 要素の HTML を作成
  element.innerHTML = `<h1>TWO: ${heading()}</h1>
<p>${content()}</p>`
  return element;
}

// jQuery を使って body 要素に component() の実行結果を設定
$('body').html(component());
// jQuery を使って body 要素の色を緑色に変更
$('body').css('color', 'blue');

以下のような webpack.config.js を作成します。output プロパティでは、[name] を使って出力されるファイル名を設定しています。[name] にはエントリポイントのエントリ名が入ります。

webpack.config.js
const path = require('path');
//html-webpack-plugin の読み込み
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  //2つのエントリポイントを設定
  entry: {
    one: './src/one.js',
    two: './src/two.js',
  },
  //出力先の設定
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist/js'),
  },
  //プラグインの設定
  plugins: [
    //one.bundle.js のみを読み込む HTML を自動的に生成
    new HtmlWebpackPlugin({
      chunks: ['one'],
      //出力先を output で指定した dist/js から dist/html に変更及びファイル名を指定
      filename: path.resolve(__dirname, 'dist/html/one.html'),
    }),
    //two.bundle.js のみを読み込む HTML を自動的に生成
    new HtmlWebpackPlugin({
      chunks: ['two'],
      filename: path.resolve(__dirname, 'dist/html/two.html'),
    })
  ],
};

この状態で webpack コマンドを実行すると、それぞれのエントリポイントに対しバンドルされた JavaScript(one.bundle.js と two.bundle.js)が生成されます。

また HtmlWebpackPlugin により2つの HTML ファイル(one.html と two.html)が生成されます。

$ npm run dev  return //webpack コマンドを実行

> myprojectsc@1.0.0 dev
> webpack --mode development

assets by path *.js 651 KiB
  asset one.bundle.js 325 KiB [emitted] (name: one)  //バンドルされた JavaScript
  asset two.bundle.js 325 KiB [emitted] (name: two)  //バンドルされた JavaScript
assets by path ../html/*.html 486 bytes
  asset ../html/one.html 243 bytes [emitted]  //HtmlWebpackPlugin で生成された HTML
  asset ../html/two.html 243 bytes [emitted]  //HtmlWebpackPlugin で生成された HTML
runtime modules 1.83 KiB 8 modules
cacheable modules 283 KiB
  modules by path ./src/*.js 1.28 KiB
    ./src/one.js 719 bytes [built] [code generated]
    ./src/two.js 588 bytes [built] [code generated]
  modules by path ./src/modules/*.js 262 bytes
    ./src/modules/foo.js 131 bytes [built] [code generated]
    ./src/modules/bar.js 131 bytes [built] [code generated]
  ./node_modules/jquery/dist/jquery.js 282 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 254 ms

バンドルされた2つのファイルを確認すると、共通のモジュールがそれぞれにバンドルされていてファイルサイズが大きくなっています。

myProject2
├── dist
│   ├── html
│   │   ├── one.html  //HtmlWebpackPlugin により生成されたファイル
│   │   └── two.html  //HtmlWebpackPlugin により生成されたファイル
│   └── js
│       ├── one.bundle.js  //バンドルされたファイル(325 KiB)
│       └── two.bundle.js  //バンドルされたファイル(325 KiB)
├── package-lock.json
├── package.json
├── src
│   ├── modules
│   │   ├── bar.js
│   │   └── foo.js
│   ├── one.js
│   └── two.js
└── webpack.config.js

splitChunks を設定して共通のモジュールを分離

以下のように webpack.config.js に optimization.splitChunks の設定(31〜39行目)を追加して、エントリポイントで共通のモジュールを別のファイル(チャンク)として分離すると、合計のファイルサイズを小さくすることができます。

以下では、共通のモジュールを分離したファイル(chunk)の名前 name を vendor としているので、分離して出力されるチャンクのファイル名は vendor.bundle.js になります。

chunks は対象とするチャンク(chunk)に含めるモジュールの種類を指定する項目で、all, async, initial を指定することができます。

webpack.config.js
const path = require('path');
//html-webpack-plugin の読み込み
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  //2つのエントリポイントを設定
  entry: {
    one: './src/one.js',
    two: './src/two.js',
  },
  //出力先の設定
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist/js'),
  },
  //プラグインの設定
  plugins: [
    //one.bundle.js のみを読み込む HTML を自動的に生成
    new HtmlWebpackPlugin({
      chunks: ['one'],
      //出力先を output で指定した dist/js から dist/html に変更及びファイル名を指定
      filename: path.resolve(__dirname, 'dist/html/one.html'),
    }),
    //two.bundle.js のみを読み込む HTML を自動的に生成
    new HtmlWebpackPlugin({
      chunks: ['two'],
      filename: path.resolve(__dirname, 'dist/html/two.html'),
    })
  ],
  //optimization の設定を追加
  optimization: {
    //optimization.splitChunks の設定
    splitChunks: {
      // 分離されて生成される chunk の名前(任意の名前)
      name: 'vendor',
      // 対象とするチャンク(chunk)に含めるモジュールの種類
      chunks: 'initial',   // または 'all'
    }
  },
};

optimization.splitChunks の設定(31〜39行目)を追加して webpack コマンドを実行すると、共通のモジュールが webpack により自動で判別されて別のチャンクに分離されて出力されます。

name を vendor と設定したので、分離して出力されるチャンクのファイル名は vendor.bundle.js になっていて、one.bundle.js と two.bundle.js のファイルサイズは小さくなっています。

$ npm run dev return //webpack コマンドを実行
// または npx webpack --mode=development

> myprojectsc@1.0.0 dev
> webpack --mode development

assets by path *.js 340 KiB
  // 共通モジュールのバンドル vendor.bundle.js
  asset vendor.bundle.js 320 KiB [emitted] (name: vendor) (id hint: vendors)
  asset two.bundle.js 10.2 KiB [emitted] (name: two)  //バンドルされた JavaScript
  asset one.bundle.js 10.1 KiB [emitted] (name: one)  //バンドルされた JavaScript
assets by path ../html/*.html 590 bytes
  asset ../html/one.html 295 bytes [emitted]
  asset ../html/two.html 295 bytes [emitted]
//エントリーポイント one にバンドルされたファイル
Entrypoint one 330 KiB = vendor.bundle.js 320 KiB one.bundle.js 10.1 KiB
//エントリーポイント two にバンドルされたファイル
Entrypoint two 330 KiB = vendor.bundle.js 320 KiB two.bundle.js 10.2 KiB
runtime modules 6.62 KiB 12 modules
cacheable modules 284 KiB
  modules by path ./src/*.js 1.51 KiB
    ./src/one.js 714 bytes [built] [code generated]
    ./src/two.js 832 bytes [built] [code generated]
  modules by path ./src/modules/*.js 272 bytes
    ./src/modules/foo.js 136 bytes [built] [code generated]
    ./src/modules/bar.js 136 bytes [built] [code generated]
  ./node_modules/jquery/dist/jquery.js 282 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 255 ms

また、HtmlWebpackPlugin により自動的に生成される HTML の script 要素は以下のように2つのチャンク(vendor.bundle.js と one.bundle.js)を読み込むように変更されています。

one.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script defer src="../js/vendor.bundle.js"></script><!-- 分離されたチャンク -->
    <script defer src="../js/one.bundle.js"></script>
  </head>
  <body></body>
</html>
two.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script defer src="../js/vendor.bundle.js"></script><!-- 分離されたチャンク -->
    <script defer src="../js/two.bundle.js"></script>
  </head>
  <body></body>
</html>
myProjectSC
├── dist
│   ├── html
│   │   ├── one.html
│   │   └── two.html
│   └── js
│       ├── one.bundle.js
│       ├── two.bundle.js
│       └── vendor.bundle.js  //共通のモジュールが分離されたバンドルファイル(チャンク)
├── package-lock.json
├── package.json
├── src
│   ├── modules
│   │   ├── bar.js
│   │   └── foo.js
│   ├── one.js
│   └── two.js
└── webpack.config.js

オプション

前述の splitChunks の設定は以下のようなものでしたが、この場合、共通のモジュールは全て分離して出力されるので(サイズなどのデフォルトの設定の条件にもよりますが)、特定のファイルを分離したい場合などは、cacheGroups というオプションを指定することができます。

webpack.config.js 抜粋
optimization: {
  splitChunks: {
    // 分離されて生成される chunk の名前(任意の名前)
    name: 'vendor',
    //対象とする chunk に含めるモジュールの種類
    chunks: 'initial',
  }
},

cacheGroups を使用すると、条件に基づいてチャンクを作成することができます。cacheGroups はオブジェクトで、キー(key)はチャンクの名前、値(value)はそのチャンクの構成を指定します。

初期状態(デフォルト)では、defaultVendors と default というキーの cacheGroups が設定されています。

以下は cacheGroups を設定して、node_modules ディレクトリ配下の jquery モジュールを分離する場合の例です。

test は対象のファイルを正規表現で指定することができます。パスセパレータはクロスプラットフォームに対応するため [\\/] を使用しています。

webpack.config.js 抜粋
optimization: {
    //optimization.splitChunks の設定
    splitChunks: {
      //特定のファイルを分離する場合などに設定
      cacheGroups: {
        // vendor 以外の任意の名前を設定可能(cacheGroups のキー)
        vendor: {
          // node_modules 配下の jquery モジュールを分離する対象とする
          test: /[\\/]node_modules[\\/]jquery[\\/]/,
          // 分離されて生成される chunk の名前(cacheGroups のキーを上書き)
          name: 'vendor',
          //対象とする chunk に含めるモジュールの種類
          chunks: 'initial'  // または 'all'
        }
      }
    }
  },

以下は SplitChunksPlugin に記載されているサンプルで、react と react-dom を別のチャンクに分離します。

webpack.config.js 抜粋
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'vendor',
          chunks: 'all',
        }
      }
    }
  }
};
デフォルトの設定

以下が SplitChunksPlugin のデフォルトの設定です。必要に応じて変更することができます。

webpack.config.js
//SplitChunksPlugin のデフォルトの設定(初期値)
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

※上記 cacheGroup の test フィールド(15行目)でパスセパレータを表すのに [\\/] を使用しているのはクロスプラットフォームに対応するためです(ファイルパスが webpack によって処理される場合、Unix 系では常に /、Windowsでは \ が含まれるため)。

上記のデフォルトの設定があるため、前述の例の場合、以下のように設定すると jquery のモジュールは minSize の 30k より大きく、エントリーポイントが2つなので minChunks を満たすので、共通のモジュールのチャンクとして分離されて出力されます。

optimization: {
  splitChunks: {
    //対象とする chunk に含めるモジュールの種類
    chunks: 'initial',    // または 'all'
  }
},

vendors-node_modules_jquery_dist_jquery_js.bundle.js という名前で分離されて出力されているのが確認できます。name を指定していないのでデフォルトの false が適用され、名前は自動的に生成されます。

$ npm run dev return //webpack コマンドを実行
// または npx webpack --mode=development

> myprojectsc@1.0.0 dev
> webpack --mode development

assets by path *.js 340 KiB
  asset vendors-node_modules_jquery_dist_jquery_js.bundle.js 320 KiB [emitted] (id hint: vendors)
  asset two.bundle.js 10.3 KiB [emitted] (name: two)
  asset one.bundle.js 10.2 KiB [emitted] (name: one)
assets by path ../html/*.html 662 bytes
  asset ../html/one.html 331 bytes [emitted]
  asset ../html/two.html 331 bytes [emitted]
Entrypoint one 330 KiB = vendors-node_modules_jquery_dist_jquery_js.bundle.js 320 KiB one.bundle.js 10.2 KiB
Entrypoint two 330 KiB = vendors-node_modules_jquery_dist_jquery_js.bundle.js 320 KiB two.bundle.js 10.3 KiB
runtime modules 6.62 KiB 12 modules
cacheable modules 284 KiB
  modules by path ./src/*.js 1.51 KiB
    ./src/one.js 714 bytes [built] [code generated]
    ./src/two.js 832 bytes [built] [code generated]
  modules by path ./src/modules/*.js 272 bytes
    ./src/modules/foo.js 136 bytes [built] [code generated]
    ./src/modules/bar.js 136 bytes [built] [code generated]
  ./node_modules/jquery/dist/jquery.js 282 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 261 ms

以下はオプションの一部抜粋です。

オプション 初期値 説明
chunks 'async' 分離の対象とする chunk に含めるモジュールの種類。指定可能な値は all, async, initial または関数で指定。
  • all:test の条件に含まれるすべてのモジュールを分離
  • async:import() された非同期で読み込むモジュール
  • initial:静的にインポートされた(初期ロードに必要な)モジュール
minSize 30000 生成されるチャンクの最小サイズをバイト単位で指定
minChunks 1 分離する場合に必要な共有されているエントリーポイントの最小数
name false 分離されたチャンクの名前。false を指定するとチャンクの名前が同じに保たれ、名前が不必要に変更されることはありません。

参考サイト

webpack の import()

webpack の import() はモジュールを動的にロードします。

import() は Promise を返すので、then() メソッドなどを使って処理を記述します。

import('モジュールのパス').then((result) => {
  // result を使って処理を記述
});

また、import() の呼び出しは分割ポイント(split points)として扱われ、要求されたモジュールとその子は別のチャンクに分離されます。

※この機能は内部で Promise に依存しているため、IE などの古いブラウザで import() を使用する場合は、es6-promisepromise-polyfill などのポリフィルを使用する必要があります。

以下は import() を使って動的にモジュールをロードする例です。前項の splitChunks で使用した構成に動的にロードするモジュール src/modules/async.js を追加します。

myProjectSC
├── package-lock.json
├── package.json
├── src
│   ├── modules
│   │   ├── async.js  //動的にロードされるモジュール
│   │   ├── bar.js
│   │   └── foo.js
│   ├── one.js  //import() を使って async.js を動的にロード
│   └── two.js
└── webpack.config.js

以下を作成して src/modules/ に追加します。

src/modules/async.js
// async.js
export const HELLO = 'Hello from async.js!';

1つのエントリポイント(one.js)を以下のように書き換えて、ボタン要素を追加し、そのボタンをクリックすると、import() を使って async.js を動的にロードして、取得した値(HELLO)をアラート表示します。

この例の場合、HELLO という値はボタンクリックしたときに必要になるので(ファイルの読み込み時には必要はないので)import() を使っています。ファイルの読み込み時に必要な値は import を使います。

import() 関数はモジュールを非同期に読み込み promise を返し、成功すればそのモジュールがエクスポートしているものを返します。

src/one.js(エントリポイント)
import $ from 'jquery';
import { content } from './modules/foo.js';
import { heading } from './modules/bar.js';

const component = () => {
  const element = document.createElement('div');
  element.innerHTML = `<h1>ONE: ${heading()}</h1>
<p>${content()}</p>`;

  //ボタン要素を作成
  const btn = document.createElement('button');
  //ボタン要素のテキスト
  btn.textContent = 'click';
  //ボタン要素にイベントハンドラを設定
  btn.addEventListener('click', () => {
    //動的に async.js をロード
    import( './modules/async.js' ).then(result => {
      alert(result.HELLO );
    });
  });
  //div 要素にボタンを追加
  element.appendChild(btn);

  return element;
}

$('body').html(component());
$('body').css('color', 'green');

webpack.config.js は前項の splitChunks の場合と同じで、2つのエントリポイントを設定し、HtmlWebpackPlugin で HTML ファイルを自動的に生成します。また、共通のモジュール(jquery)は splitChunks で分離します。

webpack.config.js
const path = require('path');
//html-webpack-plugin の読み込み
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  //2つのエントリポイントを設定
  entry: {
    one: './src/one.js',
    two: './src/two.js',
  },
  //出力先の設定
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist/js'),
  },
  //プラグインの設定
  plugins: [
    //one.bundle.js のみを読み込む HTML を自動的に生成
    new HtmlWebpackPlugin({
      chunks: ['one'],
      //出力先を output で指定した dist/js から dist/html に変更及びファイル名を指定
      filename: path.resolve(__dirname, 'dist/html/one.html'),
    }),
    //two.bundle.js のみを読み込む HTML を自動的に生成
    new HtmlWebpackPlugin({
      chunks: ['two'],
      filename: path.resolve(__dirname, 'dist/html/two.html'),
    })
  ],

  //optimization の設定を追加
  optimization: {
    //optimization.splitChunks の設定
    splitChunks: {
      cacheGroups: {
        // vendor 以外の任意の名前を設定可能
        vendor: {
          // node_modules 配下のモジュールを分離する対象とする
          test: /[\\/]node_modules[\\/]/,
          // 分離されて生成される chunk の名前(任意の名前)
          name: 'vendor',
          // 対象とする chunk に含めるモジュールの種類
          chunks: 'initial'
        }
      }
    }
  },
};

webpack コマンドを実行すると、splitChunks による vendor.bundle.js の他に import() の呼び出しによる async.js のチャンク src_modules_async_js.bundle.js が生成されます。

$ npm run dev return //webpack コマンドを実行
// または npx webpack --mode=development

> myprojectsc@1.0.0 dev
> webpack --mode development

assets by path *.js 348 KiB
  asset vendor.bundle.js 320 KiB [emitted] (name: vendor) (id hint: vendor)
  asset one.bundle.js 16.7 KiB [emitted] (name: one)
  asset two.bundle.js 10.2 KiB [emitted] (name: two)
  //import() の呼び出しにより分離されたチャンク
  asset src_modules_async_js.bundle.js 1.22 KiB [emitted]
assets by path ../html/*.html 590 bytes
  asset ../html/one.html 295 bytes [emitted]
  asset ../html/two.html 295 bytes [emitted]
Entrypoint one 336 KiB = vendor.bundle.js 320 KiB one.bundle.js 16.7 KiB
Entrypoint two 330 KiB = vendor.bundle.js 320 KiB two.bundle.js 10.2 KiB
runtime modules 11.2 KiB 17 modules
cacheable modules 284 KiB
  modules by path ./src/modules/*.js 332 bytes
    ./src/modules/foo.js 136 bytes [built] [code generated]
    ./src/modules/bar.js 136 bytes [built] [code generated]
    ./src/modules/async.js 60 bytes [built] [code generated]
  modules by path ./src/*.js 1.57 KiB
    ./src/one.js 780 bytes [built] [code generated]
    ./src/two.js 832 bytes [built] [code generated]
  ./node_modules/jquery/dist/jquery.js 282 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 271 ms
dist/js/src_modules_async_js.bundle.js
"use strict";

(self["webpackChunkmyprojectsc"] = self["webpackChunkmyprojectsc"] || []).push([["src_modules_async_js"],{

/***/ "./src/modules/async.js":
/*!******************************!*\
  !*** ./src/modules/async.js ***!
  \******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"HELLO\": () => (/* binding */ HELLO)\n/* harmony export */ });\n// async.js\nconst HELLO = 'Hello from async.js!';\n\n\n\n\n\n//# sourceURL=webpack://myprojectsc/./src/modules/async.js?");

/***/ })

}]);
myProjectSC
├── dist //ビルド時に生成される出力ディレクトリ
│   ├── html
│   │   ├── one.html  //HtmlWebpackPlugin により自動的に出力される HTML
│   │   └── two.html  //HtmlWebpackPlugin により自動的に出力される HTML
│   └── js
│       ├── one.bundle.js
│       ├── two.bundle.js
│       ├── src_modules_async_js.bundle.js //import() の呼び出しにより分離されたチャンク
│       └── vendor.bundle.js  //splitChunks により分離されたチャンク
├── package-lock.json
├── package.json
├── src
│   ├── modules
│   │   ├── async.js  //one.js で動的に(クリックイベントで)ロード
│   │   ├── bar.js
│   │   └── foo.js
│   ├── one.js  //import() を使って async.js を動的にロード
│   └── two.js
└── webpack.config.js

以下は HtmlWebpackPlugin により自動的に出力される one.html です。

dist/html/one.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script defer src="../js/vendor.bundle.js"></script>
    <script defer src="../js/one.bundle.js"></script>
  </head>
  <body>
  </body>
</html>

src_modules_async_js.bundle.js の読み込み用の script タグはありませんが、ボタンがクリックされる(イベントハンドラによりモジュールが要求される)と script タグが追加され、src_modules_async_js.bundle.js がダウンロードされます。

読み込みが終わると(この場合はアラートの OK をクリックすると)、追加された script 要素は削除されます。ダウンロードは一度のみ行われ、ボタンを複数回クリックしても重複してダウンロードされることはありません。

以下の場合、ビルド時はエラーになりませんが、9行目の console.log(HELLO) はファイル読み込み時に HELLO は取得できていない(動的に async.js をロードしていない)のでエラーになります。

ファイル読み込み時に必要な場合は、通常の import 文を使います。

src/two.js(エントリポイント)
import $ from 'jquery';
import { content } from './modules/foo.js';
import { heading } from './modules/bar.js';
import( './modules/async.js' ).then(result => {
  const HELLO = result.HELLO;
  console.log(HELLO);  // OK
});

console.log(HELLO);  //このタイミングではエラーになる

const component = () => {
  const element = document.createElement('div');
  element.innerHTML = `<h1>TWO: ${heading()}</h1>
<p>${content()}</p>`
  return element;
}

$('body').html(component());
$('body').css('color', 'blue');

webpack guides: import()

webpack-dev-server

webpack では webpack-dev-server を使って簡単に開発用サーバを立ち上げることができます。

以下は、基本的な webpack-dev-server の設定方法と使い方です。すでに webpack はインストールされていることを前提にしています。(webpack のインストール

この例のディレクトリ構成とそれぞれのファイルは以下のようになっています。

myProject4
├── dist  //出力先ディレクトリ
│   ├── html
│   │   └── index.html // 表示用 HTML ファイル
│   └── main.js  //バンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //エントリポイント
│   └── modules
│       └── foo.js  //モジュール
└── webpack.config.js
foo.js
export const greet = ()  => 'Hello world!';
index.js
import { greet } from './modules/foo.js';

const divElem = document.createElement('div');
const p = document.createElement('p');
p.textContent = greet();
divElem.appendChild(p);
document.body.appendChild(divElem);
index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>DevServer Sample</title>
  </head>
  <body>
    <script src="../main.js"></script>
  </body>
</html>

webpack と webpack-cli をインストール後、package.json の "main": "index.js" の行を削除し、"private": true を追加して、scripts フィールドに "build": "webpack --mode production" と "dev": "webpack --mode development" を追加しています。

package.json (例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

設定ファイル webpack.config.js を作成します。以下の内容はデフォルトと同じなので省略できます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',  //エントリポイント
  output: {  //出力先
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

ファイルを変更をした際に更新を確認するには webpack コマンドや package.json の scripts フィールドに追加した npm script を実行してビルドし、ブラウザで確認する必要があります。

または、以下のように watch オプションを利用すればファイルが変更されると自動的に webpack コマンドを実行するようにはできますが、ブラウザには反映されません。

$ npx webpack --watch --mode development  //watch モードで実行

webpack-dev-server を使えば、毎回ビルドコマンドを実行したりブラウザを再読込しなくとリアルタイムに変更内容を確認しながら開発ができるようになります。

インストール

プロジェクトのディレクトリで以下を実行して webpack-dev-server をローカルインストールします。

-D(または --save-dev)は開発環境で使う(本番ビルドに含めない)場合に指定するオプションです。

$ npm install -D webpack-dev-server  return

added 218 packages, and audited 338 packages in 31s

上記実行後の package.json は以下のようになっています(17行目に webpack-dev-server が追加)。

package.json (例)
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  }
}

バージョン

webpack-dev-server のバージョンは package.json で確認できますが、コマンドラインで npx webpack serve に -v または --version を指定しても確認できます。

$ npx webpack serve -v  return
@webpack-cli/serve 1.6.0
webpack: 5.65.0
webpack-cli: 4.9.1
webpack-dev-server 4.7.2

webpack-dev-server v4.0.0 +は、ノード v12.13.0 以上、webpack v4.37.0 以上(webpack v5.0.0以上を推奨)、および webpack-cli v4.7.0 以上が必要です。

ヘルプの表示

ヘルプを表示するにはコマンドラインで npx webpack serve に -h または --help を指定します。

$ npx webpack serve -h  return

Usage: webpack serve|server|s [entries...] [options]
Run the webpack dev server.
Options:
  -c, --config <value...>                   Provide path to a webpack configuration file e.g. ./webpack.config.js.
  --config-name <value...>                  Name of the configuration to use.
  ・・・中略・・・
Global options:
  --color                                   Enable colors on console.
  --no-color                                Disable colors on console.
  -v, --version                             Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.
  -h, --help [verbose]                      Display help for commands and options.

To see list of all supported commands and options run 'webpack --help=verbose'.

Webpack documentation: https://webpack.js.org/.
CLI documentation: https://webpack.js.org/api/cli/.
Made with ♥ by the webpack team.

webpack.config.js

webpack.config.js に webpack-dev-server の設定を追加します。この例では webpack-dev-server に静的ファイルの場所を指定する static オプションに HTML ファイルのパスを指定します。

この例の場合、表示用の HTML ファイル(index.html)は手動で作成して dist/html/index.html に配置してあるので以下のように設定します。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //webpack-dev-server の設定
  devServer: {
    static: './dist/html',  //静的ファイルの場所
  },
};

webpack guides: Using webpack-dev-server | DevServer

webpack-dev-server を起動

webpack-dev-server を起動するには npx webpack serve を実行します。

$ npx webpack serve   return  //webpack-dev-server を起動

<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:8080/
<i> [webpack-dev-server] On Your Network (IPv4): http://192.168.11.2:8080/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:8080/
<i> [webpack-dev-server] Content not from webpack is served from '/Applications/MAMP/htdocs/myProject/dist/html' directory
asset main.js 115 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 27 KiB 12 modules
orphan modules 17.3 KiB [orphan] 8 modules
cacheable modules 161 KiB
  modules by path ./node_modules/webpack-dev-server/client/ 56.8 KiB
    modules by path ./node_modules/webpack-dev-server/client/modules/ 30.7 KiB 2 modules
    3 modules
  modules by path ./node_modules/webpack/hot/*.js 4.3 KiB 4 modules
  modules by path ./node_modules/html-entities/lib/*.js 81.3 KiB
    ./node_modules/html-entities/lib/index.js 7.74 KiB [built] [code generated]
    ./node_modules/html-entities/lib/named-references.js 72.7 KiB [built] [code generated]
    ./node_modules/html-entities/lib/numeric-unicode-map.js 339 bytes [built] [code generated]
    ./node_modules/html-entities/lib/surrogate-pairs.js 537 bytes [built] [code generated]
  ./src/index.js + 1 modules 305 bytes [built] [code generated]
  ./node_modules/ansi-html-community/index.js 4.16 KiB [built] [code generated]
  ./node_modules/events/events.js 14.5 KiB [built] [code generated]

WARNING in configuration  //モードが指定されていない旨の警告
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.65.0 compiled with 1 warning in 1118 ms

この例の場合、mode option を指定していないので、25行目以降に警告が表示されています。

Project is running at: の後に記載されている URL (http://localhost:8080/ など)にアクセスするとこの例の場合、以下のように表示されます。

mode option を指定していないため警告がオーバーレイ表示されますが、取り敢えず右上の X 印をクリックして警告を非表示にすると以下のように index.html が表示されます。

警告やエラーをオーバーレイ表示させないで起動するには --no-client-overlay オプションを指定します(client オプションでも設定可能です)。

$ npx webpack serve --no-client-overlay   return  //オプションを指定をして起動

サーバを終了

サーバを終了するには、 control + c を押します。

モードを指定して起動

npx webpack serve を実行する際にモードのオプション(--mode production または --mode development)を指定するか、webpack.config.js で mode を指定すれば警告は表示されません。

$ npx webpack serve --mode development  return

npm scripts を設定

コマンドラインで簡単にサーバを起動できるように package.json の scripts フィールドに npm scripts を設定することもできます。

以下の例では start:dev に npx webpack serve --mode development を設定しています(10行目に追加)。

package.json
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "start:dev": "npx webpack serve --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  }
}

これで、npm run start:dev を実行すればモードの警告なしで webpack-dev-server を起動できます。

$ npm run start:dev  return

以下は webpack-dev-server の github ページです。コマンドラインのオプションなども記載されています。

webpack-dev-server

オプション

オプションはコマンド実行時に指定するか、webpack の設定ファイル webpack.config.js に記述します。

devServer オプションの一部抜粋
オプション 説明
static 静的ファイルを表示するためのオプション
open ブラウザを自動的に起動
port デフォルトのポート番号を変更
client ログ、エラー・警告時のオーバーレイ表示、再接続などのオプションの設定
writeToDisk バンドルされたファイルを出力(devMiddleware のオプション)

webpack guides: DevServer

static

サーバーで表示する静的ファイル(HTML)の場所を指定します。dist フォルダのファイルを表示するなら ./dist と指定。デフォルトは ./public になります。

この例の場合、開発サーバーで表示する静的ファイル index.html は dist/html/ にあるので、static に dist/html を指定します。

コマンド実行時に指定する場合は、以下のように指定することができます。

$ npx webpack serve --static dist/html

//モードも指定する場合
$ npx webpack serve --static dist/html --mode development
myProject4
├── dist
│   ├── html
│   │   └── index.html  //公開するファイル
│   └── main.js  //バンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //エントリポイント
│   └── modules
│       └── foo.js
└── webpack.config.js

以下は webpack.config.js に設定を記述する例です。

webpack-dev-server の設定は devServer に記述します。公開するファイルが dist 直下の場合は dist/html の代わりに dist と指定します。環境に合わせて設定します。

webpack.config.js
const path = require('path'); //path モジュールの読み込み

module.exports = {
  entry: './src/index.js',  //エントリポイント
  output: {  //出力先
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //webpack-dev-server の設定
  devServer: {
    //表示する静的ファイルのディレクトリを指定
    static: './dist/html',
  },
};

webpack.config.js に上記の設定を追加して 以下を実行してブラウザで確認すると、例えば index.js や foo.js を編集して保存した時点でその変更がブラウザに自動的に反映されます。

$ npm run start:dev   return  //npm scripts を設定している場合
//または $ npx webpack serve --mode development

> myproject@1.0.0 start:dev
> npx webpack serve --mode development

<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:8080/
<i> [webpack-dev-server] On Your Network (IPv4): http://192.168.11.2:8080/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:8080/
<i> [webpack-dev-server] Content not from webpack is served from '/Applications/MAMP/htdocs/myProject/public' directory
asset main.js 244 KiB [emitted] (name: main)
・・・以下省略・・・

ターミナルの出力にある http://localhost:8080/ で公開ファイルにアクセスできます。

directory オプションを使って以下のように記述することもできます。

webpack.config.js
const path = require('path');

module.exports = {
  ・・・その他の設定・・・
  devServer: {
    static: {
      //対象のディレクトリを指定
      directory: path.join(__dirname, 'dist/html'),
    },
  },
};

open ブラウザを自動的に起動

open オプションを指定すると、サーバー起動時にブラウザを自動的に起動することができます。

以下はコマンドラインで指定して実行する例です。

$ npx webpack serve --static dist/html --mode development --open  return

以下は webpack.config.js に設定する例で、open: true を指定します。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //webpack-dev-server の設定
  devServer: {
    static: './dist/html',
    //サーバー起動時にブラウザを自動的に起動
    open: true
  },
};

port ポート番号の変更

port オプションを指定するとデフォルトのポート(8080)を変更することができます

以下は webpack.config.js でポート番号を 3000 に変更する例です。以下の場合、アクセスする URL は「http://localhost:3000/」のようになります。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //webpack-dev-server の設定
  devServer: {
    static: './dist/html',
    //サーバー起動時にブラウザを自動的に起動
    open: true,
    // ポート番号を変更
    port: 3000
  },
};

client ブラウザの設定

logging

logging オプションを使うとブラウザのログ(コンソールへの出力)をカスタマイズすることができます。指定できる値は 'log' 、 'info'、 'warn'、'error'、'none'、 'verbose' です。

以下はエラーのみを表示する例です。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //webpack-dev-server の設定
  devServer: {
    static: './dist/html',
    open: true,
    port: 3000,
    client: {
      //エラーのみを表示(警告は表示しない)
      logging: 'error',
    },
  },
};

overlay

overlay オプションを使うとエラーや警告がある場合のオーバーレイ表示をカスタマイズすることができます。デフォルトは true で、エラーや警告がある場合にそれらをオーバーレイ表示します。

エラーや警告がある場合でもオーバーレイ表示をさせないようにするには false を指定します。

webpack.config.js
module.exports = {

  devServer: {
    client: {
      //オーバーレイ表示をしない
      overlay: false,
    },
  },
};

以下はエラーの場合のみオーバーレイ表示させる例です。

webpack.config.js
module.exports = {
  devServer: {
    client: {
      overlay: {
        errors: true, //エラーの場合は表示
        warnings: false,, //警告の場合は表示しない
      },
    },
  },
};

reconnect (v4.4.0+)

reconnect オプションを使ってクライアントの再接続を試行する回数を設定できます。デフォルトは true で、無制限に再接続を試みます。

例えば、 control + c でサーバーを終了してコンソールを確認すると、以下のように再接続が試みられています。

再接続をしないようにするには reconnect に false を指定します。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //webpack-dev-server の設定
  devServer: {
    static: './dist/html',
    open: true,
    port: 3000,
    client: {
      //再接続をしない
      reconnect: false,
    },
  },
};

コマンドラインのオプションとして指定する場合は --no-client-reconnect を指定します。

webpack.config.js
$ npx webpack serve --no-client-reconnect  //再接続をしない

再接続を試みる回数を指定することもできます。

webpack.config.js
module.exports = {

  devServer: {
    client: {
      reconnect: 5, //5回まで再接続を試みる
    },
  },
};

以下はコマンドラインのオプションとして指定する場合の例です。

webpack.config.js
$ npx webpack serve --client-reconnect 5  //5回まで再接続を試みる

バンドルされたファイルは出力されない

webpack-dev-server を実行してファイルを編集して保存すると、サーバ上でビルドされてブラウザにその変更が即座に反映されます。

但し、webpack-dev-server で生成された(バンドルされた)ファイルはメモリ上に保存されていて、デフォルトでは実際には出力されません。

例えば、以下の構成で webpack-dev-server を実行して foo.js を編集して保存するとその変更はブラウザに反映されますが、ローカル環境で index.html を開いて見ると変更は反映されていません。これはバンドルされて出力されるファイル main.js が実際には書き出されていないためです。

myProject4
├── dist
│   ├── html
│   │   └── index.html  //公開するファイル
│   └── main.js  //バンドルされて出力されるファイル
├── package-lock.json
├── package.json
├── src
│   ├── index.js  //エントリポイント
│   └── modules
│       └── foo.js
└── webpack.config.js

実際にバンドルされるファイルを出力するには、webpack を実行してビルドするか devMiddleware オプションの writeToDisk オプションを設定する必要があります。

webpack-dev-server を終了して、webpack コマンドを実行すると実際に main.js が出力されます。その後 index.html をローカル環境で開けば変更を確認することができます。

$ npm run dev   return
// または npx webpack --mode=development(モードはどちらでも)

> myproject@1.0.0 dev
> webpack --mode development

asset main.js 4.35 KiB [emitted] (name: main)
runtime modules 670 bytes 3 modules
cacheable modules 305 bytes
  ./src/index.js 214 bytes [built] [code generated]
  ./src/modules/foo.js 91 bytes [built] [code generated]
webpack 5.65.0 compiled successfully in 66 ms

webpack コマンドで watch オプションを指定して watch モードで実行した場合は、ファイルの変更は監視されて自動的にバンドルされたファイルが書き出されますが、ブラウザには反映されないので、手動で再読み込みをして確認する必要があります。

writeToDisk

デフォルトでは webpack-dev-server はバンドルされたファイルを出力しません(ディスクに書き込みません)が、devMiddleware オプションの writeToDisk オプションを設定することで生成されたファイルをディスクに書き込むように指示することができます(webpack-dev-server は内部的には webpack-dev-middleware が使われています)。出力先は output で指定したディレクトリになります。

以下は devMiddleware オプションの writeToDisk: true を指定してバンドルされたファイルを出力するようにする例です(18行目)。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  //webpack-dev-server の設定
  devServer: {
    static: './dist/html',
    //サーバー起動時にブラウザを自動的に起動
    open: true,
    // ポート番号を変更
    port: 3000,
    //webpack-dev-middleware 関連の設定
    devMiddleware: {
      writeToDisk: true, //バンドルされたファイルを出力する(実際に書き出す)
    },
  },
};

webpack guides: devServer.devMiddleware

webpack-dev-middleware