webpack を使って Bootstrap 5 をインストール(バンドル)

webpack 5 を使って Bootstrap 5 をインストール(バンドル)する方法や Sass を使ったスタイルのカスタマイズ($theme-colors の変更方法など)、JavaScript の記述方法、Bootstrap Icons をインストールしてアイコンフォントを利用する方法などの覚書(メモ)です。

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

作成日:2020年11月14日

使用している主なモジュールのバージョンは以下になります。

Bootstrap 5 公式サイト(英語):https://getbootstrap.com/

Bootstrap 5 日本語サイト:https://v5.getbootstrap.jp/

【2022年1月8日】 内容を上記モジュールのバージョンに合わせて更新しました。

関連ページ:

ファイル構成

以下がファイル及びフォルダ構成です。mySite の中に assets という開発用フォルダを配置し、webpack を使って Bootstrap 5 をインストール(バンドル)します。

この例ではローカル環境(MAMP)の htdocs に mySite を配置しています。

mySite
├── assets  //開発用フォルダ(Bootstrap 5 をインストール)
│   └── src
│       ├── custom.scss  // Sass ファイル(Bootstrap のスタイルをカスタマイズ)
│       ├── images // custom.scss で使用する背景画像を配置するフォルダ(必要に応じて)
│       └── index.js  //Javascript ファイル(エントリポイント)
├── images // 画像を配置するフォルダ(必要に応じて)
└── index.html //表示確認用 HTML ファイル

assets の中に src フォルダを作成し、index.js という Javascript ファイル(エントリポイント)と custom.scss という Sass ファイルを配置します。

assets 内の images フォルダは custom.scss で背景画像を設定する場合に画像を配置するフォルダで、必要に応じて配置します。background などスタイルで指定する画像を配置してビルドすると Asset Modules により出力先の指定したフォルダに画像がコピーされます(この例の場合、単に HTML の img 要素で参照する画像はコピーされません)。

mySite フォルダの直下には表示確認用の index.html という HTML ファイルを配置します。

エントリポイント

エントリポイントの index.js では、import で webpack でインストールする bootstrap の Javascript と custom.scss(Sass ファイル)を読み込みます。

以下は全ての Bootstrap の JavaScript をインポートする例です。

mySite/assets/src/index.js(エントリポイント)
// 全ての Bootstrap の名前付きエクスポート(named export)をインポート
import * as bootstrap from 'bootstrap';

// スタイルシート(Sass)をインポート
import './custom.scss';

または、プラグインの一部だけが必要な場合は、必要に応じてプラグインを個別にインポートできます。

import Alert from 'bootstrap/js/dist/alert';

//または、以下のように複数の必要なプラグインを指定します。
import { Tooltip, Toast, Popover } from 'bootstrap';

参考:

スタイルのインポート

プリコンパイルされた Sass のインポート

custom.scss では Bootstrap の変数を定義(上書き)して Bootstrap のスタイルをカスタマイズします。Bootstrap の変数は !default フラグが指定されているので、カスタマイズしたい変数を定義して、その記述の後で Bootstrap の Sass を読み込みます。

mySite/assets/src/custom.scss
/*
Bootstrap の変数をここで上書きして Bootstrap のスタイルをカスタマイズ
Bootstrap の変数は node_modules/bootstrap/scss/_variables.scss を参照
*/

/* Bootstrap の Sass を読み込む */
@import "~bootstrap/scss/bootstrap.scss";

webpack での @import

@import でパスの先頭にプレフィックス ~ を使用すると、node_modules のパスを解決するように webpack に指示できます。

上記の @import は以下と同じことです。

@import "../node_modules/bootstrap/scss/bootstrap";

Optimize(最適化)

必要なコンポーネントのみをインポートすることで読み込むファイルサイズを小さく(最適化)することができます。

Lean Sass imports

例えば、Sass の場合、以下のように個別にインポートするようにして、使用しないコンポーネントをコメントアウトすることができます。但し、相互に依存関係があるので注意が必要です。

mySite/assets/src/custom.scss
/*
Bootstrap の変数をここで上書きして Bootstrap のスタイルをカスタマイズ
Bootstrap の変数は node_modules/bootstrap/scss/_variables.scss を参照
*/

/* Bootstrap の Sass を読み込む */
// Configuration
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/utilities";

// Layout & components(使用しないものはコメントアウトするか削除)
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/images";
@import "~bootstrap/scss/containers";
@import "~bootstrap/scss/grid";
@import "~bootstrap/scss/tables";
@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/transitions";
@import "~bootstrap/scss/dropdown";
@import "~bootstrap/scss/button-group";
@import "~bootstrap/scss/nav";
@import "~bootstrap/scss/navbar";
@import "~bootstrap/scss/card";
@import "~bootstrap/scss/accordion";
@import "~bootstrap/scss/breadcrumb";
@import "~bootstrap/scss/pagination";
@import "~bootstrap/scss/badge";
@import "~bootstrap/scss/alert";
@import "~bootstrap/scss/progress";
@import "~bootstrap/scss/list-group";
@import "~bootstrap/scss/close";
@import "~bootstrap/scss/toasts";
@import "~bootstrap/scss/modal";
@import "~bootstrap/scss/tooltip";
@import "~bootstrap/scss/popover";
@import "~bootstrap/scss/carousel";
@import "~bootstrap/scss/spinners";
@import "~bootstrap/scss/offcanvas";
@import "~bootstrap/scss/placeholders";

// Helpers
@import "~bootstrap/scss/helpers";

参考:

Lean JavaScript

Bootstrap の dist フォルダにある基本的な JavaScript (bootstrap.js と bootstrap.min.js) は全てのコンポーネントを含んでいます。

また、バンドルファイル(bootstrap.bundle.js と boostrap.bundle.min.js) は主な依存ライブラリ(Popper.js)を含んでいます。

前述の Lean Sass imports のように個別に Sass のコンポーネントをインポートする場合、JavaScript でも必要なコンポーネントのみインクルードすることができます。

index.js
// button と modal の JavaScript のみをインクルードする場合
// import 'bootstrap/js/dist/alert';
import 'bootstrap/js/dist/button';
// import 'bootstrap/js/dist/carousel';
// import 'bootstrap/js/dist/collapse';
// import 'bootstrap/js/dist/dropdown';
import 'bootstrap/js/dist/modal';
// import 'bootstrap/js/dist/popover';
// import 'bootstrap/js/dist/scrollspy';
// import 'bootstrap/js/dist/tab';
// import 'bootstrap/js/dist/toast';
// import 'bootstrap/js/dist/tooltip';

// スタイルシート(Sass)をインポート
import './custom.scss';

Lean JavaScript(日本語) | Lean JavaScript

表示確認用の HTML

index.html では webpack でビルドして出力される style.css と main.js を読み込んで表示を確認します(この例では出力先フォルダは assets/dist になります)。以下の HTML では Bootstrap のボタンを表示しているだけですが、必要に応じて確認する項目などを追加します(この時点でビルドを実行していないので style.css と main.js はまだ出力されていません)。

mySite/index.html
<!doctype html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- スタイルの読み込み  -->
  <link rel="stylesheet" href="assets/dist/style.css">
  <title>webpack5/Bootstrap5 サンプル</title>
</head>

<body>
  <h1>webpack Sample</h1>
  <div>
    <h2>Button Sample</h2>
    <button type="button" class="btn btn-primary">Primary</button>
    <button type="button" class="btn btn-secondary">Secondary</button>
    <button type="button" class="btn btn-success">Success</button>
    <button type="button" class="btn btn-danger">Danger</button>
    <button type="button" class="btn btn-warning">Warning</button>
    <button type="button" class="btn btn-info">Info</button>
  </div>
  <!-- Javascript の読み込み  -->
  <script src="assets/dist/main.js"></script>
</body>
</html>

webpack とローダーをインストール

作業するフォルダ(この例では assets)に移動して、package.json を生成します。

デフォルトのオプションで package.json を生成すれば良いので -y オプションを指定して npm init コマンドを実行します。

$ cd /Applications/MAMP/htdocs/mySite/assets  return

$ npm init -y  return

