Webpack５ 設定②

###この記事について
この記事は[Webpack５ 設定①](https://qiita.com/zgoovw_/items/39e8543a86da09e19229)の続きです。

#webpackを分割
webpackを共有、商用、開発用に分割します。

-  webpack.config.jsのファイル名をwebpack.common.jsに変更
- webpack.common.jsをコピーしてファイル名をwebpack.dev.jsに変更 
- 同じくもう一つwebpack.common.jsをコピーしてファイル名をwebpack.prod.jsに変更 
- それぞれ中身を以下のように変更

```js:webpack.common.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = ({ outputFile, assetFile }) => ({
  entry: {
    app: "./src/app.js",
    sub: "./src/sub.js",
  },
  output: {
    path: path.resolve(__dirname, "public"),
    filename: `${outputFile}.js`,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "babel-loader",
        exclude: /node_modules/,
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "eslint-loader",
        options: {
          fix: true,
        },
      },
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
      },
      {
        test: /\.(jpe?g|gif|png|svg|woff2?|ttf|ept)$/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: `${assetFile}.[ext]`,
              outputPath: "images",
              publicPath: "images",
            },
          },
        ],
      },
      {
        test: /\.html$/,
        use: ["html-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: `${outputFile}.css`,
    }),
  ],
});

```
```js:webpack.dev.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpackMerge = require("webpack-merge").merge;
const commonConf = require("./webpack.common");
const outputFile = "[name]";
const assetFile = "[name]";

module.exports = () =>
  webpackMerge(commonConf({ outputFile, assetFile }), {
    mode: "development",
    devtool: "source-map",
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        inject: "body",
      }),
    ],
  });
```
```js:webpack.prod.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpackMerge = require("webpack-merge").merge;
const commonConf = require("./webpack.common");
const outputFile = "[name].[chunkhash]";
const assetFile = "[name].[contenthash]";

module.exports = () =>
  webpackMerge(commonConf({ outputFile, assetFile }), {
    mode: "production",
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        inject: "body",
      }),
    ],
  });

```

bundleして作られたファイルを自動で消すために以下を追加

```
yarn add rimraf --dev
```

webpack-mergeも追加

```
yarn add webpack-merge --dev
```

package.jsonにscripts追加

```json:package.json
{
  "name": "webpack-lesson",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  // ここから追加
  "scripts": {
    "cleanup": "npx rimraf ./public", 
    "dev": "npm run cleanup && npx webpack --config ./webpack.dev.js",
    "build": "npm run cleanup && npx webpack --config ./webpack.prod.js"
  },
  // ここまで
  "devDependencies": {
   // ...省略
}
```
ローカルパッケージを実行するときはnpxでscriptsのコマンドはnpm run

#ファイルをミニファイ化する
jsのミニファイプラグイン(terser-webpack-plugin)とcssのミニファイプラグイン(optimize-css-assets-webpack-plugin, html-webpack-plugin)を追加

html-webpack-pluginはすでに入れてあるので他の残りをinstall

```
yarn add optimize-css-assets-webpack-plugin terser-webpack-plugin --dev
```

webpack.prod.jsに追加

```js:webpack.prod.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const OptimizeCssPlugin = require("optimize-css-assets-webpack-plugin"); // 追加
const TerserPlugin = require("terser-webpack-plugin"); // 追加
const webpackMerge = require("webpack-merge").merge;
const commonConf = require("./webpack.common");
const outputFile = "[name].[chunkhash]";
const assetFile = "[name].[contenthash]";

module.exports = () =>
  webpackMerge(commonConf({ outputFile, assetFile }), {
    mode: "production",
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        inject: "body",
        // ここから追加
        minify: {
          collapseWhitespace: true,
          keepClosingSlash: true,
          removeComments: true,
          removeRedundantAttributes: true,
          removeScriptTypeAttributes: true,
          removeStyleLinkTypeAttributes: true,
          useShortDoctype: true,
        },
        // ここまで
      }),
    ],
    // ここから追加
    optimization: {
      minimizer: [new TerserPlugin(), new OptimizeCssPlugin()],
    },
    // ここまで
  });
```
各プラグインについてのリンク
- [webpackのmode](https://webpack.js.org/configuration/mode/#mode-development)
- [Optimize CSS Assets Webpack Plugin](https://github.com/NMFR/optimize-css-assets-webpack-plugin)
- [webpackのminification](https://github.com/jantimon/html-webpack-plugin#minification)
- [terser-webpack-plugin](https://github.com/webpack-contrib/terser-webpack-plugin)

#開発サーバー設定

webpackのサーバー追加

```
yarn add webpack-dev-server --dev
```

webpack.dev.jsに設定
[webpadk dev-serverページ](https://webpack.js.org/configuration/dev-server/)

```js:webpack.dev.js
//　...省略
module.exports = () =>
  webpackMerge(commonConf({ outputFile, assetFile }), {
    mode: "development",
    devtool: "source-map",
    // ここから追加
    devServer: {
      open: true,
      contentBase: "./public",
      watchOptions: {
        ignored: /node_modules/,
      },
    },
    // ここまで
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        inject: "body",
      }),
    ],
  });
```

scriptsの追加と変更

```json:package.json
{
  "name": "webpack-lesson",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "cleanup": "npx rimraf ./public",
    "dev": "npm run webpack:dev && npm run webpack:server",
    "webpack:server": "npx webpack serve --config ./webpack.dev.js",
    "webpack:dev": "npm run cleanup && npx webpack --config ./webpack.dev.js",
    "build": "npm run cleanup && npx webpack --config ./webpack.prod.js"
  },
  "devDependencies": {
// ...省略
```

#共有プラグイン設定
jQueryを例に、使用場所ごとにimportしなくても使えるように設定する

``` js:webpack.common.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { ProvidePlugin } = require("webpack"); // 追加

// ...省略

 plugins: [
    new MiniCssExtractPlugin({
      filename: `${outputFile}.css`,
    }),
    // ここから追加
    new ProvidePlugin({
      jQuery: "jquery",
      $: "jquery",
    }),
    // ここまで
  ],
});
```

.eslintrcにも追加

```json:.eslintrc
{
    "env": {
        "browser": true,
        "es2017": true,
        "jquery": true // 追加
    },
// ...省略
```

#SplitChunksを設定
一つ前のセクションでjQueryの共有プラグイン設定をしましたが、現状ではapp.jsでsub.jsそれぞれでjQueryを使用してる場合、jQueryを2回読み込んでることになります。またそれぞれのjsファイルに変更があった場合にjQueryを含んだキャッシュが生成されているので変更があるたびにjQueryも再配信されることになります。
そうした無駄を無くすためにバンドル時にjQueryを別で生成して、他のjsファイルに変更があった場合にjQueryは再配信しなくても済むように処理をします。

[webpack optimization.splitChunksページ](https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks)

###jQueryのようなサードパーティーライブラリの登録

webpack.common.jsにSplitChunksを設定

```js:webpack.common.js
// ...省略
  output: {
    path: path.resolve(__dirname, "public"),
    filename: `${outputFile}.js`,
    chuncFilename: `${outputFile}.js`, // 追加
  },
// ...省略
plugins: [
    new MiniCssExtractPlugin({
      filename: `${outputFile}.css`,
    }),
    new ProvidePlugin({
      jQuery: "jquery",
      $: "jquery",
    }),
  ],
  //　ここから追加
  optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 0,
      cacheGroups: {
        vendor: {
          name: "vendors",
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
        },
        default: false,
      },
    },
    // ここまで
  },
});

```

###独自の共通部品の登録

次の例ではsrc直下にutilsフォルダ作成して、その中にindex.jsを作成し、
以下のようなfunctionをExportして、それぞれのjsファイルでimportせずに使いまわせるようにする

```js:index.js
export default {
  log: function (str) {
    console.log(str);
  },
};
```

webpack.common.jsに以下を追加

```js:webpack.common.js
// ...省略
  plugins: [
    new MiniCssExtractPlugin({
      filename: `${outputFile}.css`,
    }),
    new ProvidePlugin({
      jQuery: "jquery",
      $: "jquery",
      utils: [path.resolve(__dirname, "src/utils"), "default"], // 追加 指定したpathでexportしているdefault関数を指定している
    }),
  ],
  optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 0,
      cacheGroups: {
        vendors: {
          name: "vendors",
          test: /node_modules/,
          priority: -10,
        },
        // ここから追加
        utils: {
          name: "utils",
          test: /src[\\/]utils/,
        },
        // ここまで
        default: false,
      },
    },
  },
});

```

.eslintrcにutils追加

```json:.eslintrc
{
    "env": {
        "browser": true,
        "es2017": true
    },
    "extends": "eslint:recommended",
    "parser": "babel-eslint",
    "parserOptions": {
        "sourceType": "module" 
    },
    "globals": {
        "utils": "readonly", // 追加
        "jQuery": "readonly",
        "$": "readonly"
    },
    "rules": {
    }
}
```

#Resolveを使う
ツリー構造を整理してresolveを設定することでpathの最適化をします。

src直下にjsフォルダを作成して、その中にutilsを移動
app.jsとsub.jsもjsフォルダに移動

scssフォルダを作成して、その中にapp.scssを移動

ファイルの移動に合わせてpathを変更

```js:webpack.common.js
// ...省略
module.exports = ({ outputFile, assetFile }) => ({
  entry: {
    app: "./src/js/app.js", // 変更
    sub: "./src/js/sub.js", // 変更
  },
// ...省略
new ProvidePlugin({
      jQuery: "jquery",
      $: "jquery",
      utils: [path.resolve(__dirname, "src/js/utils"), "default"], // 変更
    }),
// ...省略
utils: {
          name: "utils",
          test: /src[\\/]js[\\/]utils/, // 変更
        },
// ...省略
```

resolve追加

```js:webpack.common.js
optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 0,
      cacheGroups: {
        vendors: {
          name: "vendors",
          test: /node_modules/,
          priority: -10,
        },
        utils: {
          name: "utils",
          test: /src[\\/]js[\\/]utils/,
        },
        default: false,
      },
    },
  },
  // ここから追加 resolveを設定することでpathや拡張子を省略して書くことができます。
  resolve: {
    alias: {
      "@scss": path.resolve(__dirname, "src/scss"), 
      "@imgs": path.resolve(__dirname, "src/images"),
    },
    extensions: [".js", ".scss"],
    modules: [path.resolve(__dirname, "src"), "node_modules"],
  },
  // ここまで
});
```

#VSCodeでResolveの自動補完設定をする

root直下にjsconfig.jsonファイルを作成し以下を記入

```json:jsconfig.json
{
  "compilerOptions": {
     "baseUrl": "./src",
     "paths": {
       "@scss": ["scss"],
       "scss": ["scss"],
       "js": ["js"],
       "images": ["images"]
     }
  }
}
```

VSCodeを再起動すると補完が効く

#非同期でファイルを読み込む

```js:webpack.common.js
optimization: {
    splitChunks: {
      chunks: "all", // chunksをallに設定するとdfefaultでは同期処理するようになり
      minSize: 0,    // cacheGroupsの個々のプロパティーにchunks: 'async'と設定するとで
      cacheGroups: { // 非同期で読み込むことができる。
        vendors: {
          name: "vendors",
          test: /node_modules/,
          priority: -10,
        },
        utils: {
          name: "utils",
          test: /src[\\/]/,
          chunks: 'async' //　この設定を追加すると非同期で読み込まれる。
        },                // たとえば重たい画像ファイルだけを非同期にしたりすることがある
```

非同期で呼び出すファイルは下記のようにダイナミックインポートで指定する

```js:app.js
import("@scss/app);
```

#複数のHTMLファイルでスクリプトタブで生成する

以下の例ではindex.htmlにはapp.js、other.htmlにはsub.jsをスクリプトタグで生成する
src直下にother.htmlファイルを作成

```html:other.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>this is other html</h1>
  </body>
</html>
```

html-webpack-pluginをインストール

```
yarn add --dev html-webpack-plugin
```

```js:webpack.dev.js
// ...省略
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        inject: "body",
        chunks: ["app"], // 追加
      }),
      // ここか追加
      new HtmlWebpackPlugin({
        template: "./src/other.html",
        filename: "other.html",
        inject: "body",
        chunks: ["sub"],
      }),
      // ここまで
    ],
  });
```

以上になります。
最後までお読みいただきありがとうございました。