Skip to main content Skip to docs navigation

Bootstrap and Webpack

Webpackを使ってBootstrapのCSSとJavaScriptをプロジェクトに取り込み、バンドルする方法を解説した公式ガイドです。

最後まで読み飛ばしたいですか? このガイドのソースコードと動作するデモをtwbs/examplesリポジトリからダウンロードしてください。また、StackBlitzでサンプルを開き、リアルタイムで編集することもできます。

設定

Bootstrapを使ったWebpackプロジェクトをゼロから構築するため、実際に始める前にいくつかの前提条件と前段階があります。このガイドでは、Node.jsをインストールし、ターミナルをある程度使いこなしている必要があります。

  1. プロジェクトフォルダを作成し、npmを設定します。 my-projectを作成し、npmがすべての対話的な質問をしないように、-y引数で初期化することにします。

    mkdir my-project && cd my-project
    npm init -y
    
  2. Webpackをインストールします。 次に、Webpackの開発用依存ファイルをインストールする必要があります。: Webpackのコアとなるwebpack、ターミナルからWebpackコマンドを実行するためのwebpack-cli、ローカル開発サーバーを実行するためのwebpack-dev-serverです。この依存関係がプロダクションではなく開発専用であることを伝えるため --save-devを使います。

    npm i --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin
    
  3. Bootstrapをインストールします。 これでBootstrapをインストールすることができました。ドロップダウン、ポップオーバー、ツールチップの配置はPopperに依存しているので、Popperもインストールします。これらのコンポーネントを使用する予定がない場合は、ここでPopperを省略することができます。

    npm i --save bootstrap @popperjs/core
    
  4. 追加の依存関係をインストールします。 WebpackとBootstrapに加えて、BootstrapのCSSとJSを適切にインポートしてWebpackでバンドルするために、さらにいくつかの依存関係が必要です。これらには、Sass、いくつかのローダー、およびAutoprefixerが含まれます。

    npm i --save-dev autoprefixer css-loader postcss-loader sass sass-loader style-loader
    

これで、必要な依存関係がすべてインストールされたので、プロジェクトファイルの作成とBootstrapのインポートに取り掛かることができます。

プロジェクトの構成

すでにmy-projectフォルダを作成し、npmを初期化しました。次に、srcdistフォルダを作成して、プロジェクトの構造を完成させます。my-projectから以下を実行するか、手動で以下のようなフォルダとファイル構造を作成します。

mkdir {src,src/js,src/scss}
touch src/index.html src/js/main.js src/scss/styles.scss webpack.config.js

完成すると、このような形になるはずです:

my-project/
├── src/
│   ├── js/
│   │   └── main.js
│   ├── scss/
│   │   └── styles.scss
│   └── index.html
├── package-lock.json
├── package.json
└── webpack.config.js

この時点で、すべてが正しい場所にありますが、Webpackが動作しないのは、まだwebpack.config.jsを埋めていないためです。

Webpackを設定する

