• 最終更新日:

webpack 5の設定方法 - Dart SassやAutoprefixer、Babel、画像の圧縮などを自動化する

webpack 5の設定方法 - Dart SassやAutoprefixer、Babel、画像の圧縮などを自動化する

今回はwebpack5の設定方法について説明します。

設定する内容は以下です。

  • Dart Sass
  • CSSのAutoprefixerの設定
  • JavaScriptをBabelでトランスパイル
  • 画像の圧縮
  • 開発サーバーの立ち上げ、ホットリロード
  • ライセンス情報をjsファイルの中に含める

説明する環境は以下です。

  • macOS Catalina v10.15.5
  • Visual Studio Code v1.57.0
  • webpack v 5.50.0
  • node.jsはインストール済み
この記事の目次

webpack 5の設定方法

サイト制作で必要な基本的なwebpackの設定です。
TypeScriptやPugを使ってサイト制作する方は以下の記事を合わせて参照してみてください。

ファルダ構成について

以下の画像にある【src】フォルダは作業スペースで、そこで作業した内容が【dist】フォルダに出力さます。

【src】と【dist】の中を見てみると、以下のようになっています。

webpackをインストールする

webpackをインストールする前に、プロジェクトの設定情報に必要なpackage.jsonを準備します。VSCodeを使っているなら【control + shift + @】でターミナルが表示されるので、以下のコマンドを打ってpackage.jsonを作成します。

npm init -y

package.jsonが作成できたら、以下のコマンドをターミナルで打ってwebpackをインストールします。

npm i -D webpack webpack-cli

webpack 5で使うパッケージをインストールする

今回の設定では以下のパッケージが必要になるので、1行ずつインストールしていきます。

npm i -D babel-loader @babel/preset-env @babel/core #Babel設定
npm i -D postcss-loader #Autoprefixerの設定用
npm i -D autoprefixer #Autoprefixer
npm i -D clean-webpack-plugin #必要ないファイル、フォルダを削除
npm i -D copy-webpack-plugin #圧縮した画像をコピーする
npm i -D mini-css-extract-plugin #CSSの出力用
npm i -D css-loader #CSS用のローダー
npm i -D fibers #Dart Sassのコンパイル処理を早くする
npm i -D sass #Sass用
npm i -D sass-loader #Sass用のローダー
npm i -D globule #ファイルの検索用
npm i -D html-webpack-plugin #HTMLの出力用
npm i -D imagemin-webpack-plugin #画像圧縮用
npm i -D imagemin-gifsicle #gif圧縮用
npm i -D imagemin-mozjpeg #jpg圧縮用
npm i -D imagemin-pngquant #png圧縮用
npm i -D imagemin-svgo #svg圧縮用
npm i -D webpack-dev-server #仮想サーバー立ち上げ用
npm i -D terser-webpack-plugin #パッケージのライセンス情報をjsファイルの中に含める

一括してインストールする場合は以下のように1行で書きます。

npm i -D babel-loader @babel/preset-env @babel/core postcss-loader autoprefixer clean-webpack-plugin copy-webpack-plugin mini-css-extract-plugin css-loader fibers sass sass-loader globule html-webpack-plugin imagemin-webpack-plugin imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo webpack-dev-server terser-webpack-plugin

インストールができるとpackage.jsonにパッケージ名とバージョンが載っているので確認してみましょう。以下は例です。

一部のパッケージがインストールできない場合

パッケージの依存関係が解決できず、エラーになってパッケージがインストールできない場合があります。インストールできないパッケージについては、以下のコマンドで強制的にインストールすることは可能です。ただ、互換性を無視するため動作の確認は必要になります。

npm i --legacy-peer-deps 【パッケージ名】

詳しくはこちらの記事を参照してください。

webpack.config.jsに設定を書く

webpackの設定を書くために、webpack.config.jsというファイルを作成して以下の内容を書きます。

