Webpack を使って Bootstrap 5 をインストール

Webpack を使って Bootstrap 5 をインストールする方法の覚書(メモ)です。

現時点(2020年11月14日)では Bootstrap 5 は alpha 版なので、以下の方法は今後変更が必要になる可能性があります。

以下は Node.js がインストールされていることを前提にしています。また、使用している主なモジュールのバージョンは以下になります。

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

作成日:2020年11月14日

関連ページ:

ファイル構成

この例では bs5 というフォルダ(任意の名前のフォルダ)を作成し、 Webpack を使って Bootstrap 5 をインストールします。

その中に src フォルダを作成し、index.js という Javascript ファイルと custom.scss という Sass ファイルを配置します。

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

images フォルダは CSS で背景画像を設定する場合に画像を配置するフォルダで、必要に応じて配置します。background などスタイルで指定する画像を配置してビルドすると file-loader により dist フォルダに画像がコピーされます(単に HTML で参照する画像はコピーされません)。

bs5
├── index.html  //表示用 HTML ファイル 
└── src  //開発用フォルダ
    ├── images // 背景画像を配置するフォルダ(必要に応じて)
    ├── index.js  //Javascript ファイル(エントリーポイント)
    └── custom.scss  // Sass ファイル

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

以下は全ての Bootstrap のプラグインを bootstrap オブジェクトにロードする例です。

参考:Download npm | Webpack Importing JavaScript

index.js
// Bootstrap を読み込む(全ての Bootstrap のプラグインを bootstrap オブジェクトにロード)
import bootstrap from '../node_modules/bootstrap/dist/js/bootstrap';
// または const bootstrap = require('bootstrap')  でも同じ

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

Javascript は以下のように必要な Bootstrap のプラグインを個別に指定して読み込むこともできます。

import { Tooltip, Toast, Popover } from 'bootstrap';

custom.scss では Bootstrap の変数を定義(上書き)して Bootstrap のスタイルを調整します。その記述の後で Bootstrap の Sass を読み込みます。

custom.scss
/*
Bootstrap の変数をここで上書きして Bootstrap のスタイルを調整
Bootstrap の変数は node_modules/bootstrap/scss/_variables.scss を参照
*/

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

webpack での @import

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

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

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

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

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="dist/style.css">
<title>webpack 読み込みサンプル</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="dist/main.js"></script>
</body>
</html>

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

作業するフォルダに移動して、package.json を生成します。

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

$ cd /Applications/MAMP/htdocs/bs5/  return
        
$ npm init -y  return
Wrote to /Applications/MAMP/htdocs/bs5/package.json:
//以下の内容の package.json が上記パスに生成されます
{
  "name": "bs5",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
bs5          
├── index.html 
├── package.json //生成された package.json
└── src 
   ├── images
   ├── index.js
   └── custom.scss 

webpack と Bootstrap 5 を使うのに必要なモジュールやローダーをインストールします。

この例では最低限必要と思われる Sass(CSS)関連のモジュールをインストールしていますが、必要に応じて Babel や ESLint などのモジュールをインストールします。

モジュール 説明
webpack webpack のモジュール
webpack-cli コマンドライン操作用のパッケージ
css-loader CSSを処理するためのモジュール
file-loader ファイルの import / require() を URL に解決してファイルを出力(画像をコピー)するモジュール
sass Sass のモジュール。Bootstrap 5 Alpha 3 では Dart Sass になっているので node-sass ではなく sass(dart-sass)をインストールしています。
sass-loader Sass を CSS へ変換するモジュール
fibers Sass のコンパイルの速度を速くするモジュール
postcss-loader postCSS のモジュール(autoprefixer に必要)
autoprefixer ベンダープレフィックスを付与するモジュール
mini-css-extract-plugin CSS を別ファイルとして出力するモジュール

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

bootstrap のインストール

bootstrap 関連のモジュールをインストールします。

Bootstrap は Popper に依存しているので popper.js もインストールする必要があります。

この時点(2020年11月13日)では bootstrap 5 はまだ alpha 版なので bootstrap@next としてインストールします。

また、bootstrap 5 からは jQuery に依存していないので jQuery のインストールは不要です。

インストールされた Bootstrap と Popper などは node_modules ディレクトリに格納されています。

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

bs5          
├── index.html 
├── node_modules  //npm でインストールされるパッケージのディレクトリ
├── package-lock.json
├── package.json
└── src  //開発用フォルダ
   ├── images
   ├── index.js
   └── custom.scss 

また、package.json は以下のようになっています。

package.json
{
  "name": "bs5",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^10.0.2",
    "css-loader": "^5.0.1",
    "fibers": "^5.0.0",
    "file-loader": "^6.2.0",
    "mini-css-extract-plugin": "^1.3.0",
    "postcss-loader": "^4.0.4",
    "sass": "^1.29.0",
    "sass-loader": "^10.1.0",
    "webpack": "^5.4.0",
    "webpack-cli": "^4.2.0"
  },
  "dependencies": {
    "bootstrap": "^5.0.0-alpha3",
    "popper.js": "^1.16.1"
  }
}