Wrote to /Applications/MAMP/htdocs/mySite/assets/package.json:
//以下の内容の package.json が上記パスに生成されます
{
  "name": "assets",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
mySite
├── assets
│   ├── package.json //生成された package.json
│   └── src
│       ├── custom.scss
│       ├── images //空のフォルダ(必要に応じて配置)
│       └── index.js
├── images //空のフォルダ(必要に応じて配置)
└── index.html

package.json の "main": "index.js", は不要なので削除し、"private": true, を追加します。description や keywords、author、license も削除しても問題ありません。

変更後の assets/package.json
{
  "name": "assets",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

webpack と Bootstrap 5 を使うのに必要なローダーやプラグインなどのパッケージをインストールします。この例では以下のパッケージをインストールします。

最低限必要なパッケージ
パッケージ 説明
webpack webpack のモジュール
webpack-cli webpack のコマンドライン操作用のモジュール
css-loader CSSを変換するためのモジュール
sass-loader Sass を CSS へ変換するモジュール
sass Sass の本体。Bootstrap 5 は Dart Sass を採用しているので node-sass ではなく sass(dart-sass)をインストールします。
mini-css-extract-plugin CSS を別ファイルとして出力するプラグイン(または style-loader を使って CSS を head 要素内に出力)。

この例では以下のパッケージもインストールします。

必要に応じてインストールするパッケージ
パッケージ 説明
postcss-loader postCSS のローダー
postcss postcss-loader と合わせてインストール
postcss-preset-env CSS の新しい仕様を後方互換性を持って変換したり、ベンダープレフィックスを付与することができるプラグイン(内部に autoprefixer を含む)
babel-loader Babel のローダー
@babel/core Babel の本体(JavaScript のトランスパイラ)
@babel/preset-env Babel の環境設定用モジュール

assets ディレクトリで npm install コマンドに --save-dev オプションを指定してモジュールとプラグインをインストールします。以下は install の短縮形 i と --save-dev の短縮形 -D を使用しています。

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

$ npm i -D webpack webpack-cli css-loader sass sass-loader mini-css-extract-plugin  return

added 166 packages, and audited 167 packages in 30s

必要に応じて postCSS や Babel もインストールします。モジュール名の先頭の @ は Scoped packages という指定方法です。

$ npm i -D babel-loader @babel/core @babel/preset-env postcss-loader postcss postcss-preset-env  return

added 213 packages, and audited 380 packages in 10s

関連ページ

bootstrap のインストール

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

bootstrap 5 からは jQuery に依存していないので jQuery のインストールは不要です。また、popper.js も自動的にインストールされます。

$ npm install bootstrap  return

added 2 packages, and audited 382 packages in 2s

参考:パッケージマネージャ npm | Package managers

インストールされた Bootstrap などのパッケージは node_modules ディレクトリに格納されています。

ここまでのインストールでファイル構成は以下のようになっています。

mySite
├── assets
│   ├── node_modules  //npm でインストールされたパッケージのディレクトリ
│   │   ├── @babel
│   │   ├── @discoveryjs
│   │   ├── @popperjs
│   │   ├── @types
│   │   ├── @webassemblyjs
│   │   ├── @webpack-cli
│   │    ・・・中略・・・
│   │   ├── bootstrap
│   │    ・・・中略・・・
│   │   ├── webpack
│   │   ├── webpack-cli
│   │   ├── webpack-merge
│   │   ├── webpack-sources
│   │   ├── which
│   │   ├── wildcard
│   │   ├── yallist
│   │   └── yaml
│   ├── package-lock.json
│   ├── package.json
│   └── src
│       ├── custom.scss
│       ├── images(空)
│       └── index.js
├── images(空)
└── index.html 

この時点での package.json は以下のようになっています。

assets/package.json
{
  "name": "assets",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.7",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "mini-css-extract-plugin": "^2.4.6",
    "postcss": "^8.4.5",
    "postcss-loader": "^6.2.1",
    "postcss-preset-env": "^7.2.0",
    "sass": "^1.46.0",
    "sass-loader": "^12.4.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  },
  "dependencies": {
    "bootstrap": "^5.1.3"
  }
}

webpack.config.js の作成

webpack の設定ファイル webpack.config.js(JavaScript) を作成します。

mySite
├── assets
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   ├── images(空)
│   │   └── index.js
│   └── webpack.config.js  // webpack 設定ファイルを追加
├── images(空)
└── index.html 

以下のような内容を記述します。

この例ではエントリポイントと出力先はデフォルトの値(/src/index.js と main.js)を設定しているので、11行目、14〜15行目は省略可能です。

出力される CSS の名前は style.css にしていますが、必要に応じて変更します(92行目)。

また、以下の例では webpack を実行する際に NODE_ENV にパラメータとして production を渡せば、ソースマップファイルを出力しないようにしています(ソースマップの出力の制御)。モードに関わらず常にソースマップファイルを出力する場合は、devtool に source-map を指定します。

assets/webpack.config.js
//path モジュールの読み込み
const path = require('path');
//MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//変数 devMode は production モードの場合は false でその他の場合は true
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {

  //エントリポイント(デフォルトと同じなので省略可)
  entry: './src/index.js',
  //出力先
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    //ファイルを出力する前にディレクトリをクリーンアップ
    clean: true,
    //Asset Modules の出力先の指定
    assetModuleFilename: 'images/[name][ext][query]'
  },
  module: {
    rules: [
      //SASS 及び CSS 用のローダー
      {
        //拡張子 .scss、.sass、css を対象
        test: /\.(scss|sass|css)$/i,
        // 使用するローダーの指定
        use: [
          //CSS を別ファイルとして出力するプラグインのローダー
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              importLoaders: 2,
            },
          },
          // PostCSS ローダーの設定
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      //必要に応じて postcss-preset-env のオプションを指定
                    },
                  ],
                ],
              },
            },
          },
          //Sass ローダー
          'sass-loader',
        ],
      },
      // Babel 用のローダー(Babel を使用しない場合は58〜78行目の部分は不要)
      {
        // ローダーの処理対象ファイル(拡張子 .js のファイルを対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに使用するローダーやオプションを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
      //Asset Modules の設定
      {
        //対象とするアセットファイルの拡張子を正規表現で指定
        test: /\.(png|svg|jpe?g|gif)$/i,
        //画像をコピーして出力
        type: 'asset/resource'
      },
    ],
  },
  //プラグインの設定
  plugins: [
    new MiniCssExtractPlugin({
      // 抽出する CSS のファイル名
      filename: 'style.css',
    }),
  ],
  //production モード以外の場合は source-map タイプのソースマップを出力
  devtool: devMode ? 'source-map' : 'eval',
  // node_modules を監視(watch)対象から除外
  watchOptions: {
    ignored: /node_modules/  //正規表現で指定
  },
};

関連ページ

  • webpack.config.js / webpack の基本的な使い方
  • postCSS / webpack を使って Sass をコンパイルする方法

npm script を追加

package.json の scripts フィールドにコマンド(npm script)を追加して npm run コマンドで webpack を実行できるようにしておきます。

npm init -y で生成した際にデフォルトで記述されていた "test": "echo \..." の行は削除しても問題ないのでこの例では削除します(残す場合は行の最後にカンマを追加します)。

以下では build(ビルド)、dev(開発モード)、watch(監視モード) のコマンドを追加しています。

"scripts": {
  "build": "NODE_ENV=production webpack --mode production",
  "dev": "webpack --mode development",
  "watch": "webpack --mode development --watch"
},
スクリプト名 記述内容
build 環境変数 NODE_ENV に production を指定して production モードでビルドを実行するコマンド。 NODE_ENV=production npx webpack --mode production と同じ。
dev development モードでビルドを実行するコマンド。npx webpack --mode development と同じ。
watch ファイルを監視して変更があれば development モードでビルドを実行するコマンド。npx webpack --mode development --watch と同じ。
assets/package.json
{
  "name": "assets",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "build": "NODE_ENV=production webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.7",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "mini-css-extract-plugin": "^2.4.6",
    "postcss": "^8.4.5",
    "postcss-loader": "^6.2.1",
    "postcss-preset-env": "^7.2.0",
    "sass": "^1.46.0",
    "sass-loader": "^12.4.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  },
  "dependencies": {
    "bootstrap": "^5.1.3"
  }
}

ビルドの実行

npm script を追加してあるので npm run build や npm run dev でビルドを実行できます。例えば開発(development)モードでビルドするには assets ディレクトリで以下を実行します。

$ npm run dev  return  //development モード

> assets@1.0.0 dev
> webpack --mode development