//pathの読み込み
const path = require("path");
//パッケージのライセンス情報をjsファイルの中に含める
const TerserPlugin = require("terser-webpack-plugin");
//mini-css-extract-plugin の読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// clean-webpack-plugin の読み込み
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
// globuleの読み込み
const globule = require("globule");
// html-webpack-pluginの読み込み
const HtmlWebpackPlugin = require("html-webpack-plugin");
// copy-webpack-pluginの読み込み
const CopyPlugin = require("copy-webpack-plugin");
// imagemin-webpack-pluginの読み込み
const ImageminPlugin = require("imagemin-webpack-plugin").default;
// imagemin-mozjpegの読み込み
const ImageminMozjpeg = require("imagemin-mozjpeg");

// 本番環境のときはsoucemapを出力させない設定
const enabledSourceMap = process.env.NODE_ENV !== "production";

const app = {
  // 読み込み先(srcの中のjsフォルダのinit.jsを読み込む)
  entry: path.resolve(__dirname, "src/js/init.js"),
  //出力先(distの中のjsフォルダへinit.jsを出力)
  output: {
    filename: "./js/init.js",
    path: path.resolve(__dirname, "dist")
  },

  //仮想サーバーの設定
  devServer: {
    //ルートディレクトリの指定
    contentBase: path.join(__dirname, "dist"),
    //ブラウザを自動的に起動
    open: true,
    // ファイルを監視するかどうか
    watchContentBase: true,
    //バンドルされたファイルを出力するかどうか。お好みで。
    //writeToDisk: true
  },
  //パッケージのライセンス情報をjsファイルの中に含める
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false
      })
    ]
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/env"]
          }
        }
      },
      {
        // 対象となるファイルの拡張子(scssかsassかcss)
        test: /\.(sa|sc|c)ss$/,
        // Sassファイルの読み込みとコンパイル
        use: [
          // CSSファイルを抽出するように MiniCssExtractPlugin のローダーを指定
          {
            loader: MiniCssExtractPlugin.loader
          },
          // CSSをバンドルするためのローダー
          {
            loader: "css-loader",
            options: {
              // CSS内のurl()メソッドの取り込みを禁止
              url: false,
              // production モードでなければソースマップを有効に
              sourceMap: enabledSourceMap,
              // postcss-loader と sass-loader の場合は2を指定
              importLoaders: 2
              // 0 => no loaders (default);
              // 1 => postcss-loader;
              // 2 => postcss-loader, sass-loader
            }
          },
          // PostCSS(autoprefixer)の設定
          {
            loader: "postcss-loader",
            options: {
              // production モードでなければソースマップを有効に
              sourceMap: enabledSourceMap,
              postcssOptions: {
                // ベンダープレフィックスを自動付与
                plugins: [require("autoprefixer")({ grid: true })]
              }
            }
          },
          // Sass を CSS へ変換するローダー
          {
            loader: "sass-loader",
            options: {
              // dart-sass を優先
              implementation: require("sass"),
              sassOptions: {
                // fibers を使わない場合は以下で false を指定
                fiber: require("fibers")
              },
              //  production モードでなければソースマップを有効に
              sourceMap: enabledSourceMap
            }
          }
        ]
      }
    ]
  },
  target: "web",
  
 //webpackの中に画像の圧縮処理など、重い処理を含めるとwarningが表示されます。それを回避する設定
  performance: {
    hints: false,
    maxEntrypointSize: 512000,
    maxAssetSize: 512000
  },

  //プラグインの設定
  plugins: [
    new CleanWebpackPlugin({ // dist内の不要なファイルやフォルダを消す
    }),
    new MiniCssExtractPlugin({ // distの中にあるcssフォルダにstyle.cssを出力
      filename: "./css/style.css"
    }),
    new CopyPlugin({ //圧縮した画像をsrcのimagesフォルダからコピーして、distのimagesフォルダに出力する
      patterns: [
        {
          from: `${path.resolve(__dirname, "src")}/images/`,
          to: `${path.resolve(__dirname, "dist")}/images/`
        }
      ]
    }),
    new ImageminPlugin({ //画像圧縮処理の指定
      test: /\.(jpe?g|png|gif|svg)$/i,
      plugins: [
        ImageminMozjpeg({
          quality: 89,
          progressive: true
        })
      ],
      pngquant: {
        quality: "80-89"
      },
      gifsicle: {
        interlaced: false,
        optimizationLevel: 10,
        colors: 256
      },
      svgo: {}
    })
  ],
  //source-map タイプのソースマップを出力
  devtool: "source-map",
  // node_modules を監視(watch)対象から除外
  watchOptions: {
    ignored: /node_modules/ //正規表現で指定
  }
};