webpack.config.js の作成

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

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

この例では以下のような内容を記述します。

15行目は背景画像を images フォルダに配置した場合、Error: Automatic publicPath is not supported in this browser というエラーが出ないようにするための publicPath の設定です。

この例ではエントリポイントと出力先はデフォルトの値(/src/index.js と main.js)を設定していますが、必要に応じて変更します(変更した場合は HTML での読み込みも変更します)。

出力される CSS の名前は style.css にしていますが、こちらも必要に応じて変更します。

webpack.config.js
//path モジュールの読み込み
const path = require('path');
//MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {

  //エントリポイント(デフォルトと同じなので省略可)
  entry: './src/index.js',  
  //出力先(デフォルトと同じなので省略可)
  output: { 
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    // publicPath を追加
    publicPath: '',
  },
  module: {
    rules: [
      {
        // 対象となるファイルの拡張子(scss)
        test: /\.scss$/,
        // Sassファイルの読み込みとコンパイル
        use: [
          // CSSファイルを抽出するように MiniCssExtractPlugin のローダーを指定
          {
            loader: MiniCssExtractPlugin.loader,
          },
          // CSSをバンドルするためのローダー
          {
            loader: "css-loader",
            options: {
              // ソースマップを有効に
              sourceMap: true,
              // css-loader の前に適用されるローダーの数を指定
              importLoaders: 2,
            },
          },
          // PostCSS
          {
            loader: "postcss-loader",
            options: {
              // PostCSS でもソースマップを有効に
              sourceMap: true,
              postcssOptions: {
                // ベンダープレフィックスを自動付与
                plugins: ["autoprefixer"],
              },
            },
          },
          // Sass をコンパイルするローダー
          {
            loader: "sass-loader",
            options: {
              // dart-sass を優先
              implementation: require('sass'),
              sassOptions: {
                // fibers を使わない場合は以下で false を指定
                fiber: require('fibers'),
              },
              sourceMap: true,
            },
          },
        ],
      },
      {
        //file-loader の対象となるファイルの拡張子
        test: /\.(gif|png|jpe?g|svg|eot|wof|woff|ttf)$/i,
        use: [
          {
            //画像を出力フォルダーにコピーするローダー
            loader: 'file-loader',
            options: {
              // 画像ファイルの名前とパスの設定
              name: './images/[name].[ext]'
            }
          }
        ],
      },
    ],
  },
  //プラグインの設定
  plugins: [
    //MiniCssExtractPlugin プラグインのインスタンスを生成
    new MiniCssExtractPlugin({
      //出力される CSS のファイル名を指定
      filename: "style.css",
    }),
  ],
  //source-map タイプのソースマップを出力
  devtool: "source-map",
};

npm script を追加

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

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

"scripts": {
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  • build: production モードでビルド(npx webpack --mode production と同じ)
  • dev: development モードでビルド(npx webpack --mode development と同じ)
  • watch: ファイルを監視して変更があれば development モードでリビルド(npx webpack --mode development --watch と同じ)
package.json
{
  "name": "bs5",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^10.0.2",
    "css-loader": "^5.0.1",
    "fibers": "^5.0.0",
    "file-loader": "^6.2.0",
    "mini-css-extract-plugin": "^1.3.0",
    "postcss-loader": "^4.0.4",
    "sass": "^1.29.0",
    "sass-loader": "^10.1.0",
    "webpack": "^5.4.0",
    "webpack-cli": "^4.2.0"
  },
  "dependencies": {
    "bootstrap": "^5.0.0-alpha3",
    "popper.js": "^1.16.1"
  }
}

ビルドの実行

npm script を追加してあるので npm run build でビルドを実行できます。

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

bs5
├── dist //ビルドの出力先
│   ├── main.js
│   ├── main.js.map
│   ├── style.css
│   └── style.css.map   
├── index.html 
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── images 
│   ├── index.js  
│   └── custom.scss  
└── webpack.config.js 