依存関係がインストールされ、プロジェクトフォルダでコーディングを開始できるようになったので、Webpackを設定し、ローカルでプロジェクトを実行できるようにします。

  1. webpack.config.jsをエディタで開きます。 空白なので、サーバーを起動できるように、いくつかの定型的な設定を追加する必要があります。この設定部分は、WebpackがプロジェクトのJavaScriptを探す場所、コンパイルしたコードを出力する場所(dist)、開発サーバーの動作(ホットリロードで distフォルダから取得)を指示します。

    'use strict'
    
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      mode: 'development',
      entry: './src/js/main.js',
      output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
      },
      devServer: {
        static: path.resolve(__dirname, 'dist'),
        port: 8080,
        hot: true
      },
      plugins: [
        new HtmlWebpackPlugin({ template: './src/index.html' })
      ]
    }
    
  2. 次に、src/index.htmlを入力します。 これは、後のステップで追加するバンドルされたCSSとJSを利用するために、Webpackがブラウザに読み込ませるHTMLページです。その前に、レンダリングするものを与え、前のステップのoutputJSを含める必要があります。

    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Bootstrap w/ Webpack</title>
      </head>
      <body>
        <div class="container py-4 px-3 mx-auto">
          <h1>Hello, Bootstrap and Webpack!</h1>
          <button class="btn btn-primary">Primary button</button>
        </div>
      </body>
    </html>
    

    ここでは、div class="container"<button>でBootstrapのスタイリングを少し入れて、BootstrapのCSSがWebpackで読み込まれたときに表示されるようにしています。

  3. あとは、Webpackを実行するためのnpmスクリプトが必要です。 package.jsonを開き、以下に示す startスクリプトを追加します(すでにテストスクリプトはあるはずです)。このスクリプトを使用して、ローカルのWebpack開発サーバーを起動することにします。

    {
      // ...
      "scripts": {
        "start": "webpack serve",
        "build": "webpack build --mode=production",
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      // ...
    }
    
  4. そして、最後にWebpackを起動します。 ターミナルにあるmy-projectフォルダから、新しく追加したnpmスクリプトを実行します:

    npm start
    
    Webpack dev server running

このガイドの最後となる次のセクションでは、Webpackローダーをセットアップし、BootstrapのすべてのCSSとJavaScriptをインポートします。

Bootstrapをインポートする

BootstrapをWebpackにインポートするには、最初のセクションでインストールしたローダーが必要です。npmでインストールしましたが、今度はWebpackがそれらを使用するように設定する必要があります。

  1. webpack.config.jsでローダーを設定します。 これで設定ファイルは完成し、以下のようになります。ここで唯一新しい部分はmoduleセクションです。

    'use strict'
    
    const path = require('path')
    const autoprefixer = require('autoprefixer')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      mode: 'development',
      entry: './src/js/main.js',
      output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
      },
      devServer: {
        static: path.resolve(__dirname, 'dist'),
        port: 8080,
        hot: true
      },
      plugins: [
        new HtmlWebpackPlugin({ template: './src/index.html' })
      ],
      module: {
        rules: [
          {
            test: /\.(scss)$/,
            use: [
              {
                // Adds CSS to the DOM by injecting a `<style>` tag
                loader: 'style-loader'
              },
              {
                // Interprets `@import` and `url()` like `import/require()` and will resolve them
                loader: 'css-loader'
              },
              {
                // Loader for webpack to process CSS with PostCSS
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    plugins: () => [
                      autoprefixer
                    ]
                  }
                }
              },
              {
                // Loads a SASS/SCSS file and compiles it to CSS
                loader: 'sass-loader'
              }
            ]
          }
        ]
      }
    }
    

    ここで、なぜこれらのローダーが必要なのか、その理由をまとめてみました。style-loaderはHTMLページの<head>内の<style>要素にCSSを注入し、css-loader@importurl()の使用を助け、postcss-loaderはAutoprefixerに必要で、sass-loaderでSassを使えるようにします。

  2. では、BootstrapのCSSをインポートしてみましょう。 Bootstrapの全てのソースSassを取得するにはsrc/scss/styles.scssに以下を追加してください。

    // Import all of Bootstrap's CSS
    @import "bootstrap/scss/bootstrap";
    

    また、必要であれば、スタイルシートを個別にインポートすることもできます。詳しくは、Sass importのドキュメントをご覧ください

  3. 次に、CSSを読み込み、BootstrapのJavaScriptをインポートします。 src/js/main.jsに以下を追加し、CSSを読み込み、BootstrapのJSを全てインポートします。PopperはBootstrapを通して自動的にインポートされます。

    // Import our custom CSS
    import '../scss/styles.scss'
    
    // Import all of Bootstrap's JS
    import * as bootstrap from 'bootstrap'
    

    また、必要に応じてJavaScriptプラグインを個別にインポートすることで、バンドルサイズを抑えることができます:

    import Alert from 'bootstrap/js/dist/alert'
    
    // or, specify which plugins you need:
    import { Tooltip, Toast, Popover } from 'bootstrap'
    

    Bootstrapのプラグインの使用方法については、JavaScriptのドキュメントをお読みください

  4. で、終了です! 🎉 BootstrapのソースであるSassとJSが完全に読み込まれ、ローカルの開発サーバーはこのような状態になりました。

    Webpack dev server running with Bootstrap

    これで、使用したいBootstrapコンポーネントを追加し始めることができます。カスタムSassを追加して、BootstrapのCSSとJSの必要な部分のみをインポートしてビルドを最適化する方法については、Webpackの完全なサンプルプロジェクトを必ずチェックしてください。