// htmlファイルを見つけて配列化
const templates = globule.find("./src/templates/**/*.html");

//htmlファイルごとにループさせる
templates.forEach((template) => {
  const fileName = template.replace("./src/templates/", "");
  app.plugins.push(
    new HtmlWebpackPlugin({
      filename: `${fileName}`,
      template: template,
      inject: false, //false, head, body, trueから選べる
      minify: false //本番環境でも圧縮しない
    })
  );
});
module.exports = app;

Autoprefixerの指定を書く

package.jsonにAutoprefixerの指定を追記します。今回は以下のように設定しました。

  • > 3% in JP
  • ie 11
  • android 4.4
  • last 1 versions

package.jsonを開いて以下のように書きます。

"browserslist": [
    "> 3% in JP",
    "ie 11",
    "android 4.4",
    "last 1 versions"
  ],

webpack.config.jsの中身を説明する

webpack.config.jsに書いた設定について、簡単に説明します。

読み込み先と出力先を指定する

webpackはjsファイルを中心に処理されていきます。そのため読み込ませるjsファイルの指定と、読み込んだ後にjsファイルを出力する場所を指定する必要があります。

// 読み込み先(srcの中のjsフォルダのinit.jsを読み込む)
  entry: path.resolve(__dirname, "src/js/init.js"),
  //出力先(distの中のjsフォルダへinit.jsを出力)
  output: {
    filename: "./js/init.js",
    path: path.resolve(__dirname, "dist")
  }

仮想サーバーの立ち上げ

パッケージのwebpack-dev-serverを使うことで仮想サーバーを立ち上げることができます。必要な指定は以下が考えられます。

  • contentBase … どのフォルダのファイルを表示させるか
  • open … ブラウザを自動で立ち上げるかどうか
  • watchContentBase … ファイルが更新されたら、表示も変更させるか
  • writeToDisk … 開発環境でもフォルダやファイルを出力させるかどうか
//仮想サーバーの設定
  devServer: {
    //ルートディレクトリの指定
    contentBase: path.join(__dirname, "dist"),
    //ブラウザを自動的に起動
    open: true,
    // ファイルを監視するかどうか
    watchContentBase: true,
    //バンドルされたファイルを出力するかどうか。お好みで。
    //writeToDisk: true
  },

writeToDiskについて、指定がなければdistフォルダにはファイルもフォルダも出力されません。ただwebpack上でデータが保存されているため、表示はされます。

開発している間もフォルダやファイルを出力させたいのであれば、writeToDiskをtrueにしておきましょう。

ただし、webpack-dev-serverは3系と4系で書き方が変わります。いまは3系の書き方で書いてありますが、4系がインストールされているとエラーになります。

4系では以下のように変更する必要があります。

//仮想サーバーの設定
  devServer: {
    //ルートディレクトリの指定
    static: {
      directory: path.join(__dirname, "dist")
    },
    //ブラウザを自動的に起動
    open: true,
    // 監視するかフォルダはどれか
    watchFiles: ["src/**/*"],
    //バンドルされたファイルを出力するかどうか。お好みで。
    //writeToDisk: true
  },

書き方の違いについては以下の記事を参考にしてみてください。