npm run build は --production モードで実行されるため、コンパイルされて生成されるファイルはミニファイされています。

style.css の例
/*!
 * Bootstrap v5.0.0-alpha3 (https://getbootstrap.com/)
 * Copyright 2011-2020 The Bootstrap Authors
 * Copyright 2011-2020 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-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-primary: #0d6efd;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,*::before,*::after{box-sizing:border-box}@media(prefers-reduced-motion: no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);
 ・・・以下省略・・・

npm run dev を実行すれば開発モードでビルドされるので、出力されるファイルはミニファイされません。

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

スタイルのカスタマイズ

Bootstrap のスタイルをカスタマイズするには、custom.scss に変更したい Sass 変数に値を設定してデフォルト値を上書きします(必要に応じて変数と値を /node_modules/bootstrap/scss/_variables.scss からコピーし、その値を変更して !default フラグを削除します)。

また、変数の記述は、Bootstrap の @import よりも前に記述する必要があります。

参考:Bootstrap Sass | Options | Color

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 のカスタマイズと独自のスタイルを設定してコンパイルする例です。

Bootstrap のスタイルに関係しない独自のスタイルは Bootstrap の読み込みの後に記述していますが、実際には別ファイルにしてインポートする方が管理しやすいかと思います。または、custom.scss には含めずに(一緒にコンパイルせずに)別途定義するなどした方が良いかも知れません。

bs5
├── dist 
│   ├── main.js
│   ├── style.css
│   └── images  //ビルド時にコピーされてここに出力される
│        └── sample.jpg   
├──  images // HTML から参照する画像 
│   ├── carousel_1.png
│   ├── carousel_2.png
│   └── carousel_3.png
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── images
│   │   └── sample.jpg  //背景画像(ビルド時に dist/images にコピーされる) 
│   ├── index.js  
│   └── style.scss  
└── webpack.config.js 
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="dist/style.css">
<title>webpack 読み込みサンプル</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>
  <!-- カルーセル :画像は bs5/images に保存-->
  <div id="carouselExampleControls" class="carousel slide mb-3" data-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-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-slide="next">
      <span class="carousel-control-next-icon" aria-hidden="true"></span>
      <span class="sr-only">Next</span>
    </a>
  </div>
  <div>
    <!-- 背景画像を指定した要素:画像は src/images に配置して dist/images にコピーされる -->
    <p class="bg-img">sample</p>
  </div>
</div>
<!--  コンパイルされて出力された dist/main.js の読み込み -->
<script src="dist/main.js"></script>
</body>
</html>
index.js (エントリポイント)
// Bootstrap を読み込む
import bootstrap from '../node_modules/bootstrap/dist/js/bootstrap';
// または const bootstrap = require('bootstrap');

// スタイルシートを読み込む
import './custom.scss';
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;
  background-image: url("./images/sample.jpg");
  height: 300px;
  line-height: 300px;
  text-align: center;
  max-width: 600px;
}

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

custom.scss により、--bs-light や body のスタイルなどがカスタマイズされています。

style.css
@charset "UTF-8";
/*
Bootstrap の変数を以下で上書きして Bootstrap のスタイルを調整
Bootstrap の変数は node_modules/bootstrap/scss/_variables.scss を参照
*/
/* Bootstrap の読み込み */
/*!
 * Bootstrap v5.0.0-alpha3 (https://getbootstrap.com/)
 * Copyright 2011-2020 The Bootstrap Authors
 * Copyright 2011-2020 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-orange: #fd7e14;
  --bs-yellow: #ffc107;
  --bs-green: #198754;
  --bs-teal: #20c997;
  --bs-cyan: #0dcaf0;
  --bs-white: #fff;
  --bs-gray: #6c757d;
  --bs-gray-dark: #343a40;
  --bs-primary: #0d6efd;
  --bs-secondary: #6c757d;
  --bs-success: #198754;
  --bs-info: #0dcaf0;
  --bs-warning: #ffc107;
  --bs-danger: #dc3545;
  --bs-light: #ccc;
  --bs-dark: #212529;
  --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

@media (prefers-reduced-motion: no-preference) {
  :root {
    scroll-behavior: smooth;
  }
}

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

[tabindex="-1"]:focus:not(:focus-visible) {
  outline: 0 !important;
}

・・・中略・・・

/* 以下は 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 で以下のように個別の変数の値を上書きします。

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

上記の設定により、Bootstrap の $theme-colors map(マップ)は以下のようにセットされ、.btn-primary や .btn-danger、.link-primary、.link-danger などの色に反映されます。

$theme-colors map
$theme-colors: (
  "primary": $primary,
  "danger": $danger
);

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

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

JavaScript

index.js(エントリポイント)で Bootstrap の JavaScript をインポートする際に、以下のようにパスを指定しないでインポートすると Tooltips などの自分で初期化する必要があるプラグインを例えば以下のように初期化するとうまくいきません。

index.js
// Bootstrap を読み込む(Tooltip などが初期化できない例)
import bootstrap from 'bootstrap';

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

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

npm でインストールしたモジュールは webpack のモジュール解決の仕組みがあるのでパスや拡張子を省略できますが、上記の場合コンパイルすると以下のような WARNING が表示されてしまいます

$ npm run build

> bs5@1.0.0 build /Applications/MAMP/htdocs/bs5
> webpack --mode production
[webpack-cli] Compilation finished

//以下の WARNING が表示される
WARNING in ./src/index.js 10:13-30
export 'default' (imported as 'bootstrap') was not found in 'bootstrap' (possible exports: Alert, Button, Carousel, Collapse, Dropdown, Modal, Popover, ScrollSpy, Tab, Toast, Tooltip)

そのため、上記の場合、ツールチップが表示されず「Uncaught TypeError: Cannot read property 'Tooltip' of undefined」のようなエラーが表示されます。

但し、index.js でインポートする際に以下のようにパスを指定すると問題ありません。または require を使っても大丈夫です。

index.js
import bootstrap from '../node_modules/bootstrap/dist/js/bootstrap';
// または以下でも問題なし
// const bootstrap = require('bootstrap')  

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

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

または、個別に以下のように Tooltips だけを使う場合は、Tooltips だけをインポートしても大丈夫です。

index.js
// Bootstrap を読み込む(Tooltip を取得)
import { Tooltip } from 'bootstrap';

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

var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-toggle="tooltip"]'))
var 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="dist/style.css">
<title>webpack 読み込みサンプル</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-toggle="tooltip" data-placement="top" title="Tooltip on top">
      Tooltip on top
    </button>

   ・・・中略・・・

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

jQuery

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

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

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

index.js
// Bootstrap を読み込む
import bootstrap from '../node_modules/bootstrap/dist/js/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">
<!--  コンパイルされて出力された dist/style.css の読み込み -->
<link rel="stylesheet" href="dist/style.css">
<title>webpack 読み込みサンプル</title>
</head>
<body>
<div class="container">
    <!-- Tooltip のサンプル -->
    <button type="button" class="btn btn-secondary mb-5" data-toggle="tooltip" data-placement="top" title="Tooltip on top">
      Tooltip on top
    </button>
</div>
<!--  jQuery を CDN 経由でを読み込む -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!--  コンパイルされて出力された dist/main.js の読み込み -->
<script src="dist/main.js"></script>
<script>
jQuery(function($){
  //jQuery でツールチップを初期化
  $('[data-toggle="tooltip"]').tooltip() ;
});  
</script>
</body>

node_modules/bootstrap/dist/js/bootstrap.js を見ると、それぞれのコンポーネントごとに以下のような記述があり、Bootstrap が window オブジェクト内で jQuery を検出すると、全てのコンポーネントに jQuery プラグインを追加しているようです。

bootstrap.js 抜粋
・・・中略・・・
var getjQuery = function getjQuery() {
  var _window = window,
      jQuery = _window.jQuery;

  if (jQuery && !document.body.hasAttribute('data-no-jquery')) {
    return jQuery;
  }

  return null;
};

・・・中略(以下 Alert コンポーネント部分の抜粋)・・・

Alert.jQueryInterface = function jQueryInterface(config) {
  return this.each(function () {
    var data = Data.getData(this, DATA_KEY);

    if (!data) {
      data = new Alert(this);
    }

    if (config === 'close') {
      data[config](this);
    }
  });
};

・・・中略・・・
          
/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 * add .Alert to jQuery only if jQuery is present
 */