asset main.js 304 KiB [emitted] (name: main) 1 related asset
asset style.css 207 KiB [emitted] (name: main) 1 related asset
Entrypoint main 512 KiB (578 KiB) = style.css 207 KiB main.js 304 KiB 2 auxiliary assets
orphan modules 550 KiB (javascript) 937 bytes (runtime) [orphan] 24 modules
runtime modules 937 bytes 4 modules
modules by path ./node_modules/@popperjs/core/lib/ 76.5 KiB
  modules by path ./node_modules/@popperjs/core/lib/dom-utils/*.js 18.8 KiB 22 modules
  modules by path ./node_modules/@popperjs/core/lib/utils/*.js 14.5 KiB 21 modules
  modules by path ./node_modules/@popperjs/core/lib/modifiers/*.js 29.9 KiB 10 modules
  modules by path ./node_modules/@popperjs/core/lib/*.js 13.3 KiB 5 modules
modules by path ./src/ 381 bytes (javascript) 207 KiB (css/mini-extract)
  ./src/index.js 331 bytes [built] [code generated]
  ./src/custom.scss 50 bytes [built] [code generated]
  css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./node_modules/sass-loader/dist/cjs.js!./src/custom.scss 207 KiB [built] [code generated]
./node_modules/bootstrap/dist/js/bootstrap.js 145 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 5361 ms

上記を実行すると dist フォルダが作成されて、main.js と style.css 及びソースマップファイル(main.js.map と style.css.map)が生成されます。

npm run dev は development モードで実行されるため、この例の場合はソースマップファイルも生成されます。

mySite
├── assets
│   ├── dist //ビルドの出力先
│   │   ├── main.js  //バンドルされた JavaScript
│   │   ├── main.js.map
│   │   ├── style.css  //スタイルシート
│   │   └── style.css.map
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   ├── images(空)
│   │   └── index.js
│   └── webpack.config.js
├── images(空)
└── index.html 

npm run build を実行する場合は、ファイルはミニファイされ、ソースマップは出力されません。

$ npm run build  return  //production モード

> assets@1.0.0 build
> NODE_ENV=production webpack --mode production

asset main.js 211 KiB [compared for emit] [minimized] (name: main)
asset style.css 167 KiB [compared for emit] (name: main)
Entrypoint main [big] 378 KiB = style.css 167 KiB main.js 211 KiB
orphan modules 391 KiB (javascript) 937 bytes (runtime) [orphan] 84 modules
runtime modules 670 bytes 3 modules
cacheable modules 210 KiB (javascript) 167 KiB (css/mini-extract)
  ./src/index.js + 56 modules 210 KiB [built] [code generated]
  css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./node_modules/sass-loader/dist/cjs.js!./src/custom.scss 167 KiB [built] [code generated]

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  main (378 KiB)
      style.css
      main.js


WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

WARNING in entrypoint size limit

プロダクションビルドを実行すると「次のエントリポイントの合計アセットサイズが推奨制限(244 KiB)を超えています。 これはWebのパフォーマンスに影響を与える可能性があります。」のような警告と「import() またはrequire.ensureを使用してバンドルのサイズを制限し、アプリケーションの一部を遅延ロードすることができます。」のような提言が表示されます。

インポートする JavaScriptSass のコンポーネントを絞り込んでサイズを縮小することもできますが、気にしなくても問題ない(?)かと思います。

watch モード

npm run watch を実行すれば watch モードになり、src フォルダのファイルに変更が発生すれば自動的にリビルドされます。watch モードを終了するには control + c を押します。

スタイルのカスタマイズ

Bootstrap のスタイルをカスタマイズするには Bootstrap で定義されている Sass 変数を上書きします。

Bootstrap の変数は !default フラグが指定されているので、カスタマイズしたい変数を定義(設定)して、その記述の後で Bootstrap の Sass を読み込みます。

参考:Bootstrap Sass | Options | Color

Bootstrap の変数は /node_modules/bootstrap/scss/_variables.scss で定義されています。

assets/node_modules/bootstrap/scss/_variables.scss 一部抜粋
// Variables
// Variables should follow the `$component-state-property-size` formula for consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.

// Color system

// scss-docs-start gray-color-variables
$white:    #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;

・・・中略・・・

// Body
// Settings for the `<body>` element.

$body-bg: $white !default;  //例:この行をコピーして値を変更し、!default を削除
$body-color: $gray-900 !default;
$body-text-align: null !default;

・・・中略・・・

// Options
// Quickly modify global styling by enabling or disabling optional features.

$enable-caret:   true !default;
$enable-rounded: true !default;
$enable-shadows: false !default;

・・・以下省略・・・

カスタマイズしたい変数と値を /node_modules/bootstrap/scss/_variables.scss からコピーし、値を変更して !default フラグを削除します。

変数の記述は、Bootstrap の読み込み(16行目の @import)よりも前に記述する必要があります。

assets/src/custom.scss
/*
Bootstrap の変数を上書きして Bootstrap のスタイルをカスタマイズ
*/

/*body の背景色($body-bg: $white !default;)を変更
_variables.scss から変数と値をコピーし、値を変更して !default フラグを削除*/
$body-bg: #fefefe;

/* body の文字色($body-color: $gray-900 !default;)を変更*/
$body-color: #777;

/*角丸設定($enable-rounded: true !default;)を変更*/
$enable-rounded: false;

/* Bootstrap の読み込み (これより前で変数を設定して変更) */
@import "~bootstrap/scss/bootstrap.scss";

カスタマイズ例

以下は Bootstrap スタイル(Sass)のカスタマイズと独自のスタイルを設定してビルドする例です。

Bootstrap のスタイルに関係しない独自のスタイルは Bootstrap の読み込みの後に記述していますが、別ファイルにしてインポートすることもできます。また、custom.scss には含めずに別途定義することもできます(以下は動作確認のための例で、ファイルやフォルダの構成はもっと良い方法があるかと思います)。

mySite
├── assets
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   ├── images
│   │   │   └── sample.jpg //custom.scss で背景画像に指定
│   │   └── index.js
│   └── webpack.config.js
├── images  // HTML から参照する画像
│   ├── carousel_1.png
│   ├── carousel_2.png
│   └── carousel_3.png
└── index.html
index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--  コンパイルされて出力された style.css の読み込み -->
<link rel="stylesheet" href="assets/dist/style.css">
<title>webpack5/Bootstrap5 サンプル</title>
</head>

<body>
<div class="container pt-5">
  <h1>webpack Sample</h1>
  <div class="py-3">
    <h2>Button Sample</h2>
    <!-- Bootstrap のボタンのサンプル -->
    <button type="button" class="btn btn-primary">Primary</button>
    <button type="button" class="btn btn-secondary">Secondary</button>
    <button type="button" class="btn btn-success">Success</button>
    <button type="button" class="btn btn-danger">Danger</button>
    <button type="button" class="btn btn-warning">Warning</button>
    <button type="button" class="btn btn-info">Info</button>
    <button type="button" class="btn btn-light">Light</button>
  </div>
  <!-- Bootstrap カルーセル(画像は mySite 直下の images に保存)-->
  <div id="carouselExampleControls" class="carousel slide mb-3" data-bs-ride="carousel">
    <div class="carousel-inner">
      <div class="carousel-item active">
        <img src="images/carousel_1.png" class="d-block w-100" alt="">
      </div>
      <div class="carousel-item">
        <img src="images/carousel_2.png" class="d-block w-100" alt="">
      </div>
      <div class="carousel-item">
        <img src="images/carousel_3.png" class="d-block w-100" alt="">
      </div>
    </div>
    <a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-bs-slide="prev">
      <span class="carousel-control-prev-icon" aria-hidden="true"></span>
      <span class="sr-only">Previous</span>
    </a>
    <a class="carousel-control-next" href="#carouselExampleControls" role="button" data-bs-slide="next">
      <span class="carousel-control-next-icon" aria-hidden="true"></span>
      <span class="sr-only">Next</span>
    </a>
  </div>
  <div>
    <!-- custom.scss で背景画像を指定した要素(画像は src/images に配置して dist/images にコピーされる) -->
    <p class="bg-img">sample</p>
  </div>
</div>
<!--  バンドルされて出力された main.js の読み込み -->
<script src="assets/dist/main.js"></script>
</body>
</html>
assets/src/index.js (エントリポイント)
// 全ての Bootstrap の名前付きエクスポート(named export)をインポート
import * as bootstrap from 'bootstrap';

// スタイルシートを読み込む
import './custom.scss';
assets/src/custom.scss
/*
Bootstrap の変数を以下で上書きして Bootstrap のスタイルを調整
Bootstrap の変数は node_modules/bootstrap/scss/_variables.scss を参照
*/

// body の背景色と文字色を上書き
$body-bg: #fefefe;
$body-color: #777;

//フォントを変更
$font-family-base: "ヒラギノ角ゴ ProN W3", "Hiragino Kaku Gothic ProN", Meiryo, メイリオ, Verdana, Arial, sans-serif;

//角丸設定を上書き
$enable-rounded: false;

//$theme-colors の $light を上書き
$light: #ccc;

/* Bootstrap の読み込み */
@import "~bootstrap/scss/bootstrap.scss";

/* 以下は Bootstrap のスタイルとは関係ないスタイル */

#carouselExampleControls {
  max-width: 600px;
}

$p_color: lightblue;

p.bg-img {
  color: $p_color;
  /* 背景画像を指定(Asset Modules の設定により画像は dist/images にコピーされる) */
  background-image: url("./images/sample.jpg");
  height: 300px;
  line-height: 300px;
  text-align: center;
  max-width: 600px;
}

npm run build でビルドを実行すると、バンドルされた JavaScript は出力先ディレクトリ dist に main.js というファイル名で生成され、Sass は CSS にコンパイルされ mini-css-extract-plugin により style.css として出力先ディレクトリ dist に生成されます。

また、custom.scss で背景画像に指定した画像は Asset Modules の設定により 出力先ディレクトリ dist 内にコピーされ、出力される style.css ではコピーされた画像が参照されます。

mySite
├── assets
│   ├── dist //ビルド出力先ディレクトリ
│   │   ├── images
│   │   │   └── sample.jpg //ビルド時にコピーされた背景画像
│   │   ├── main.js //バンドルされた JavaScript
│   │   └── style.css //Sass がコンパイルされて生成された CSS
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   ├── images
│   │   │   └── sample.jpg //custom.scss で背景画像に指定
│   │   └── index.js
│   └── webpack.config.js
├── images  // HTML から参照する画像
│   ├── carousel_1.png
│   ├── carousel_2.png
│   └── carousel_3.png
└── index.html

npm run dev を実行して開発モードでビルドすると、出力先ディレクトリ dist にソースマップファイルも出力されます。

mySite
├── assets
│   ├── dist
│   │   ├── images
│   │   │   └── sample.jpg
│   │   ├── main.js
│   │   ├── main.js.map  //ソースマップファイル
│   │   ├── style.css
│   │   └── style.css.map  //ソースマップファイル
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   ├── images
│   │   │   └── sample.jpg
│   │   └── index.js
│   └── webpack.config.js
├── images
│   ├── carousel_1.png
│   ├── carousel_2.png
│   └── carousel_3.png
└── index.html