プロダクションの最適化

あなたのセットアップによっては、プロダクションでプロジェクトを実行するのに便利な追加のセキュリティと速度の最適化を実装したい場合があります。これらの最適化はWebpackのサンプルプロジェクトでは適用されないので、実装するのはあなた次第であることに注意してください。

CSSを抽出する

上記で設定した style-loaderは、CSSをバンドルに出力するので、手動でdist/index.htmlにあるCSSファイルを読み込む必要がありません。しかし、この方法は厳格なContent Security Policyでは機能しないかもしれませんし、バンドルサイズが大きいためアプリケーションのボトルネックになるかもしれません。

CSSを分離してdist/index.htmlから直接読み込めるようにするには、Webpackプラグインのmini-css-extract-loaderを使用します。

まず、プラグインをインストールします:

npm install --save-dev mini-css-extract-plugin

そして、Webpackの設定でプラグインをインスタンス化して使用します:

--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,6 +3,7 @@
 const path = require('path')
 const autoprefixer = require('autoprefixer')
 const HtmlWebpackPlugin = require('html-webpack-plugin')
+const miniCssExtractPlugin = require('mini-css-extract-plugin')

 module.exports = {
   mode: 'development',
@@ -17,7 +18,8 @@ module.exports = {
     hot: true
   },
   plugins: [
-    new HtmlWebpackPlugin({ template: './src/index.html' })
+    new HtmlWebpackPlugin({ template: './src/index.html' }),
+    new miniCssExtractPlugin()
   ],
   module: {
     rules: [
@@ -25,8 +27,8 @@ module.exports = {
         test: /\.(scss)$/,
         use: [
           {
-            // Adds CSS to the DOM by injecting a `<style>` tag
-            loader: 'style-loader'
+            // Extracts CSS for each JS file that includes CSS
+            loader: miniCssExtractPlugin.loader
           },
           {

npm run buildを再度実行すると、src/js/main.jsでインポートしたCSSをすべて含むdist/main.cssというファイルが新たに作成されるはずです。今ブラウザでdist/index.htmlを表示すると、dist/main.cssにあるように、スタイルが欠落しているはずです。このように、生成されたCSSをdist/index.htmlに含めることができます:

--- a/dist/index.html
+++ b/dist/index.html
@@ -3,6 +3,7 @@
   <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" href="./main.css">
     <title>Bootstrap w/ Webpack</title>
   </head>
   <body>

SVGファイルの取り出し

BootstrapのCSSには、インラインのdata:URIを介したSVGファイルへの複数の参照が含まれています。もし、プロジェクトのコンテンツセキュリティポリシーで、画像のdata:URIをブロックするように定義している場合、これらのSVGファイルはロードされません。この問題は、Webpackのアセットモジュール機能を使ってインラインのSVGファイルを抽出することで回避できます。

このようにインラインのSVGファイルを抽出するようにWebpackを設定します:

--- a/webpack.config.js
+++ b/webpack.config.js
@@ -23,6 +23,14 @@ module.exports = {
   },
   module: {
     rules: [
+      {
+        mimetype: 'image/svg+xml',
+        scheme: 'data',
+        type: 'asset/resource',
+        generator: {
+          filename: 'icons/[hash].svg'
+        }
+      },
       {
         test: /\.(scss)$/,
         use: [

再度npm run buildを実行すると、SVGファイルがdist/iconsに展開され、CSSから正しく参照されていることが確認できます。


何か間違っていたり、古くなっていたりしませんか?GitHubで課題を開いてください。トラブルシューティングのヘルプが必要ですか?GitHubで検索するか、ディスカッションを開始してください。

` const jsSnippetContent = jsSnippet ? '\/\/ NOTICE!!! Initially embedded in our docs this JavaScript\n\/\/ file contains elements that can help you create reproducible\n\/\/ use cases in StackBlitz for instance\.\n\/\/ In a real project please adapt this content to your needs\.\n\/\/ \u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\u002b\n\n\/\*!\n \* JavaScript for Bootstrap\u0027s docs \(https:\/\/getbootstrap\.com\/\)\n \* Copyright 2011\-2023 The Bootstrap Authors\n \* Licensed under the Creative Commons Attribution 3\.0 Unported License\.\n \* For details, see https:\/\/creativecommons\.org\/licenses\/by\/3\.0\/\.\n \*\/\n\n\/\* global bootstrap: false \*\/\n\n\(\(\) =\u003e \{\n \u0027use strict\u0027\n\n \/\/ \-\-\-\-\-\-\-\-\n \/\/ Tooltips\n \/\/ \-\-\-\-\-\-\-\-\n \/\/ Instantiate all tooltips in a docs or StackBlitz\n document\.querySelectorAll\(\u0027\[data\-bs\-toggle=\u0022tooltip\u0022\]\u0027\)\n \.forEach\(tooltip =\u003e \{\n new bootstrap\.Tooltip\(tooltip\)\n \}\)\n\n \/\/ \-\-\-\-\-\-\-\-\n \/\/ Popovers\n \/\/ \-\-\-\-\-\-\-\-\n \/\/ Instantiate all popovers in docs or StackBlitz\n document\.querySelectorAll\(\u0027\[data\-bs\-toggle=\u0022popover\u0022\]\u0027\)\n \.forEach\(popover =\u003e \{\n new bootstrap\.Popover\(popover\)\n \}\)\n\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Toasts\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Used by \u0027Placement\u0027 example in docs or StackBlitz\n const toastPlacement = document\.getElementById\(\u0027toastPlacement\u0027\)\n if \(toastPlacement\) \{\n document\.getElementById\(\u0027selectToastPlacement\u0027\)\.addEventListener\(\u0027change\u0027, function \(\) \{\n if \(!toastPlacement\.dataset\.originalClass\) \{\n toastPlacement\.dataset\.originalClass = toastPlacement\.className\n \}\n\n toastPlacement\.className = `\$\{toastPlacement\.dataset\.originalClass\} \$\{this\.value\}`\n \}\)\n \}\n\n \/\/ Instantiate all toasts in docs pages only\n document\.querySelectorAll\(\u0027\.bd\-example \.toast\u0027\)\n \.forEach\(toastNode =\u003e \{\n const toast = new bootstrap\.Toast\(toastNode, \{\n autohide: false\n \}\)\n\n toast\.show\(\)\n \}\)\n\n \/\/ Instantiate all toasts in docs pages only\n \/\/ js\-docs\-start live\-toast\n const toastTrigger = document\.getElementById\(\u0027liveToastBtn\u0027\)\n const toastLiveExample = document\.getElementById\(\u0027liveToast\u0027\)\n\n if \(toastTrigger\) \{\n const toastBootstrap = bootstrap\.Toast\.getOrCreateInstance\(toastLiveExample\)\n toastTrigger\.addEventListener\(\u0027click\u0027, \(\) =\u003e \{\n toastBootstrap\.show\(\)\n \}\)\n \}\n \/\/ js\-docs\-end live\-toast\n\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Alerts\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Used in \u0027Show live alert\u0027 example in docs or StackBlitz\n\n \/\/ js\-docs\-start live\-alert\n const alertPlaceholder = document\.getElementById\(\u0027liveAlertPlaceholder\u0027\)\n const appendAlert = \(message, type\) =\u003e \{\n const wrapper = document\.createElement\(\u0027div\u0027\)\n wrapper\.innerHTML = \[\n `\u003cdiv class=\u0022alert alert\-\$\{type\} alert\-dismissible\u0022 role=\u0022alert\u0022\u003e`,\n ` \u003cdiv\u003e\$\{message\}\u003c\/div\u003e`,\n \u0027 \u003cbutton type=\u0022button\u0022 class=\u0022btn\-close\u0022 data\-bs\-dismiss=\u0022alert\u0022 aria\-label=\u0022Close\u0022\u003e\u003c\/button\u003e\u0027,\n \u0027\u003c\/div\u003e\u0027\n \]\.join\(\u0027\u0027\)\n\n alertPlaceholder\.append\(wrapper\)\n \}\n\n const alertTrigger = document\.getElementById\(\u0027liveAlertBtn\u0027\)\n if \(alertTrigger\) \{\n alertTrigger\.addEventListener\(\u0027click\u0027, \(\) =\u003e \{\n appendAlert\(\u0027Nice, you triggered this alert message!\u0027, \u0027success\u0027\)\n \}\)\n \}\n \/\/ js\-docs\-end live\-alert\n\n \/\/ \-\-\-\-\-\-\-\-\n \/\/ Carousels\n \/\/ \-\-\-\-\-\-\-\-\n \/\/ Instantiate all non\-autoplaying carousels in docs or StackBlitz\n document\.querySelectorAll\(\u0027\.carousel:not\(\[data\-bs\-ride=\u0022carousel\u0022\]\)\u0027\)\n \.forEach\(carousel =\u003e \{\n bootstrap\.Carousel\.getOrCreateInstance\(carousel\)\n \}\)\n\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Checks \u0026 Radios\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Indeterminate checkbox example in docs and StackBlitz\n document\.querySelectorAll\(\u0027\.bd\-example\-indeterminate \[type=\u0022checkbox\u0022\]\u0027\)\n \.forEach\(checkbox =\u003e \{\n if \(checkbox\.id\.includes\(\u0027Indeterminate\u0027\)\) \{\n checkbox\.indeterminate = true\n \}\n \}\)\n\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Links\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Disable empty links in docs examples only\n document\.querySelectorAll\(\u0027\.bd\-content \[href=\u0022#\u0022\]\u0027\)\n \.forEach\(link =\u003e \{\n link\.addEventListener\(\u0027click\u0027, event =\u003e \{\n event\.preventDefault\(\)\n \}\)\n \}\)\n\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Modal\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Modal \u0027Varying modal content\u0027 example in docs and StackBlitz\n \/\/ js\-docs\-start varying\-modal\-content\n const exampleModal = document\.getElementById\(\u0027exampleModal\u0027\)\n if \(exampleModal\) \{\n exampleModal\.addEventListener\(\u0027show\.bs\.modal\u0027, event =\u003e \{\n \/\/ Button that triggered the modal\n const button = event\.relatedTarget\n \/\/ Extract info from data\-bs\-\* attributes\n const recipient = button\.getAttribute\(\u0027data\-bs\-whatever\u0027\)\n \/\/ If necessary, you could initiate an Ajax request here\n \/\/ and then do the updating in a callback\.\n\n \/\/ Update the modal\u0027s content\.\n const modalTitle = exampleModal\.querySelector\(\u0027\.modal\-title\u0027\)\n const modalBodyInput = exampleModal\.querySelector\(\u0027\.modal\-body input\u0027\)\n\n modalTitle\.textContent = `New message to \$\{recipient\}`\n modalBodyInput\.value = recipient\n \}\)\n \}\n \/\/ js\-docs\-end varying\-modal\-content\n\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ Offcanvas\n \/\/ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n \/\/ \u0027Offcanvas components\u0027 example in docs only\n const myOffcanvas = document\.querySelectorAll\(\u0027\.bd\-example\-offcanvas \.offcanvas\u0027\)\n if \(myOffcanvas\) \{\n myOffcanvas\.forEach\(offcanvas =\u003e \{\n offcanvas\.addEventListener\(\u0027show\.bs\.offcanvas\u0027, event =\u003e \{\n event\.preventDefault\(\)\n \}, false\)\n \}\)\n \}\n\}\)\(\)\n' : null const project = { files: { 'index.html': markup, 'index.js': jsSnippetContent }, title: 'Bootstrap Example', description: `Official example from ${window.location.href}`, template: jsSnippet ? 'javascript' : 'html', tags: ['bootstrap'] } StackBlitzSDK.openProject(project, { openFile: 'index.html' }) }