onDOMContentLoaded(function () {
  var $ = getjQuery();
  /* istanbul ignore if */

  if ($) {
    var JQUERY_NO_CONFLICT = $.fn[NAME];
    $.fn[NAME] = Alert.jQueryInterface;
    $.fn[NAME].Constructor = Alert;

    $.fn[NAME].noConflict = function () {
      $.fn[NAME] = JQUERY_NO_CONFLICT;
      return Alert.jQueryInterface;
    };
  }
});
・・・以下省略・・・

開発用サーバを設定

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

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

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

npm scripts を追加

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

package.json 抜粋
"scripts": {
  "build": "webpack --mode production",
  "dev": "webpack --mode development",
  "watch": "webpack --mode development --watch",
  "start": "webpack serve --mode development"
},
{
  "name": "bs5",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch",
    "start": "webpack serve --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^10.0.2",
    "css-loader": "^5.0.1",
    "fibers": "^5.0.0",
    "file-loader": "^6.2.0",
    "mini-css-extract-plugin": "^1.3.0",
    "postcss-loader": "^4.0.4",
    "sass": "^1.29.0",
    "sass-loader": "^10.1.0",
    "webpack": "^5.4.0",
    "webpack-cli": "^4.2.0",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "bootstrap": "^5.0.0-alpha3",
    "popper.js": "^1.16.1"
  }
}

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

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