以下は出力された style.css です。npm run dev の場合、/* 〜 */ のコメントアウトは残りますが、npm run build でビルドするとミニファイされ、コメントアウトも削除されます。

custom.scss により body のスタイル(font-family や color、background-color)などがカスタマイズされています。

また、postCSS により CSS カスタムプロパティの値が下位互換用に出力されていたり、ベンダープリフィックスが付与されています。

assets/dist/style.css の例
@charset "UTF-8";
/*
Bootstrap の変数を以下で上書きして Bootstrap のスタイルを調整
Bootstrap の変数は node_modules/bootstrap/scss/_variables.scss を参照
*/
/* Bootstrap の読み込み */
/*!
 * Bootstrap v5.1.3 (https://getbootstrap.com/)
 * Copyright 2011-2021 The Bootstrap Authors
 * Copyright 2011-2021 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 */
:root {
  --bs-blue: #0d6efd;
  --bs-indigo: #6610f2;
  --bs-purple: #6f42c1;
  --bs-pink: #d63384;
  --bs-red: #dc3545;

  ・・・中略・・・

  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
  --bs-body-font-family: ヒラギノ角ゴ ProN W3, Hiragino Kaku Gothic ProN, Meiryo, メイリオ, Verdana, Arial, sans-serif;
  --bs-body-font-size: 1rem;
  --bs-body-font-weight: 400;
  --bs-body-line-height: 1.5;
  --bs-body-color: #777;
  --bs-body-bg: #fefefe;
}

・・・中略・・・

body {
  margin: 0;
  font-family: ヒラギノ角ゴ ProN W3, Hiragino Kaku Gothic ProN, Meiryo, メイリオ, Verdana, Arial, sans-serif;
  font-family: var(--bs-body-font-family);
  font-size: 1rem;
  font-size: var(--bs-body-font-size);
  font-weight: 400;
  font-weight: var(--bs-body-font-weight);
  line-height: 1.5;
  line-height: var(--bs-body-line-height);
  color: #777;
  color: var(--bs-body-color);
  text-align: var(--bs-body-text-align);
  background-color: #fefefe;
  background-color: var(--bs-body-bg);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

・・・中略・・・

abbr[title],
abbr[data-bs-original-title] {
  -webkit-text-decoration: underline dotted;
          text-decoration: underline dotted;
  cursor: help;
  -webkit-text-decoration-skip-ink: none;
          text-decoration-skip-ink: none;
}

・・・中略・・・

/* 以下は Bootstrap のスタイルとは関係ないスタイル */
#carouselExampleControls {
  max-width: 600px;
}

p.bg-img {
  color: lightblue;
  background-image: url(images/sample.jpg);
  height: 300px;
  line-height: 300px;
  text-align: center;
  max-width: 600px;
}


/*# sourceMappingURL=style.css.map*/

以下は上記の場合の表示例です。custom.scss で $enable-rounded: false を指定しているのでボタンは角丸になっていません。また、.btn-light クラスのボタンの色はデフォルトより暗くなっています。

$theme-colors(テーマカラー)

$theme-colors の変数を変更するには、custom.scss で以下のように個別の変数の値を上書きします。 Bootstrap の読み込みよりも前に記述します。

assets/src/custom.scss
$primary: #5635A0;
$danger: #BC4906;

Bootstrap の _variables.scss で $theme-colors は以下のように定義されているので上記の設定により、.btn-primary や .btn-danger、.alert-primary、.alert-danger などの色に反映されます。

theme-colors-map(assets/node_modules/bootstrap/scss/_variables.scss)
$theme-colors: (
  "primary": $primary,
  "danger": $danger,
  "success": $success,
  ・・・

);

以下は _buttons.scss の一部抜粋です。$theme-colors マップを @each を使って .btn-primary などのクラスを生成しています。

assets/node_modules/bootstrap/scss/_buttons.scss
// scss-docs-start btn-variant-loops
@each $color, $value in $theme-colors {
  .btn-#{$color} {
    @include button-variant($value, $value);
  }
}

@each $color, $value in $theme-colors {
  .btn-outline-#{$color} {
    @include button-outline-variant($value);
  }
}

以下は _alert.scss の一部抜粋です。同様に $theme-colors マップを @each を使って .alert-primary などのクラスを生成しています。

assets/node_modules/bootstrap/scss/_alert.scss
@each $state, $value in $theme-colors {
  $alert-background: shift-color($value, $alert-bg-scale);
  $alert-border: shift-color($value, $alert-border-scale);
  $alert-color: shift-color($value, $alert-color-scale);
  @if (contrast-ratio($alert-background, $alert-color) < $min-contrast-ratio) {
    $alert-color: mix($value, color-contrast($alert-background), abs($alert-color-scale));
  }
  .alert-#{$state} {
    @include alert-variant($alert-background, $alert-border, $alert-color);
  }
}

Bootstrap 4 との違い

Bootstrap 4 では以下のように記述しますが、Bootstrap 5 では以下を記述すると $theme-colors の primary と danger 以外の設定が消えてしまいます。

custom.scss(Bootstrap 4 の場合:Bootstrap 5 では NG)
$theme-colors: (
  "primary": #5635A0,
  "danger":#BC4906
);

テーマカラーの追加

以下は $theme-colors へ新しい色を追加(変更)する例です(もっと良い方法があるかも知れません)。

全ての $theme-colors を記述し、$theme-colors マップを記述します。以下では既存の $primary などの色も上書きして変更しています。

そして、$custom-colors マップを用意して独自の色の追加し、map-merge で $theme-colors に $custom-colors をマージします。

※ 以下は Bootstrap の読み込みの前に記述します。

assets/src/custom.scss
/*全ての $theme-colors を上書き*/
$primary: #7e0f83;
$secondary: #364799;
$success: #1F501D;
$danger:#BE1A1C;
$warning:#F06B04;
$info: #cea6cc;
$light:#f8f9fa;
$dark:#212529;

/*$theme-colors マップを記述*/
$theme-colors: (
  "primary":    $primary,
  "secondary":  $secondary,
  "success":    $success,
  "info":       $info,
  "warning":    $warning,
  "danger":     $danger,
  "light":      $light,
  "dark":       $dark
);

/*独自の色の追加($custom-colors マップを用意)*/
$custom-colors: (
  "my-green": #648D61,
  "my-blue": #215FD9,
  "my-red": #C34E50
);

/*map-merge で  $theme-colors に $custom-colors をマージ*/
$theme-colors: map-merge($theme-colors, $custom-colors);

/*・・・その他の設定・・・*/

//navbar のバーガー(三本線)の色を変更する例(stroke の色を変更)
$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#BD9DC6' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>");

/* Bootstrap の読み込み */
@import "~bootstrap/scss/bootstrap.scss";

/* Bootstrap のスタイルとは関係ないスタイルは以下に記述 */
・・・

コンパイルすると、bg-my-blue や btn-my-red などで追加した色を使用することができます。

<p class="bg-my-blue text-light">Blue text</p>
<button type="button" class="btn btn-my-red">Red Button</button>

以下は map-merge の代わりにビルトインモジュールの sass:map の関数 map.merge を使う場合の例です。違いは map モジュールの読み込みと関数名だけです。

@use "sass:map";  /* map モジュールの読み込み */

/*全ての $theme-colors を上書き*/
$primary: #7e0f83;
$secondary: #364799;

・・・中略・・・

/*map.merge に変更*/
$theme-colors: map.merge($theme-colors, $custom-colors);

・・・以下省略・・・

$spacer(スペーシング) の追加

$spacer(マージンやパディングのスペーシング)の基準のデフォルトは 1rem で、6つのサイズが用意されていますが、これらも簡単に変更や追加ができます。

以下は _variables.scss に記述されているデフォルトの設定です。

※以前(v 5.0 まで)は $spacer * .25 は $spacer / 4 のようにスラッシュを使った除算で記述されていましたが、Dart Sass でスラッシュによる除算が非推奨になったため、乗算での記述に変更になっています(コンパイル時の警告が出ないようになりました)。

assets/node_modules/bootstrap/scss/ _variables.scss から抜粋
$spacer: 1rem !default;
$spacers: (
  0: 0,
  1: $spacer * .25,
  2: $spacer * .5,
  3: $spacer,
  4: $spacer * 1.5,
  5: $spacer * 3,
) !default;

以下は 4と5のサイズを変更し、更に3つのサイズを追加する例です。これで .mt-6 や .mx-7、.p-8 などのスペーシングのクラスが追加されます。

assets/src/custom.scss
$spacer: 1rem;
$spacers: (
  0: 0,
  1: $spacer * .25,
  2: $spacer * .5,
  3: $spacer,
  4: $spacer * 1.25, /* 変更 */
  5: $spacer * 1.5, /* 変更 */
  6: $spacer * 2, /* 追加 */
  7: $spacer * 3, /* 追加 */
  8: $spacer * 4, /* 追加 */
);

JavaScript

index.js(エントリポイント)で全ての Bootstrap の JavaScript をインポートするには以下のように記述することができます。以下は bootstrap を現在のスコープに加え、bootstrap のモジュール(bootstrap.js)のエクスポート全てをインポートします(参考:MDN import)。