JavaScriptの処理について

JavascriptはBabelによってトランスパイルさせています。

{
  test: /\.js$/,
  exclude: /node_modules/,
   use: {
       loader: "babel-loader",
       options: {
         presets: ["@babel/env"]
       }
   }
}

Sassの処理について

ローダーは下から順番に読み込まれて処理されていきます。下から順番にローダーを書くと以下です。

  • sass-loader … SassをCSSに変換する。ここでDart Sassを指定。
  • postcss-loader … Autoprefixerを適用させている。
  • css-loader … CSS内のurl()メソッドの取り扱いについて。
{
  // 対象となるファイルの拡張子(scss)
  test: /\.scss$/,
  // Sassファイルの読み込みとコンパイル
  use: [
  // CSSファイルを抽出するように MiniCssExtractPlugin のローダーを指定
  {
    loader: MiniCssExtractPlugin.loader
  },
  // CSSをバンドルするためのローダー
  {
    loader: "css-loader",
      options: {
        //CSS内のurl()メソッドの取り込みを禁止
        url: false,
        // production モードでなければソースマップを有効に
        sourceMap: enabledSourceMap,
        // postcss-loader と sass-loader の場合は2を指定
        importLoaders: 2
        // 0 => no loaders (default);
        // 1 => postcss-loader;
        // 2 => postcss-loader, sass-loader
      }
   },
   // PostCSS(autoprefixer)の設定
   {
     loader: "postcss-loader",
       options: {
         // production モードでなければソースマップを有効に
         sourceMap: enabledSourceMap,
         postcssOptions: {
         // ベンダープレフィックスを自動付与
           plugins: [require("autoprefixer")({ grid: true })]
         }
       }
    },
    // Sass を CSS へ変換するローダー
    {
     loader: "sass-loader",
       options: {
          // dart-sass を優先
          implementation: require("sass"),
          sassOptions: {
          // fibers を使わない場合は以下で false を指定
          fiber: require("fibers")
          },
          //  production モードでなければソースマップを有効に
          sourceMap: enabledSourceMap
       }
     }
  ]
}

45行目にあるfibersを指定することで、Dart Sassのコンパイル処理を早めるために指定しています。詳しくは以下を参照してください。

Dart Sassを使ったサイト設計については以下に書いたので参考にしてみてください。

パッケージのライセンス情報をjsファイルに含める

通常だとライセンス情報が、【ファイル名】.LICENSE.txtのようにテキストファイルで出力されます。ファイルを出力させず、jsファイルの中にライセンス情報を含めるために、terser-webpack-pluginを使用しています。

//terser-webpack-pluginの読み込み
const TerserPlugin = require("terser-webpack-plugin");

//パッケージのライセンス情報をjsファイルの中に含める
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false
      })
    ]
  },

こちらの記事を参考にさせてもらいました。

HTMLの処理について

srcの中にあるtemplatesフォルダのhtmlファイルがある分だけ、distの中に出力させています。

// htmlファイルを見つけて配列化
const templates = globule.find("./src/templates/**/*.html");

//htmlファイルごとにループさせる
templates.forEach((template) => {
  const fileName = template.replace("./src/templates/", "");
  app.plugins.push(
    new HtmlWebpackPlugin({
      filename: `${fileName}`,
      template: template,
      inject: false, //false, head, body, trueから選べる
      minify: false //本番環境でも圧縮しない
    })
  );
});

HtmlWebpackPluginのオプションについては以下です。

  • filename … ファイル名を指定
  • template … どのフォルダから読み込むのか指定
  • inject … trueにするとCSSファイルとJavascriptファイルの読み込みをHTMLファイルに書いてくれる。JavaScriptファイルはheadを指定すればhead内に、bodyを設定すればbodyが閉じる前に書き込まれる。
  • minify … HTMLファイルを圧縮するかどうか

その他のプラグインの指定について