この例の場合、公開するリソース(html ファイル)のルートディレクトリ(ドキュメントルート)は webpack.config.js と同じ位置(デフォルトと同じ)なので省略可能ですが contentBase は path.join(__dirname, '') としています。

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

webpack.config.js 抜粋
devServer: {
  //ルートディレクトリの指定
  contentBase: path.join(__dirname, ''),
  //サーバー起動時にブラウザを自動的に起動
  open: true,
  // ルートディレクトリのファイルを監視(変更があると自動的にリロードされる)
  watchContentBase: true,
  //バンドルされたファイルを出力する(実際に書き出す)
  writeToDisk: true
},
webpack.config.js
//path モジュールの読み込み
const path = require('path');
//MiniCssExtractPlugin の読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {

  //エントリポイント(デフォルトと同じなので省略可)
  entry: './src/index.js',  
  //出力先(デフォルトと同じなので省略可)
  output: { 
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    // publicPath を追加
    publicPath: '',
  },
  //開発サーバ(webpack-dev-server)の設定
  devServer: {
    //ルートディレクトリの指定
    contentBase: path.join(__dirname, ''),
    //サーバー起動時にブラウザを自動的に起動
    open: true,
    // ルートディレクトリのファイルを監視(変更があると自動的にリロードされる)
    watchContentBase: true,
    //バンドルされたファイルを出力する(実際に書き出す)
    writeToDisk: true
  },
  module: {
    rules: [
      {
        // 対象となるファイルの拡張子(scss)
        test: /\.scss$/,
        // Sassファイルの読み込みとコンパイル
        use: [
          // CSSファイルを抽出するように MiniCssExtractPlugin のローダーを指定
          {
            loader: MiniCssExtractPlugin.loader,
          },
          // CSSをバンドルするためのローダー
          {
            loader: "css-loader",
            options: {
              // ソースマップを有効に
              sourceMap: true,
              // css-loader の前に適用されるローダーの数を指定
              importLoaders: 2,
            },
          },
          // PostCSS
          {
            loader: "postcss-loader",
            options: {
              // PostCSS でもソースマップを有効に
              sourceMap: true,
              postcssOptions: {
                // ベンダープレフィックスを自動付与
                plugins: ["autoprefixer"],
              },
            },
          },
          // Sass をコンパイルするローダー
          {
            loader: "sass-loader",
            options: {
              // dart-sass を優先
              implementation: require('sass'),
              sassOptions: {
                // fibers を使わない場合は以下で false を指定
                fiber: require('fibers'),
              },
              sourceMap: true,
            },
          },
        ],
      },
      {
        // file-loader の対象となるファイルの拡張子
        test: /\.(gif|png|jpe?g|svg|eot|wof|woff|ttf)$/i,
        use: [
          {
            //画像を出力フォルダーにコピーするローダー
            loader: 'file-loader',
            options: {
              // 画像ファイルの名前とパスの設定
              name: './images/[name].[ext]'
            }
          }
        ],
      },
    ],
  },
  //プラグインの設定
  plugins: [
    //MiniCssExtractPlugin プラグインのインスタンスを生成
    new MiniCssExtractPlugin({
      //出力される CSS のファイル名を指定
      filename: "style.css",
    }),
  ],
  //source-map タイプのソースマップを出力
  devtool: "source-map",
};

開発サーバの起動

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

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

$ npx webpack serve --mode development  return 

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

また、オプションで watchContentBase: true を指定しているので、index.html や style.scss を変更するとブラウザがリロードされ変更が自動的に反映されます。

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