または CommonJS(Node.js) の require() を使ってインポートすることもできます。

assets/src/index.js
// 全ての Bootstrap の名前付きエクスポート(named export)をインポート
import * as bootstrap from 'bootstrap';
//または const bootstrap = require('bootstrap');

・・・以下省略・・・

上記の場合、Tooltips などの自分で初期化する必要があるプラグインを使用するには、以下のように bootstrap を名前空間として bootstrap.Tooltip で Tooltip にアクセスして初期化することができます。

assets/src/index.js
import * as bootstrap from 'bootstrap';

//ページ上のすべてのツールチップを初期化
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
  //new bootstrap.Tooltip() でインスタンスを生成
  return new bootstrap.Tooltip(tooltipTriggerEl)
});

・・・以下省略・・・

全ての Bootstrap のモジュールをインポートするのではなく、必要なモジュールのみをインポートすることもできます。

以下は Tooltip と Carousel のみをインポートする場合のの例です。

import { Tooltip, Carousel} from 'bootstrap';

//または以下のように bootstrap/js/dist からインポートすることもできます
import  Tooltip from 'bootstrap/js/dist/tooltip';
import  Carousel from 'bootstrap/js/dist/carousel';

上記のように名前付きインポートの場合、Tooltips を初期化するには以下のようになります。

assets/src/index.js
import { Tooltip, Carousel} from 'bootstrap';

//ページ上のすべてのツールチップを初期化
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
  //new Tooltip() でインスタンスを生成
  return new Tooltip(tooltipTriggerEl)
});

Tooltip を表示する HTML の例。

index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--  コンパイルされて出力された dist/style.css の読み込み -->
<link rel="stylesheet" href="assets/dist/style.css">
<title>webpack5/Bootstrap5 サンプル</title>
</head>
<body>
<div class="container pt-5">
  <h1>webpack Sample</h1>
  <div class="py-3">
    <!-- Tooltip のサンプル -->
    <button type="button" class="btn btn-secondary mb-5" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">
      Tooltip on top
    </button>

   ・・・中略・・・

</div>
<!--  コンパイルされて出力された dist/main.js の読み込み -->
<script src="assets/dist/main.js"></script>
</body>
</html>

data 属性の名前の付け方が変更(-bs が追加)

bootstrap5 beta バージョンから data 属性の名前の付け方に変更があり、infix として -bs が追加されています。例えば、alpha 版や bootstrap4 までの data-toggle は data-bs-toggle に、data-placement は data-bs-placement になります。

jQuery

Bootstrap 5 は jQuery を使わないよう設計されていますが、Bootstrap のコンポーネントを jQuery と共に使うことができます(JavaScript/JQuery の利用 より)。

以下は jQuery を別途 CDN 経由で読み込んで使用する例です。

index.js では Bootstrap とスタイルシートを読み込みコンパイルします。

assets/src/index.js
// Bootstrap を読み込む
import * as bootstrap from 'bootstrap';

// スタイルシートを読み込む
import './custom.scss';

index.html ではコンパイルした Bootstrap(dist/main.js)を読み込む前に、jQueryを読み込みます。

index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--  コンパイルされて出力された style.css の読み込み -->
<link rel="stylesheet" href="assets/dist/style.css">
<title>webpack5/Bootstrap5 サンプル</title>
</head>
<body>
<div class="container">
    <!-- Tooltip のサンプル -->
    <button type="button" class="btn btn-secondary mb-5" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">
      Tooltip on top
    </button>
</div>
<!--  jQuery を CDN 経由でを読み込む -->
<script
  src="https://code.jquery.com/jquery-3.6.0.min.js"
  integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
  crossorigin="anonymous">
</script>
<!--  バンドルされて出力された main.js の読み込み -->
<script src="assets/dist/main.js"></script>
<script>
jQuery(function($){
  //jQuery でツールチップを初期化
  $('[data-bs-toggle="tooltip"]').tooltip() ;
});
</script>
</body>
</html>

参考:Bootstrap / JavaScript

開発用サーバ(DevServer)を設定

watch モード(npm run watch)を実行すれば、src フォルダのファイルに変更が発生すれば自動的にリビルドされますが、ブラウザは手動で再読み込みする必要があります。

webpack-dev-server を使って開発サーバを設定すれば、開発時にファイルの変更(index.html を含む)を自動的にブラウザに反映されるようにできます。以下は DevServer(webpack-dev-server)を使って開発用サーバを立ち上げる例です。

webpack-dev-server をインストールします。

$ npm install -D webpack-dev-server  return

added 187 packages, and audited 569 packages in 29s

npm scripts を追加

npm start で開発サーバを起動できるように package.json に npm scripts を追加します(6行目)。

5行目の "watch": "webpack --mode development --watch" の末尾にはカンマが必要です。

package.json 抜粋
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "NODE_ENV=production webpack --mode production",
  "dev": "webpack --mode development",
  "watch": "webpack --mode development --watch",
  "start": "webpack serve --mode development"
},
{
  "name": "assets",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "NODE_ENV=production webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch",
    "start": "webpack serve --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.7",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "mini-css-extract-plugin": "^2.4.6",
    "postcss": "^8.4.5",
    "postcss-loader": "^6.2.1",
    "postcss-preset-env": "^7.2.0",
    "sass": "^1.46.0",
    "sass-loader": "^12.4.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  },
  "dependencies": {
    "bootstrap": "^5.1.3"
  }
}

webpack-dev-server のオプションを追加

webpack.config.js に以下の webpack-dev-server のオプションを追加します。

この例の場合、公開するファイル(index.html)のディレクトリは webpack.config.js の1つ上の階層なので、static オプションに対象のディレクトリを directory: path.join(__dirname, '../') のように指定しています。単純に static: '../' でも同じです。

サーバー起動時にブラウザを自動的に起動するように open: true を指定し、ポート番号はデフォルトの 8080 から 3000 に変更しています。

また、デフォルトでは webpack-dev-server で生成された(バンドルされた)ファイルはメモリ上に保存されて実際には出力されないので、webpack-dev-middleware 関連の設定に writeToDisk: true を指定してバンドルされたファイルを出力する(実際に書き出す)ようにしています。

webpack.config.js 抜粋
devServer: {
    //表示する静的ファイルのディレクトリを指定
    static: {
      //対象のディレクトリを指定
      directory: path.join(__dirname, '../'),
    },
    // または static: '../',
    //サーバー起動時にブラウザを自動的に起動
    open: true,
    // ポート番号をデフォルトの 8080 から 3000 に変更
    port: 3000,
    //webpack-dev-middleware 関連の設定
    devMiddleware: {
      writeToDisk: true, //バンドルされたファイルを出力する(実際に書き出す)
    },
  },
webpack.config.js
//path モジュールの読み込み
const path = require('path');
//MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//変数 devMode は production モードの場合は false でその他の場合は true
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {

  //エントリポイント(デフォルトと同じなので省略可)
  entry: './src/index.js',
  //出力先(デフォルトと同じなので省略可)
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    //ファイルを出力する前にディレクトリをクリーンアップ
    clean: true,
    //Asset Modules の出力先の指定
    assetModuleFilename: 'images/[name][ext][query]'
  },

  //開発サーバ webpack-dev-server の設定
  devServer: {
    //表示する静的ファイルのディレクトリを指定
    static: {
      //対象のディレクトリを指定
      directory: path.join(__dirname, '../'),
    },
    // または static: '../',
    //サーバー起動時にブラウザを自動的に起動
    open: true,
    // ポート番号を変更
    port: 3000,
    //webpack-dev-middleware 関連の設定
    devMiddleware: {
      writeToDisk: true, //バンドルされたファイルを出力する(実際に書き出す)
    },
  },

  module: {
    rules: [
      //SASS 及び CSS 用のローダー
      {
        //拡張子 .scss、.sass、css を対象
        test: /\.(scss|sass|css)$/i,
        // 使用するローダーの指定
        use: [
          //CSS を別ファイルとして出力するプラグインのローダー
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              importLoaders: 2,
            },
          },
          // PostCSS ローダーの設定
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      //必要に応じて postcss-preset-env のオプションを指定
                    },
                  ],
                ],
              },
            },
          },
          //Sass ローダー
          'sass-loader',
        ],
      },
      // Babel 用のローダー(Babel を使用しない場合はこの部分は不要)
      {
        // ローダーの処理対象ファイル(拡張子 .js のファイルを対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに使用するローダーやオプションを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
      //Asset Modules の設定
      {
        //対象とするアセットファイルの拡張子を正規表現で指定
        test: /\.(png|svg|jpe?g|gif)$/i,
        //画像をコピーして出力
        type: 'asset/resource'
      },
    ],
  },
  //プラグインの設定
  plugins: [
    new MiniCssExtractPlugin({
      // 抽出する CSS のファイル名
      filename: 'style.css',
    }),
  ],
  //production モード以外の場合は source-map タイプのソースマップを出力
  devtool: devMode ? 'source-map' : 'eval',
  // node_modules を監視(watch)対象から除外
  watchOptions: {
    ignored: /node_modules/  //正規表現で指定
  },
};

開発サーバの起動

npm scripts で start に webpack serve --mode development を指定してあるので、コマンドラインで npm start を実行すると開発サーバが起動します。