4つのプラグインの指定を書いています。

  • CleanWebpackPlugin … srcフォルダの中が更新されるたびに、distの中にある不要なファイルやフォルダを削除する
  • MiniCssExtractPlugin … 抽出したCSSを、どのフォルダに何て名前で出力するか指定する
  • CopyPlugin … どのフォルダから、どのフォルダへコピーするか指定
  • ImageminPlugin … jpg、png、gif、svgの画像圧縮の指定
//プラグインの設定
  plugins: [
    new CleanWebpackPlugin({ // dist内の不要なファイルやフォルダを消す
    }),
    new MiniCssExtractPlugin({ // distの中にあるcssフォルダにstyle.cssを出力
      filename: "./css/style.css"
    }),
    new CopyPlugin({ //圧縮した画像をsrcのimagesフォルダからコピーして、distのimagesフォルダに出力する
      patterns: [
        {
          from: `${path.resolve(__dirname, "src")}/images/`,
          to: `${path.resolve(__dirname, "dist")}/images/`
        }
      ]
    }),
    new ImageminPlugin({ //画像圧縮処理の指定
      test: /\.(jpe?g|png|gif|svg)$/i,
      plugins: [
        ImageminMozjpeg({
          quality: 89,
          progressive: true
        })
      ],
      pngquant: {
        quality: "80-89"
      },
      gifsicle: {
        interlaced: false,
        optimizationLevel: 10,
        colors: 256
      },
      svgo: {}
    })
  ],

重いファイルを処理するときの警告を消す

重いファイルをコンパイルするとターミナル上で以下のような警告がでます。今回の設定では画像の圧縮処理が影響して警告がでます。

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). 

この警告が出ないように以下の指定を書いています。

performance: {
    hints: false,
    maxEntrypointSize: 512000,
    maxAssetSize: 512000
  },

これはstackoverflowの記事を参考にしました。

webpackを動かす

webpackを動かすためにコマンドを打ちます。コマンドを省略して打てるようにpackage.jsonに以下のように書きます。

"scripts": {
    "prod": "NODE_ENV=production webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch",
    "start": "webpack serve --mode development"
  }
  • prod … 本番環境として出力
  • dev … 開発環境として出力
  • watch … 開発環境モードでファイルを監視する
  • start … 仮想サーバーの立ち上げて、開発環境モードでファイルを監視する

コマンドを打つときは、最初にnpm runをつけます

開発しているときは以下のコマンドで始まり、

npm run start #仮想サーバーの立ち上げて、開発環境モードでファイルを監視する

開発が終われば以下のコマンドでファイルをまとめてdistフォルダに出力する、という流れになります。

npm run prod #本番環境として出力

ホットリロードについて

ファイルを更新したら、自動で表示しているサイトも更新させるホットリロード機能ですが、moduleの中のtargetの部分でes5を指定しているとうまく機能しません。

target:["web", "es5"]; //これだとホットリロードが機能しない

Babelの設定でpackage.jsonにbrowserslistの指定を書いているなら、targetにwebのみ書いておけばホットリロードは機能します。トランスパイルもbrowserslistの指定に沿って行わなれます。

target: "web"

CSSの出力について

webpackを通してSassファイルを読み込んで、CSSファイルとして出力させます。そのため、CSSファイルを出力させるためにはjsファイルにSassファイルをimportさせる必要があります。

// Sassファイルの読み込み
import "../scss/style.scss";

さいごに

今回はwebpack5で基本的な設定方法について説明しました。html側でpugやEJSを使う場合や、JavaScrip側でTypeScriptやReactを使う場合もwebpackは拡張して使うことができます。

webpackとgulpが比較されることが多いですが、どちらが良いというわけではなく、使いやすいほうを使えば良いです。ただ、gulp自体のシェア率が落ちているため、gulp用のプラグインの開発、メンテナンスが今後進まない可能性はあります。

ご自身が使っている言語やプロジェクトによってどちらが適しているのか使い分けてみてください。