$ npm start  return

> assets@1.0.0 start
> webpack serve --mode development

<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:3000/
<i> [webpack-dev-server] On Your Network (IPv4): http://192.168.11.2:3000/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:3000/
<i> [webpack-dev-server] Content not from webpack is served from '/Applications/MAMP/htdocs/mySite/' directory
<i> [webpack-dev-middleware] wait until bundle finished: /
asset main.js 523 KiB [emitted] (name: main) 1 related asset
asset style.css 236 KiB [emitted] (name: main) 1 related asset
asset images/sample.jpg 192 KiB [emitted] [from: src/images/sample.jpg] (auxiliary name: main)
Entrypoint main 759 KiB (1.02 MiB) = style.css 236 KiB main.js 523 KiB 3 auxiliary assets
orphan modules 595 KiB (javascript) 192 KiB (asset) 12.8 KiB (runtime) [orphan] 29 modules
runtime modules 29.9 KiB 14 modules
modules by path ./node_modules/ 379 KiB
  modules by path ./node_modules/@popperjs/core/lib/ 76.5 KiB 58 modules
  modules by path ./node_modules/webpack-dev-server/client/ 56.8 KiB 12 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 4 modules
  modules by path ./node_modules/mini-css-extract-plugin/dist/hmr/*.js 5.16 KiB 2 modules
  ./node_modules/bootstrap/dist/js/bootstrap.esm.js 136 KiB [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]
modules by path ./src/ 845 bytes (javascript) 235 KiB (css/mini-extract)
  ./src/index.js 485 bytes [built] [code generated]
  ./src/custom.scss 360 bytes [built] [code generated]
  css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./node_modules/sass-loader/dist/cjs.js!./src/custom.scss 235 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 5371 ms

または、以下のように npx コマンドを実行しても同じです(--mode development は開発モード用のオプションで、省略可能です)。

$ npx webpack serve --mode development  return 

この例ではオプションでポート番号(port)に 3000 を指定しているので、http://localhost:3000/ でページが開きます。

index.html や style.scss を変更するとブラウザがリロードされ変更が自動的に反映されます。

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

サーバーへのアップロード

本番サーバーへは production モードでビルドして、出力される dist フォルダをアップロードします。

例えばビルド後の構成が以下のような場合、mySite を表示するには直下の index.html と images フォルダとその中身、及び dist フォルダとその中身をサーバーへアップロードします。

mySite
├── assets
│   ├── dist //ビルド出力先ディレクトリ(サーバーへアップロード)
│   │   ├── images
│   │   │   └── sample.jpg //ビルド時にコピーされた背景画像
│   │   ├── main.js //バンドルされた JavaScript
│   │   └── style.css //Sass がコンパイルされて生成された CSS
│   ├── node_modules
│   │   ├── @babel
│   │   ├── @discoveryjs
│   │   ├── @nodelib
│   │   ├── @popperjs
│   │    ・・・中略・・・
│   │   ├── webpack
│   │   ├── webpack-cli
│   │   ├── webpack-dev-middleware
│   │   ├── webpack-dev-server
│   │   ├── webpack-merge
│   │   ├── webpack-sources
│   │   ├── websocket-driver
│   │   ├── websocket-extensions
│   │   ├── which
│   │   ├── wildcard
│   │   ├── wrappy
│   │   ├── ws
│   │   ├── yallist
│   │   └── yaml
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   ├── images
│   │   │   └── sample.jpg //custom.scss で背景画像に指定
│   │   └── index.js
│   └── webpack.config.js
├── images  // HTML から参照する画像  (サーバーへアップロード)
│   ├── carousel_1.png
│   ├── carousel_2.png
│   └── carousel_3.png
└── index.html(サーバーへアップロード)

以下がサイトを表示するのに必要なファイルになります。

src フォルダや webpack.config.js、package.json などはサイトの表示に必要ではありませんが、バックアップとしてサーバーに保存しておいても問題ないと思いますので、node_modules 以外を全部アップロードするという方法もあります。

node_modules もアップロードしても問題があるわけではありませんがサイズが大きいです。

mySite
├── assets
│   └── dist //ビルド出力先ディレクトリ
│        ├── images
│        │   └── sample.jpg //ビルド時にコピーされた背景画像
│        ├── main.js //バンドルされた JavaScript
│        └── style.css //Sass がコンパイルされて生成された CSS
├── images  // HTML から参照する画像
│   ├── carousel_1.png
│   ├── carousel_2.png
│   └── carousel_3.png
└── index.html

CSS の背景画像

前述までのサンプルでは Sass(src/custom.scss)の background-image で指定した背景画像(src/images)はビルド時に Assets Modules により自動的にコピーされて dist フォルダ内の images に出力するようにしています。

また、index.html で img 要素で参照する画像は mySite 直下の images フォルダに保存しています。

以下は css-loader の設定でビルド時に画像をコピーしないようにして、CSS での背景画像も mySite 直下の images フォルダに保存する例です。

Sass(src/custom.scss)の background-image で指定する画像も mySite 直下の images フォルダに保存します。

mySite
├── assets
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   └── index.js
│   └── webpack.config.js
├── images
│   ├── carousel_1.png
│   ├── carousel_2.png
│   ├── carousel_3.png
│   └── sample.jpg //custom.scss で背景画像に指定
└── index.html
src/index.js
// Bootstrap の JavaScript をインポート
import * as bootstrap from 'bootstrap';

// カスタマイズ用のスタイルシートのインポート
import './custom.scss';

背景画像の指定はビルドして出力される assets/dist/style.css からのパスで指定します(17行目)。url() で指定したパスはビルド時に書き換えられず、そのままの値になります。

src/custom.scss
/*
Bootstrap の変数を上書きして Bootstrap のスタイルをカスタマイズ
*/
$body-bg: #fefefe;
$body-color: #777;
$font-family-base: "ヒラギノ角ゴ ProN W3", "Hiragino Kaku Gothic ProN", Meiryo, メイリオ, Verdana, Arial, sans-serif;
$enable-rounded: false;

/* Bootstrap の読み込み */
@import "~bootstrap/scss/bootstrap.scss";

/* 以下は Bootstrap とは関係ないスタイル */

p.bg-img {
  color: lightblue;
  /* 背景画像を指定(ビルド時にコピーしない→パスも書き換えられない) */
  background-image: url("../../images/sample.jpg");
  height: 300px;
  line-height: 300px;
  text-align: center;
  max-width: 600px;
}

#carouselExampleControls {
  max-width: 600px;
}

以下では38行目の p 要素に .bg-img を指定して背景画像を表示しています。

index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--  コンパイルされて出力された style.css の読み込み -->
<link rel="stylesheet" href="assets/dist/style.css">
<title>webpack5/Bootstrap5 サンプル</title>
</head>

<body>
<div class="container pt-5">
  <h1>webpack Sample</h1>
  <!-- Bootstrap カルーセル(画像は mySite 直下の images に保存)-->
  <div id="carouselExampleControls" class="carousel slide mb-3" data-bs-ride="carousel">
    <div class="carousel-inner">
      <div class="carousel-item active">
        <img src="images/carousel_1.png" class="d-block w-100" alt="">
      </div>
      <div class="carousel-item">
        <img src="images/carousel_2.png" class="d-block w-100" alt="">
      </div>
      <div class="carousel-item">
        <img src="images/carousel_3.png" class="d-block w-100" alt="">
      </div>
    </div>
    <a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-bs-slide="prev">
      <span class="carousel-control-prev-icon" aria-hidden="true"></span>
      <span class="sr-only">Previous</span>
    </a>
    <a class="carousel-control-next" href="#carouselExampleControls" role="button" data-bs-slide="next">
      <span class="carousel-control-next-icon" aria-hidden="true"></span>
      <span class="sr-only">Next</span>
    </a>
  </div>
  <div>
    <!-- custom.scss で背景画像を指定(画像は mySite 直下の images に保存) -->
    <p class="bg-img">sample</p>
  </div>
</div>
<!--  バンドルされて出力された main.js の読み込み -->
<script src="assets/dist/main.js"></script>
</body>
</html>

webpack.config.js では css-loader の設定に url: false を追加して URL の解決を無効にします。

webpack.config.js 抜粋
{
  loader: "css-loader",
  options: {
    url: false, //URL の解決を無効に
    importLoaders: 2,
  },
},
//path モジュールの読み込み
const path = require('path');
//MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//変数 devMode は production モードの場合は false でその他の場合は true
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {

  //エントリポイント(デフォルトと同じなので省略可)
  entry: './src/index.js',
  //出力先(デフォルトと同じなので省略可)
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    //ファイルを出力する前にディレクトリをクリーンアップ
    clean: true,
    //Asset Modules の出力先の指定
    assetModuleFilename: 'images/[name][ext][query]'
  },

  //webpack-dev-server の設定
  devServer: {
    //表示する静的ファイルのディレクトリを指定
    static: {
      //対象のディレクトリを指定
      directory: path.join(__dirname, '../'),
    },
    // または static: '../',
    //サーバー起動時にブラウザを自動的に起動
    open: true,
    // ポート番号を変更
    port: 3000,
    //webpack-dev-middleware 関連の設定
    devMiddleware: {
      writeToDisk: true, //バンドルされたファイルを出力する(実際に書き出す)
    },
  },

  module: {
    rules: [
      //SASS 及び CSS 用のローダー
      {
        //拡張子 .scss、.sass、css を対象
        test: /\.(scss|sass|css)$/i,
        // 使用するローダーの指定
        use: [
          //CSS を別ファイルとして出力するプラグインのローダー
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              url: false, //URL の解決を無効に
              importLoaders: 2,
            },
          },
          // PostCSS ローダーの設定
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      //必要に応じて postcss-preset-env のオプションを指定
                    },
                  ],
                ],
              },
            },
          },
          //Sass ローダー
          'sass-loader',
        ],
      },
      // Babel 用のローダー(Babel を使用しない場合はこの部分は不要)
      {
        // ローダーの処理対象ファイル(拡張子 .js のファイルを対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに使用するローダーやオプションを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
      //Asset Modules の設定
      {
        //対象とするアセットファイルの拡張子を正規表現で指定
        test: /\.(png|svg|jpe?g|gif)$/i,
        //画像をコピーして出力
        type: 'asset/resource'
      },
    ],
  },
  //プラグインの設定
  plugins: [
    new MiniCssExtractPlugin({
      // 抽出する CSS のファイル名
      filename: 'style.css',
    }),
  ],
  //production モード以外の場合は source-map タイプのソースマップを出力
  devtool: devMode ? 'source-map' : 'eval',
  // node_modules を監視(watch)対象から除外
  watchOptions: {
    ignored: /node_modules/  //正規表現で指定
  },
};

これでビルドすると、背景画像に指定した画像はコピーされず、また、background-image の url() は書き換えられずそのままコンパイルされます。

mySite
├── assets
│   ├── dist
│   │   ├── main.js
│   │   └── style.css  //background-image はサイト直下の images を参照
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   └── index.js
│   └── webpack.config.js
├── images
│   ├── carousel_1.png
│   ├── carousel_2.png
│   ├── carousel_3.png
│   └── sample.jpg  //custom.scss で背景画像に指定(style.css で参照)
└── index.html

この構成の場合でも css-loader の設定に url: false を指定しないと、Asset Modules の設定により dist 内に images フォルダが作成され画像はそこにコピーされ、style.css の background-image は dist 内の images を参照するようになります。

※但し、url: false を指定すると、画像以外のファイル(フォントファイルなど)も処理の対象外になります。関連項目:css-loader url オプション

Bootstrap Icons

Bootstrap Icons をインストールしてアイコンフォントとして利用する例です。

この例の Bootstrap Icons のバージョンは v1.7.2 です。

フォルダ構成は前述までの例と同じものを使います。

mySite
├── assets
│   ├── dist //ビルド出力先ディレクトリ
│   │   ├── images
│   │   ├── main.js //バンドルされた JavaScript
│   │   └── style.css //Sass がコンパイルされて生成された CSS
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   ├── images  // custom.scss から参照する画像のフォルダ
│   │   └── index.js
│   └── webpack.config.js
├── images  // index.html から参照する画像のフォルダ
└── index.html

Bootstrap Icons をアイコンフォントとして利用するには、Bootstrap Icons をインストールしてエントリポイントでアイコンフォントの CSS をインポートします。アイコンフォントの CSS で参照されているフォントファイルは Asset Modules により出力先にコピーされます。

assets ディレクトリで以下を実行して bootstrap-icons をインストールします。

$ npm i bootstrap-icons  return

added 1 package, and audited 570 packages in 2s

package.json の dependencies に bootstrap-icons が追加されます(33行目)。babel や postcss、webpack-dev-server は前述までの続きでインストールしてありますが、必須ではありません。

package.json
{
  "name": "assets",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "NODE_ENV=production webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch",
    "start": "webpack serve --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.7",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "mini-css-extract-plugin": "^2.4.6",
    "postcss": "^8.4.5",
    "postcss-loader": "^6.2.1",
    "postcss-preset-env": "^7.2.0",
    "sass": "^1.46.0",
    "sass-loader": "^12.4.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  },
  "dependencies": {
    "bootstrap": "^5.1.3",
    "bootstrap-icons": "^1.7.2"
  }
}

node_modules 内に bootstrap-icons ディレクトリが追加され、その中の font フォルダにアイコンフォントの CSS やフォントファイルが入っています。

assets/node_modules/bootstrap-icons
bootstrap-icons
    ├── LICENSE.md
    ├── README.md
    ├── bootstrap-icons.svg
    ├── font
    │   ├── bootstrap-icons.css  //アイコンフォントの CSS
    │   ├── bootstrap-icons.json
    │   ├── bootstrap-icons.scss
    │   ├── fonts
    │   │   ├── bootstrap-icons.woff  //フォントファイル
    │   │   └── bootstrap-icons.woff2  //フォントファイル
    │   └── index.html
    └── package.json

以下はアイコンフォントの CSS(bootstrap-icons.css)の冒頭部分です。@font-face の src の url() で指定されているフォントファイルはビルド時に webpack によって取得されます(コピーされます)。

assets/node_modules/bootstrap-icons/font/bootstrap-icons.css 一部抜粋
@font-face {
  font-family: "bootstrap-icons";
  src: url("./fonts/bootstrap-icons.woff2?30af91bf14e37666a085fb8a161ff36d") format("woff2"),
url("./fonts/bootstrap-icons.woff?30af91bf14e37666a085fb8a161ff36d") format("woff");
}

.bi::before,
[class^="bi-"]::before,
[class*=" bi-"]::before {
  display: inline-block;
  font-family: bootstrap-icons !important;
  font-style: normal;
  font-weight: normal !important;
  font-variant: normal;
  text-transform: none;
  line-height: 1;
  vertical-align: -.125em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.bi-123::before { content: "\f67f"; }
.bi-alarm-fill::before { content: "\f101"; }
.bi-alarm::before { content: "\f102"; }
.bi-align-bottom::before { content: "\f103"; }
・・・以下省略・・・

webpack.config.js に Asset Modules の設定を追加

ビルド時にアイコンフォントのファイルを出力先の dist 内の fonts フォルダにコピーするようにするため、以下の Asset Modules の設定を webpack.config.js に追加します。

webpack.config.js 抜粋
//Asset Modules の設定(フォントファイル)
{
  test: /\.(woff|woff2|eot|ttf|otf)$/i, //対象とするフォントファイルの拡張子
  type: 'asset/resource', // フォントをコピーして以下の出力先に出力
  generator: {
    //出力先を指定(fonts フォルダにファイル名と拡張子で出力)
    filename: 'fonts/[name][ext][query]'
  }
},

上記を記述しない場合、この例では output の assetModuleFilename(アセットファイルの出力先)に images/[name][ext][query] を指定しているので、フォントファイルは dist 内の images フォルダにコピーされます。

webpack.config.js
//path モジュールの読み込み
const path = require('path');
//MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//変数 devMode は production モードの場合は false でその他の場合は true
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {

  //エントリポイント(デフォルトと同じなので省略可)
  entry: './src/index.js',
  //出力先(デフォルトと同じなので省略可)
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    //ファイルを出力する前にディレクトリをクリーンアップ
    clean: true,
    //Asset Modules の出力先の指定
    assetModuleFilename: 'images/[name][ext][query]'
  },

  //webpack-dev-server の設定
  devServer: {
    //表示する静的ファイルのディレクトリを指定
    static: {
      //対象のディレクトリを指定
      directory: path.join(__dirname, '../'),
    },
    // または static: '../',
    //サーバー起動時にブラウザを自動的に起動
    open: true,
    // ポート番号を変更
    port: 3000,
    //webpack-dev-middleware 関連の設定
    devMiddleware: {
      writeToDisk: true, //バンドルされたファイルを出力する(実際に書き出す)
    },
  },

  module: {
    rules: [
      //SASS 及び CSS 用のローダー
      {
        //拡張子 .scss、.sass、css を対象
        test: /\.(scss|sass|css)$/i,
        // 使用するローダーの指定
        use: [
          //CSS を別ファイルとして出力するプラグインのローダー
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              importLoaders: 2,
            },
          },
          // PostCSS ローダーの設定
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      //必要に応じて postcss-preset-env のオプションを指定
                    },
                  ],
                ],
              },
            },
          },
          //Sass ローダー
          'sass-loader',
        ],
      },
      // Babel 用のローダー(Babel を使用しない場合はこの部分は不要)
      {
        // ローダーの処理対象ファイル(拡張子 .js のファイルを対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに使用するローダーやオプションを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
      //Asset Modules の設定
      {
        //対象とするアセットファイルの拡張子を正規表現で指定
        test: /\.(png|svg|jpe?g|gif)$/i,
        //画像をコピーして出力
        type: 'asset/resource'
      },
      //Asset Modules の設定(フォントファイル)
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i, //対象とするフォントファイルの拡張子
        type: 'asset/resource', // フォントをコピーして出力
        generator: {
          //出力先を指定(fonts フォルダにファイル名と拡張子で出力)
          filename: 'fonts/[name][ext][query]'
        }
      },
    ],
  },
  //プラグインの設定
  plugins: [
    new MiniCssExtractPlugin({
      // 抽出する CSS のファイル名
      filename: 'style.css',
    }),
  ],
  //production モード以外の場合は source-map タイプのソースマップを出力
  devtool: devMode ? 'source-map' : 'eval',
  // node_modules を監視(watch)対象から除外
  watchOptions: {
    ignored: /node_modules/  //正規表現で指定
  },
};

エントリポイントで Bootstrap Icons の CSS をインポート

エントリポイントの index.js で Bootstrap Icons の CSS(bootstrap-icons.css)をインポートします。

assets/src/index.js
// Bootstrap の JavaScript をインポート
import * as bootstrap from 'bootstrap';

// Bootstrap Icons の CSS をインポート(追加)
import 'bootstrap-icons/font/bootstrap-icons.css';

// カスタマイズ用のスタイルシートのインポート
import './custom.scss';

ビルドを実行

npm run build や npm run dev などでビルドを実行します。

$ npm run dev  return

> assets@1.0.0 dev
> webpack --mode development

assets by chunk 581 KiB (name: main)
  asset main.js 296 KiB [compared for emit] (name: main) 1 related asset
  asset style.css 284 KiB [emitted] (name: main) 1 related asset
assets by path fonts/ 210 KiB
  asset fonts/bootstrap-icons.woff?30af91bf14e37666a085fb8a161ff36d 120 KiB [emitted] [from: node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff?30af91bf14e37666a085fb8a161ff36d] (auxiliary name: main)
  asset fonts/bootstrap-icons.woff2?30af91bf14e37666a085fb8a161ff36d 89.9 KiB [emitted] [from: node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2?30af91bf14e37666a085fb8a161ff36d] (auxiliary name: main)
Entrypoint main 581 KiB (890 KiB) = style.css 284 KiB main.js 296 KiB 4 auxiliary assets
orphan modules 757 KiB (javascript) 210 KiB (asset) 1.02 KiB (runtime) [orphan] 28 modules
runtime modules 1.57 KiB 7 modules
・・・以下省略・・・

ビルドを実行すると、フォントファイルは Asset Modules により出力先にコピーされ、Bootstrap Icons の CSS は css-loader と MiniCssExtractPlugin により style.css に出力されます。

mySite
├── assets
│   ├── dist
│   │   ├── fonts
│   │   │   ├── bootstrap-icons.woff  //コピーされたフォントファイル
│   │   │   └── bootstrap-icons.woff2  //コピーされたフォントファイル
│   │   ├── main.js
│   │   └── style.css
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── custom.scss
│   │   ├── images
│   │   └── index.js
│   └── webpack.config.js
├── images
└── index.html
assets/dist/style.css(development モードの場合の出力例)
@font-face {
  font-family: "bootstrap-icons";
  src: url(fonts/bootstrap-icons.woff2?30af91bf14e37666a085fb8a161ff36d) format("woff2"), url(fonts/bootstrap-icons.woff?30af91bf14e37666a085fb8a161ff36d) format("woff");
}
.bi::before,
[class^=bi-]::before,
[class*=" bi-"]::before {
  display: inline-block;
  font-family: bootstrap-icons !important;
  font-style: normal;
  font-weight: normal !important;
  font-feature-settings: normal;
  font-variant: normal;
  text-transform: none;
  line-height: 1;
  vertical-align: -0.125em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.bi-123::before {
  content: "\f67f";
}

.bi-alarm-fill::before {
  content: "\f101";
}

.bi-alarm::before {
  content: "\f102";
}

・・・以下省略・・・

アイコンフォントの表示

i 要素に表示したいアイコンのクラス名を指定して表示します。

index.html
<i class="bi-alarm"></i>

必要に応じて、インラインやスタイルシートでスタイルを指定することができます。

index.html
<p><i class="bi-alarm" style="font-size: 2rem; color: red;"></i> Bootstrap Icon</p>

css-loader url オプション

CSS の背景画像の例のように css-loader の設定(webpack.config.js)で url オプションに false を指定して URL の解決を無効にすると、背景画像だけではなく url() で参照しているフォントファイルもコピーされません。

背景画像と同じように、フォントファイルを手動で配置することもできますが、css-loader の url オプションの filter プロパティを使ってフォントファイルのみ自動でコピーすることができます。

以下は背景画像などの画像はコピーせずに、指定したフォントファイルのみ自動的にコピーして dist フォルダに出力する設定の例です。

filter プロパティで url に fonts/bootstrap-icons を含む場合は true を返して、URL の処理(解決)を有効にして、それ以外の場合は false を返して無効にしています。

includes() は指定した文字列が含まれるかどうかを判定します。この例ではファイル名の一部も含めていますが、フォルダ名のみを指定して url.includes("fonts/") などでも問題ありません。

webpack.config.js 抜粋
{
  loader: "css-loader",
  options: {
    //url: false, //URL の解決を無効に
    url: {
      filter: (url) => {
        // url に fonts/bootstrap-icons を含む場合は URL を処理(解決)
        if (url.includes("fonts/bootstrap-icons")) {
          return true;
        }
        // fonts 以外の URL の解決を無効に
        return false;
      },
    },
    importLoaders: 2,
  },
},

エントリポイントでインポートしている bootstrap-icons.css の @font-face の src は以下のように fonts フォルダ内に読み込むフォントファイル(bootstrap-icons.woff と .woff2)があります。

bootstrap-icons.css 抜粋
@font-face {
  font-family: "bootstrap-icons";
  src: url("./fonts/bootstrap-icons.woff2?30af91bf14e37666a085fb8a161ff36d") format("woff2"),
url("./fonts/bootstrap-icons.woff?30af91bf14e37666a085fb8a161ff36d") format("woff");
}

この例では Asset Modules の設定で以下のように出力先を指定しているので、コピーされたフォントファイルは dist/fonts/ に出力されます。

webpack.config.js 抜粋
{
  test: /\.(woff|woff2|eot|ttf|otf)$/i, //対象とするフォントファイルの拡張子
  type: 'asset/resource', // フォントをコピーして出力
  generator: {
    //出力先を指定(fonts フォルダにファイル名と拡張子で出力)
    filename: 'fonts/[name][ext][query]'
  }
},
//path モジュールの読み込み
const path = require('path');
//MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//変数 devMode は production モードの場合は false でその他の場合は true
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {

  //エントリポイント(デフォルトと同じなので省略可)
  entry: './src/index.js',
  //出力先(デフォルトと同じなので省略可)
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    //ファイルを出力する前にディレクトリをクリーンアップ
    clean: true,
    //Asset Modules の出力先の指定
    assetModuleFilename: 'images/[name][ext][query]'
  },

  //webpack-dev-server の設定
  devServer: {
    //表示する静的ファイルのディレクトリを指定
    static: {
      //対象のディレクトリを指定
      directory: path.join(__dirname, '../'),
    },
    // または static: '../',
    //サーバー起動時にブラウザを自動的に起動
    open: true,
    // ポート番号を変更
    port: 3000,
    //webpack-dev-middleware 関連の設定
    devMiddleware: {
      writeToDisk: true, //バンドルされたファイルを出力する(実際に書き出す)
    },
  },

  module: {
    rules: [
      //SASS 及び CSS 用のローダー
      {
        //拡張子 .scss、.sass、css を対象
        test: /\.(scss|sass|css)$/i,
        // 使用するローダーの指定
        use: [
          //CSS を別ファイルとして出力するプラグインのローダー
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              url: {
                filter: (url) => {
                  // fonts/bootstrap-icons を含む場合は URL を処理(解決)
                  if (url.includes("fonts/bootstrap-icons")) {
                    return true;
                  }
                  // fonts 以外の URL の解決を無効に
                  return false;
                },
              },
              importLoaders: 2,
            },
          },
          // PostCSS ローダーの設定
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      //必要に応じて postcss-preset-env のオプションを指定
                    },
                  ],
                ],
              },
            },
          },
          //Sass ローダー
          'sass-loader',
        ],
      },
      // Babel 用のローダー(Babel を使用しない場合はこの部分は不要)
      {
        // ローダーの処理対象ファイル(拡張子 .js のファイルを対象)
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // 処理対象のファイルに使用するローダーやオプションを指定
        use: [
          {
            // 利用するローダーを指定
            loader: 'babel-loader',
            // ローダー(babel-loader)のオプションを指定
            options: {
              // プリセットを指定
              presets: [
                // targets を指定していないので、一律に ES5 の構文に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      },
      //Asset Modules の設定
      {
        //対象とするアセットファイルの拡張子を正規表現で指定
        test: /\.(png|svg|jpe?g|gif)$/i,
        //画像をコピーして出力
        type: 'asset/resource'
      },
      //Asset Modules の設定(フォントファイル)
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i, //対象とするフォントファイルの拡張子
        type: 'asset/resource', // フォントをコピーして出力
        generator: {
          //出力先を指定(fonts フォルダにファイル名と拡張子で出力)
          filename: 'fonts/[name][ext][query]'
        }
      },
    ],
  },
  //プラグインの設定
  plugins: [
    new MiniCssExtractPlugin({
      // 抽出する CSS のファイル名
      filename: 'style.css',
    }),
  ],
  //production モード以外の場合は source-map タイプのソースマップを出力
  devtool: devMode ? 'source-map' : 'eval',
  // node_modules を監視(watch)対象から除外
  watchOptions: {
    ignored: /node_modules/  //正規表現で指定
  },
};