webpack 4 の入門メモ

# 概要

webpack バージョン4の初歩を学んだ時のメモです。JavaScript、CSS、画像ファイルをバンドルするための環境や設定についてまとめました。

webpackとはなにかという基本の部分は割愛しています。

ソースコードは[rubytomato / exercise-webpack] (https://github.com/rubytomato/exercise-webpack)にあります。

**環境**

- Windows 10 Professional 1909
- Node.js 12.16.2
- npm 6.14.4
- webpack 4.43.0

**参考**

- [Concepts | webpack](https://v4.webpack.js.org/concepts/)
- [Plugins | webpack] (https://v4.webpack.js.org/plugins/)
- [To v4 from v3](https://webpack.js.org/migrate/4/)

# プロジェクトの作成

プロジェクト名は`exercise-webpack`としました。この名前でディレクトリを作成し、そこで下記のコマンドを実行します。

```console
> npm init -y
```

(※実際には下記のようなpackage.jsonファイルがあればよく、initコマンドの実行は任意です。）

**package.json**

initコマンドで出力されたpackage.jsonファイルを下記のように編集しました。

```json
{
  "name": "exercise-webpack",
  "version": "1.0.0",
  "description": "My webpack project",
  "private": true,
  "main": "index.js",
  "scripts": {
  },
  "keywords": ["webpack"],
  "author": "rubytomato",
  "license": "MIT"
}
```

## webpack / webpack-cli

webpackとwebpack-cliの両方が必要なのでインストールします。

```console
> npm install webpack webpack-cli --save-dev
```

**version**

```console
> npx webpack --version
4.43.0
> npx webpack-cli --version
3.3.11
```

## webpack-cli/init (optional)

* 参考: [webpack-cli/init] (https://github.com/webpack/webpack-cli/tree/next/packages/init)

このパッケージは必須ではありませんが、このパッケージを使うとwebpack.config.jsの作成を始めとしてプロジェクトのひな型を作成することができます。
webpack-cliがバージョン3系の場合、webpack-cli/initのインストールにはバージョンを指定する必要があります。
（※プロジェクト作成時にしか使用しないのでローカルにインストールする必要性はあまりありません）

```console
> npm install @webpack-cli/init@0.2.2 --save-dev
```

### initの実行

`webpack-cli init`でプロジェクトのひな型を作成します。下記の通り対話形式でプロジェクトの設定を行います。
（※#から始まる日本語の行はGoogle翻訳を追記したもので、実際のコマンドには出力されていません。）
最後の方でpackage.jsonがコンフリクトしているというメッセージが表示されますが、yを入力して上書きします。

```console
> npx webpack-cli init

i INFO  For more information and a detailed description of each question, have a look at: https://github.com/webpack/webpack-cli/blob/master/INIT.md
i INFO  Alternatively, run "webpack(-cli) --help" for usage info

? Will your application have multiple bundles? (y/N) Run-async wrapped function (sync) returned a promise but async() callback must be executed to resolve.
# アプリケーションに複数のバンドルがありますか?
? Will your application have multiple bundles? No
# アプリケーションのエントリポイントはどれですか?
? Which will be your application entry point? src/js/app
# 生成したバンドルをどのフォルダーに保存しますか?
? In which folder do you want to store your generated bundles? 'dist'
# 以下のJSソリューションの1つを使用しますか?
? Will you use one of the below JS solutions? ES6
# 以下のCSSソリューションのいずれかを使用しますか?
? Will you use one of the below CSS solutions? CSS
# CSSファイルをバンドルする場合、バンドルに何という名前を付けますか?
? If you want to bundle your CSS files, what will you name the bundle? (press enter to skip) style
 conflict package.json
? Overwrite package.json? overwrite
    force package.json
   create .babelrc
   create src\js\app.js
   create README.md

// 省略

+ @babel/core@7.9.0
+ css-loader@3.5.2
+ babel-plugin-syntax-dynamic-import@6.18.0
+ webpack-cli@3.3.11
+ style-loader@1.1.4
+ terser-webpack-plugin@2.3.5
+ babel-loader@8.1.0
+ @babel/preset-env@7.9.5
+ webpack@4.42.1
+ mini-css-extract-plugin@0.9.0
+ workbox-webpack-plugin@5.1.2
added 123 packages from 106 contributors, removed 1 package and updated 9 packages in 49.11s

37 packages are looking for funding
  run `npm fund` for details


Congratulations! Your new webpack configuration file has been created!

You can now run npm run build to bundle your application!
```

### initの実行後

下記の内容でプロジェクトのひな型が出来ています。この中で必要のないものや変更を加えるものがあります。

```text
exercise-webpack
  |
  `--- /node_modules
  |
  `--- /src
  |      |
  |      `--- /js
  |             |
  |             `--- app.js
  |
  `--- .bablerc
  `--- .yo-rc.json
  `--- package-lock.json
  `--- package.json
  `--- README.md
  `--- webpack.config.js
```

**Yeoman**

Yeomanの設定ファイル(.yo-rc.json)が作成されていますが、Yeomanは使用しないので削除します。

**Workbox**

`workbox-webpack-plugin`がインストールされていますが、使用しないのでアンインストールします。

```console
> npm uninstall workbox-webpack-plugin --save-dev
```

`workbox-webpack-plugin`に関係するコードを削除します。

```javascript
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
// ↓ 削除
const workboxPlugin = require('workbox-webpack-plugin');
// ↑ 削除

module.exports = {
	mode: 'development',
	entry: './src/js/app.js',
	plugins: [
		new webpack.ProgressPlugin(),
		new MiniCssExtractPlugin({ filename: 'style.[chunkhash].css' }),
		// ↓ 削除
		new workboxPlugin.GenerateSW({
			swDest: 'sw.js',
			clientsClaim: true,
			skipWaiting: false
		})
		// ↑ 削除
	],

	// 省略
};
```

**babel**

babelもインストールされています。

* 参考: [What is Babel?] (https://babeljs.io/docs/en/index.html)

下記の内容で.babelrcファイルが作成されています。pluginsに書かれている"syntax-dynamic-import"は、バージョン6系のものなのでバージョン7系のものへ変更します。

```javascript
{
    "plugins": [
        "syntax-dynamic-import"
    ],
    "presets": [
        [
            "@babel/preset-env",
            {
                "modules": false
            }
        ]
    ]
}
```

バージョン6系の`babel-plugin-syntax-dynamic-import`をアンインストールし、代わりにバージョン7計の`@babel/plugin-syntax-dynamic-import`をインストールします。

* アンインストール
    * [babel-plugin-syntax-dynamic-import] (https://babeljs.io/docs/en/6.26.3/babel-plugin-syntax-dynamic-import)
* インストール
    * [@babel/plugin-syntax-dynamic-import] (https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import)

```console
> npm uninstall babel-plugin-syntax-dynamic-import --save-dev

> npm install @babel/plugin-syntax-dynamic-import --save-dev
```

ついでに.babelrcをbabel.config.jsへ書き換えます。

```javascript
module.exports = function (api) {
  api.cache(true);
  const presets = [['@babel/preset-env', { 'targets': { 'esmodules': true } }]];
  const plugins = ["@babel/plugin-syntax-dynamic-import"];
  return {
    presets,
    plugins
  };
}
```

`@babel/preset-env`のオプションに`{ 'targets': { 'esmodules': true } }`を設定しているのは、後ほどaxiosを利用するためです。この設定がないと”ReferenceError regeneratorRuntime is not defined”というエラーが発生します。

* 参考: [ReferenceError regeneratorRuntime is not defined #9849] (https://github.com/babel/babel/issues/9849)

**webpack**

webpack.config.jsに`output`と`devtool`を追加します。
`output`はバンドルファイルの出力設定、`devtool`はソースマップの出力設定です。

```javascript
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
	mode: 'development',
	entry: './src/js/app.js',
	// ↓ 追加
	devtool: 'source-map',
	// ↑ 追加
	plugins: [
		new webpack.ProgressPlugin(),
		new MiniCssExtractPlugin({ filename: 'style.[chunkhash].css' })
	],
	// ↓ 追加
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'js/[name].bundle.js'
	},
	// ↑ 追加
	module: {
		rules: [
			{
				test: /.(js|jsx)$/,
				include: [path.resolve(__dirname, 'src/js')],
				loader: 'babel-loader'
			},
			{
				test: /.css$/,
				use: [
					{
						loader: MiniCssExtractPlugin.loader
					},
					{
						loader: 'style-loader'
					},
					{
						loader: 'css-loader',
						options: {
							sourceMap: true
						}
					}
				]
			}
		]
	},
	optimization: {
		minimizer: [new TerserPlugin()],
		splitChunks: {
			cacheGroups: {
				vendors: {
					priority: -10,
					test: /[\\/]node_modules[\\/]/
				}
			},
			chunks: 'async',
			minChunks: 1,
			minSize: 30000,
			name: true
		}
	}
};
```

`devtool`を設定すると自動的に`SourceMapDevToolPlugin`が有効になります。このプラグインのオプションでより細かくソースマップの出力設定を行うことができます。

* 参考: [SourceMapDevToolPlugin] (https://webpack.js.org/plugins/source-map-dev-tool-plugin/)

```
devtool: false,
plugins: [
  new webpack.SourceMapDevToolPlugin({})
]
```

## webpack-dev-server

* 参考: [DevServer | webpack] (https://webpack.js.org/configuration/dev-server/)

`webpack-dev-server`をインストールします。`webpack-dev-server`は開発用途のサーバーで、バンドル対象ファイルを編集すると自動的に反映されるライブリロードを提供します。

```console
> npm install webpack-dev-server --save-dev
```

**version**

```console
> npx webpack-dev-server --version
webpack-dev-server 3.10.3
webpack 4.42.1
```

**webpack.config.jsの編集**

`webpack-dev-server`の設定を追記します。

```javascript
module.exports = {

  // ↓ 追加
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    contentBasePublicPath: '/',
    index: 'index.html',
    // サーバー起動時にブラウザを開く
    open: true,
    // エラーや警告をブラウザに表示する
    overlay: true
  }
  // ↑ 追加
}
```

## axios

* 参考: [axios / axios] (https://github.com/axios/axios)

非同期通信が行えるHTTP Clientの`axios`をインストールします。

```console
> npm install axios --save
```


# webpackの動作確認

これまでの設定でwebpackの動作確認を行います。
webpackでバンドルするファイルはすべてsrcディレクトリ下に配置します。また下記のプロジェクトの構造にはまだ書かれていませんが、バンドルしたファイルの出力先はexercise-webpack/distになります。

## プロジェクトの構造

```text
exercise-webpack
  |
  `--- /node_modules
  |
  `--- /src
  |      |
  |      `--- /js
  |             |
  |             `--- /modules
  |             |      |
  |             |      `--- util.js       ← 追加
  |             |
  |             `--- app.js
  |             `--- index.html           ← 追加
  |
  `--- bable.config.js
  `--- package-lock.json
  `--- package.json
  `--- README.md
  `--- webpack.config.js
```

### バンドルするファイルの作成

**index.html**

下記の内容でsrc/index.htmlを作成します。

```html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>webpack test</title>
  </head>
  <body class="profile">
    <div id="header">
      <!--logo-->
      <div class="box left"></div>
      <!--title-->
      <div class="box center"></div>
      <!--Resize-->
      <div class="box right"></div>
    </div>
    <div id="contents">
      <div class="caption">
        <div class="border">
          <span>webpack test</span>
        </div>
        <div class="border user"></div>
      </div>
    </div>
    <div id="footer">
      <p>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent cursus nec sem id malesuada. Curabitur lacinia sem et turpis euismod, eget elementum ex pretium.
      </p>
    </div>
    <script src="./js/main.bundle.js"></script>
  </body>
</html>
```

**app.js**

バンドルのエントリーポイントとなるsrc/js/app.jsを下記の内容で書き換えます。

```javascript
import Util from './modules/Util'
import Ro from './modules/Ro'
import Hc from './modules/Hc'

const init = async () => {
  document.getElementsByClassName('left')[0].innerHTML = Util.createLogo()
  document.getElementsByClassName('center')[0].innerHTML = Util.createElement('h2', 'Hi there and greetings!')
  // ResizeObserver
  const container = document.getElementsByClassName('right')[0]
  const profile = document.getElementsByClassName('profile')[0]
  Ro.observer(container, profile)
  // axios
  document.getElementsByClassName('user')[0].innerHTML = await Hc.getUser(1)
}

window.addEventListener('DOMContentLoaded', function () {
  init()
}, false)
```

**util.js**

また、app.jsより参照されるsrc/js/modules/util.jsを下記の内容で作成します。

```javascript
const createLogo = () => {
  const template = `
    <a href="/">
      <p class="logo"></p>
    </a>
  `
  return template
}

const createElement = (element, textNode) => {
  const template = `
    <${element}>${textNode}</${element}>
  `
  return template
}

const createImg = (src) => {
  const image = new Image()
  image.src = src
  return image
}

export default { createLogo, createElement, createImg }
```

**Hc.js**

```javascript
import axios from 'axios'

const getUser = async (id) => {
  const res = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`)
  const user = res.data

  const template = `
    <span>
      ${user.name}
      <br/>
      ${user.email}
      <br/>
      ${user.phone}
    </span>
  `
  return template
}

export default { getUser }
```

**Ro.js**

```javascript
const observer = (container, profile) => {

  const callback = (entries) => {
    entries.forEach(entry => {
      const { width, height } = entry.contentRect
      container.innerHTML = `
        <p>size is now width:<span class="emphasis">${width}</span> height:<span class="emphasis">${height}</span></p>
      `
    })
  }

  const resizeObserver = new ResizeObserver(callback)
  resizeObserver.observe(profile)
}

export default { observer }
```

### copy-webpack-plugin

* 参考: [CopyWebpackPlugin] (https://webpack.js.org/plugins/copy-webpack-plugin/)

`copy-webpack-plugin`は、指定するファイルやディレクトリをビルドディレクトリへコピーするプラグインです。
この例ではsrcディレクトリにあるindex.htmlファイルをdistディレクトリへコピーするために使います。

```console
> npm install copy-webpack-plugin --save-dev
```

**webpack.config.jsの編集**

`copy-webpack-plugin`を設定ファイルに組み込みます。

```javascript
// ↓ 追加
const CopyWebpackPlugin = require('copy-webpack-plugin'); // installed via npm

module.exports = {

  plugins: [
    // ↓ 追加
    new CopyWebpackPlugin([{ from: 'src/index.html' }], { logLevel: 'debug' })
  ],

}
```

### ~~clean-webpack-plugin~~

※ビルドが度々失敗するので利用するのを止めました。特にwebpackを`--watch`オプションを付けてファイル変更の監視を行っていると、ファイルのコピーが失敗することがありました。

* ~~参考: [CleanWebpackPlugin] (https://github.com/johnagan/clean-webpack-plugin)~~

~~`clean-webpack-plugin`は、ビルドが成功するたびにビルドディレクトリ内のファイルやディレクトリを削除するプラグインです。~~

```console
> npm install clean-webpack-plugin --save-dev
```

~~**webpack.config.jsの編集**~~

~~`clean-webpack-plugin`を設定ファイルに組み込みます。~~

```javascript
// ↓ 追加
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // installed via npm

module.exports = {

  plugins: [
    // ↓ 追加
    new CleanWebpackPlugin({ verbose: true })
  ],

}
```

### バンドルするスクリプトを追加

下記のスクリプトを追加します。buildはバンドルするスクリプト、startは開発サーバーを起動するスクリプトです。

**package.jsonを編集**

```json
"scripts": {
  "build": "webpack",
  "dev": "webpack --watch",
  "start": "webpack-dev-server"
}
```

#### webpackに指定できるオプション

**--help**

ヘルプテキストを出力します。その他に指定できるオプションを調べることができます。

```
webpack --help
```

**--mode**

バンドルのモードを指定します。指定できる値は`production`、`development`、`none`です。

```
webpack --mode production
```

**--config**

任意のコンフィグレーションファイルを指定します。

```
webpack --config webpack.config.js
```

**--watch**

ファイルを監視して変更があったら再ビルドします。

```
webpack --watch
```

#### webpack-dev-serverに指定できるオプション

**--help**

ヘルプテキストを出力します。その他に指定できるオプションを調べることができます。

```
webpack-dev-server --help
```

**--mode**

バンドルのモードを指定します。指定できる値は`production`、`development`、`none`です。

```
webpack-dev-server --mode production
```

**--config**

任意のコンフィグレーションファイルを指定します。

```
webpack-dev-server --config webpack.config.js
```

**--open**

デフォルトのブラウザを起動します。

```
webpack-dev-server --open
```

ブラウザを指定することもできます。

```
webpack-dev-server --open firefox
```

**--watch**

ファイルを監視して変更があったら再ビルドします。

```
webpack-dev-server --watch
```

## バンドルする

上記のプロジェクトで実際にバンドルしてみます。
この時点でのwebpack.config.jsの内容は下記の通りです。

```javascript
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); // installed via npm

module.exports = {
  mode: 'development',
  entry: './src/js/app.js',
  devtool: 'source-map',
  plugins: [
    new webpack.ProgressPlugin(),
    new MiniCssExtractPlugin({ filename: 'style.[chunkhash].css' }),
    new CopyWebpackPlugin([{ from: 'src/index.html' }], { logLevel: 'debug' })
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        include: [path.resolve(__dirname, 'src/js')],
        loader: 'babel-loader'
      },
      {
        test: /.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          {
            loader: 'style-loader'
          },
          {
            loader: 'css-loader',
            options: {
              sourceMap: true
            }
          }
        ]
      }
    ]
  },
  optimization: {
    minimizer: [new TerserPlugin()],
    splitChunks: {
      cacheGroups: {
        vendors: {
          priority: -10,
          test: /[\\/]node_modules[\\/]/
        }
      },
      chunks: 'async',
      minChunks: 1,
      minSize: 30000,
      name: true
    }
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    contentBasePublicPath: '/',
    index: 'index.html',
    // サーバー起動時にブラウザを開く
    open: true,
    // エラーや警告をブラウザに表示する
    overlay: true
  }
};
```

### バンドルの実行

buildでバンドルします。

```console
> npm run build
```

バンドルが成功するとdistディレクトリへバンドルされたファイルが出力されます。VSCodeにLive Server拡張機能がインストールされていれば、index.htmlファイルを開いて動作確認ができます。

```text
exercise-webpack
 |
 `--- /dist
        |
        `--- /js
        |      |
        |      `--- main.buldle.js
        |      `--- main.bundle.js.map
        |
        `--- index.html
```

### devサーバで動作確認する

devサーバーで動作確認するには`npm start`コマンドを実行します。webpack.config.jsの設定により実行後にブラウザが起動しindex.htmlが表示されます。
なお、バンドルファイルはメモリ上に展開されるのでdistディレクトリへは出力されません。

```console
> npm start
```


## ESLint

* 参考: [ESLint] (https://eslint.org/)
* 参考: [Configuring ESLint] (https://eslint.org/docs/user-guide/configuring)

ESLint、babel-eslintとeslint-loaderを使用すると、バンドル時にJavaScriptのソースコードを静的解析することができます。
babelはすでにインストールしているので、ESLintと関連のパッケージをインストールします。

### ESLintのインストール

```console
> npm install eslint --save-dev
```

**version**

```console
> npx eslint --version
v6.8.0
```

**.eslintrc.jsの生成**

`--init`オプションを指定して実行すると.eslintrc.jsを対話形式で生成できます。
（※#から始まる日本語の行はGoogle翻訳を追記したもので、実際のコマンドには出力されていません。）

```console
> npx eslint --init
# ESLintをどのように使用しますか?
? How would you like to use ESLint? To check syntax and find problems
# プロジェクトはどのタイプのモジュールを使用しますか?
? What type of modules does your project use? JavaScript modules (import/export)
# プロジェクトはどのフレームワークを使用していますか?
? Which framework does your project use? None of these
# プロジェクトでTypeScriptを使用していますか?
? Does your project use TypeScript? No
# コードはどこで実行されますか?
? Where does your code run? Browser, Node
# 構成ファイルをどの形式にしたいですか?
? What format do you want your config file to be in? JavaScript
Successfully created .eslintrc.js file in D:\dev\vsc-workspace\exercise-webpack
```

下記が生成された.eslintrc.jsです。

```javascript
module.exports = {
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "extends": "eslint:recommended",
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly"
  },
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "rules": {
  }
};
```

**.eslintignoreの作成**

リントの対象外とするファイルやディレクトリを指定するため、以下の内容で.eslintignoreファイルを作成します。

```
!*.js
!*.ts
coverage/
dist/
node_modules/
public/
```

### babel-eslint

* 参考: [babel-eslint] (https://github.com/babel/babel-eslint)

ESLintの標準パーサーと互換のある`babel-eslint`パーサーをインストールします。
`babel-eslint`をパーサーにすると、ESLintでは解釈できない新しいJavaScriptの構文をbabelでトランスパイルした結果で構文解析できるようになります。

```console
> npm install babel-eslint --save-dev
```

**.eslintrc.jsの修正**

下記の行を追加します。

```javascript
module.exports = {
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "extends": "eslint:recommended",
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly"
  },
  // ↓ 追加
  "parser": "babel-eslint",
  // ↑ 追加
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "rules": {
    // ↓ 追加
    "strict": 0
    // ↑ 追加
  }
};
```

* 0 or "off" - ルールをオフにします
* 1 or "warn" - 警告としてルールをオンにします（終了コードには影響しません）
* 2 or "error" - エラーとしてルールをオンにします（トリガーされると、終了コードは1です）

### eslint-loader

* 参考: [eslint-loader] (https://webpack.js.org/loaders/eslint-loader/)

```console
> npm install eslint-loader --save-dev
```

**webpack.config.jsの編集**

rulesにeslint-loaderの設定を加えます。ローダーは下から上に処理されるためbabel-loaderの下に追記します。そうすることによって先にeslint-loaderで静的解析が行われ、次にbabel-loaderでトランスパイルされます。

```javascript
module.exports = {

	module: {
		rules: [
			{
				test: /.(js|jsx)$/,
				include: [path.resolve(__dirname, 'src/js')],
				loader: 'babel-loader'
			},
			// ↓ 追加
			{
				test: /.js$/,
				include: [path.resolve(__dirname, 'src/js')],
				loader: 'eslint-loader'
			},
			// ↑ 追加
		]
	},

}
```

### ルールの追加

* 参考: [List of available rules - ESLint] (https://eslint.org/docs/rules/)

**.eslintrc.jsの修正**

ソースコードの静的解析に適用するルールをいくつか設定します。（これらのルールを選んだことに特にこだわりはありません。とりあえずwebpackの動作確認のために設定しました。）
これらのルールにエラーとなるコードがあるとバンドルに失敗します。

```javascript
module.exports = {

  "rules": {
    "strict": 0,
    // ↓ 追加
    "indent": ["error", 2],                                  // インデント 半角スペース2
    "quotes": ["error", "single", { "avoidEscape": true }],  // ダブルクォート禁止
    "semi": ["error", "never"],                              // セミコロン禁止
    "comma-dangle": ["error", "never"]                       // 行末カンマ禁止
    // ↑ 追加
  }
};
```

（※このルールを追加すると、VSCodeにESLintの拡張機能をインストールしている場合、リントエラーが表示されるファイルが出てくることがあります。）


# CSSファイルのバンドル

CSSファイルもローダーを追加することでバンドルすることができます。この記事では`webpack-cli/init`でプロジェクトの初期設定を行いましたが、そのときに`MiniCssExtractPlugin.loader`、`sytle-loader`、`css-loader`がすでに組み込まれ、webpack.config.jsにローダーの設定が記述されています。

## プロジェクトの構造

src/stylesディレクトリにCSSファイルを作成します。

```text
exercise-webpack
 |
 `--- /src
        |
        `--- /styles
               |
               `--- reset.css
               `--- style.css
```

### バンドルするファイルの作成

**src/sytles/reset.css**

```css
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body,
html {
  background-color: whitesmoke;
  height: 100%;
}
```

**src/sytles/style.css**

動作確認用のCSSファイルを作成します。

```css
@import "./reset.css";

.caption {
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  text-align: center;
  color: black;
}

div.border {
  margin: 5px 20px;
}

div.border span {
  display: inline-block;
  padding: 14px;
  font-size: large;
  letter-spacing: 16px;
  background-color: darkgreen;
  color: white;
  opacity: 0.7;
}

span.emphasis {
  font-weight: bold;
}

#header {
  background-color: azure;
  line-height: 1.5;
  color: cadetblue;
  display: flex;
  justify-content: center;
  align-items: center;
}

#header .left {
  width: 15%;
}

#header .center {
  width: 55%;
}

#header .right {
  width: 30%;
}

#footer {
  padding: 3px;
}

#footer img {
  max-width: 100%;
  height: auto;
  width: auto;
}
```

**src/js/app.jsの編集**

CSSファイルがバンドルの対象となるようにするには、すでにエントリーポイントになっているapp.jsに依存関係を加えます。
この場合は以下のように単にimport文を追加するだけです。

```javascript
import '../styles/style.css'
```

**webpack.config.jsの編集**

バンドルファイルの出力先とファイル名を`MiniCssExtractPlugin`プラグインのオプションで指定します。
出力先はstylesディレクトリ、ファイル名はstyle.bundle.cssとしました。

```javascript
// new MiniCssExtractPlugin({ filename: 'style.[chunkhash].css' }),
new MiniCssExtractPlugin({ filename: 'styles/style.bundle.css' }),
```

**src/index.htmlの編集**

バンドルしたCSSファイルをリンクします。

```html
<link rel="stylesheet" href="./styles/style.bundle.css" />
```

### style-loader

* 参考: [ReferenceError: window is not defined #288] (https://github.com/webpack-contrib/mini-css-extract-plugin/issues/288)

`MiniCssExtractPlugin`と`style-loader`を併用するとバンドル時にエラーが起きるので、`style-loader`をアンインストールします。

```console
> npm uninstall style-loader --save-dev
```

**webpack.config.jsの編集**

CSSファイルをバンドルするルールから`style-loader`の設定を削除します。

```javascript
module.exports = {

  module: {

    rules: [
      {
        test: /.css$/,
        include: [path.resolve(__dirname, 'src/styles')],
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          // ↓ 削除
          //{
          //  loader: 'style-loader'
          //},
          {
            loader: 'css-loader',
            options: {
              sourceMap: true
            }
          }
        ]
      }
    ]
  },

}
```

### バンドルする

buildでバンドルします。

```console
> npm run build
```

バンドルされたCSSファイルがdist/styles/style.bundle.cssへ出力されています。動作確認の方法はJavaScriptの時と同様です。

```text
exercise-webpack
 |
 `--- /dist
        |
        `--- /js
        |      |
        |      `--- main.bundle.js
        |      `--- main.bundle.js.map
        |
        `--- /styles
               |
               `--- style.bundle.css
               `--- sytle.bundle.css.map
```

## URL関数を使う

CSSのURL関数で画像ファイルを扱う場合、その画像ファイルもバンドルするための設定定が必要です。

**プロジェクトの構造**

src/imagesディレクトリに画像ファイルを配置します。

```text
exercise-webpack
 |
 `--- /src
        |
        `--- /images
        |      |
        |      `--- background-image.png (5MB)
        |      `--- logo.png (621B)
        |
        `--- /styles
               |
               `--- reset.css
               `--- style.css
```

**src/sytles/style.css**

style.cssに以下のコードを追加します。

```css
.logo {
  background-image: url(../images/logo.png);
  width: 110px;
  height: 48px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

#contents {
  background-image: url(../images/background-image.png);
  height: 85%;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}
```

### url-loader

画像ファイルをバンドルするには`url-loader`が必要です。このローダーは対象とするファイルをBase64でエンコードします。

* 参考: [url-loader] (https://webpack.js.org/loaders/url-loader/)

```console
> npm install url-loader --save-dev
```

**webpack.config.jsの編集**

`url-loader`を追加します。

```javascript
module.exports = {

  module: {
    rules: [

      // ↓ 追加
      {
        test: /.(gif|png|jpg)$/,
        include: [path.resolve(__dirname, 'src/images')],
        use: [
          {
            loader: 'url-loader'
          }
        ]
      }
      // ↑ 追加
    ]
  },

}
```

### バンドルする

```console
> npm run build
```

以下がバンドルして生成されたstyle.bundle.cssです。URLの部分が`data:image/png;base64,...`となっていて、画像ファイルがBase64でエンコードされていることがわかります。

```css
.logo {
  background-image: url(data:image/png;base64,iVBORw0KG ... TkSuQmCC);
  width: 110px;
  height: 48px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

#contents {
  background-image: url(data:image/png;base64,iVBORw0K ... TkSuQmCC);
  height: 85%;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}
```

## 一定以上サイズの画像ファイルは個別に出力する

画像ファイルをBase64エンコードして`data:image`とするのではなく、個別のファイルとしてビルドディレクトリへ出力するには`url-loader`のオプションに以下の設定を加えます。
optionsのlimitに指定した値は画像ファイルをそのまま出力するファイルサイズの閾値です。この例では30720 (30KB)としているので、サイズが30KB以上の画像ファイルはBase64エンコードされずに画像ファイルとして出力されます。

```javascript
module.exports = {

  module: {
    rules: [
      {
        test: /.css$/,
        include: [path.resolve(__dirname, 'src/styles')],
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            // ↓ 追加
            options: {
              publicPath: '../'
            }
            // ↑ 追加
          },
          {
            loader: 'css-loader',
            options: {
              sourceMap: true
            }
          }
        ]
      },
      {
        test: /.(gif|png|jpg)$/,
        include: [path.resolve(__dirname, 'src/images')],
        use: [
          {
            loader: 'url-loader',
            // ↓ 追加
            options: {
              limit: 30720,
              name: '[name].[ext]',
              outputPath: './images/'
            }
            // ↑ 追加
          }
        ]
      }
    ]
  },

}
```

### file-loader

上記の設定を加えると`file-loader`が必要になるのでインストールします。
`file-loader`自体はJavaScriptのコードで`import/require()`された画像ファイルをURLに変換し、その画像ファイルをビルドディレクトリへコピーする機能を持っています。

* 参考: [file-loader] (https://webpack.js.org/loaders/file-loader/)

```console
> npm install file-loader --save-dev
```

### バンドルする

```console
> npm run build
```

以下がバンドルして生成されたstyle.bundle.cssです。サイズが30KB以上のファイルはBase64エンコードされていないことがわかります。

```css
.logo {
  background-image: url(data:image/png;base64,iVBORw0KG ... TkSuQmCC);
  width: 110px;
  height: 48px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

#contents {
  background-image: url(../images/background-image.png);
  height: 85%;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}
```

以下がバンドル後のプロジェクトディレクトリの状態です。30KB以上のファイルはdist/imagesディレクトリへ出力されています。

```text
exercise-webpack
 |
 `--- /dist
        |
        `--- /images
        |      |
        |      `--- background-image.png
        |
        `--- /styles
               |
               `--- style.bundle.css
               `--- style.bundle.css.map
```

# 画像ファイルのバンドル

`file-loader`をインストールすると画像ファイルもバンドルすることが可能です。
CSSファイルのバンドルではURL関数で指定した画像ファイルをバンドルしましたが、JavaScriptのコードでは`import/require()`された画像ファイルをバンドルします。

## プロジェクトの構造

src/imagesディレクトリに画像ファイルを追加します。

```text
exercise-webpack
 |
 `--- /src
        |
        `--- /images
        |      |
        |      `--- background-image.png
        |      `--- footer-image.png            ←追加
        |      `--- logo.png
        |
        `--- /styles
               |
               `--- reset.css
               `--- style.css
```

### バンドルするファイル

`src/js/app.js`を下記のように修正します。

```javascript
import Util from './modules/Util'
import Ro from './modules/Ro'
import Hc from './modules/Hc'
import '../styles/style.css'

// ↓ 追加
import footerImage from '../images/footer-image.png'

const init = async () => {
  document.getElementsByClassName('left')[0].innerHTML = Util.createLogo()
  document.getElementsByClassName('center')[0].innerHTML = Util.createElement('h2', 'Hi there and greetings!')
  // ↓ 追加
  document.getElementById('footer').appendChild(Util.createImg(footerImage))
  // ResizeObserver
  const container = document.getElementsByClassName('right')[0]
  const profile = document.getElementsByClassName('profile')[0]
  Ro.observer(container, profile)
  // axios
  document.getElementsByClassName('user')[0].innerHTML = await Hc.getUser(1)
}

window.addEventListener('DOMContentLoaded', function () {
  init()
}, false)
```

### url-loader / file-loader

`url-loader`と`file-loader`が必要です。インストールと設定はCSSファイルのバンドルで説明した通りです。

### バンドルする

```console
> npm run build
```

以下がバンドル後のプロジェクトディレクトリの状態です。

```text
exercise-webpack
 |
 `--- /dist
        |
        `--- /images
        |      |
        |      `--- background-image.png
        |      `--- footer-image.png
        |
        `--- /styles
               |
               `--- style.bundle.css
               `--- style.bundle.css.map
```

## 画像ファイルをバンドルせずビルドディレクトリへコピーする

バンドルするのではなく単に画像ファイルをビルドディレクトリへコピーしたいという場合は`CopyWebpackPlugin`プラグインを利用します。

**webpack.config.jsの編集**

```javascript
// ↓ 追加
const copyFiles = [
  { from: 'src/index.html' },
  { from: 'static/images/*', to: './images/[name].[ext]' }
]
```

```javascript
// ↓ 修正
// new CopyWebpackPlugin([{ from: 'src/index.html' }], { logLevel: 'debug' }),
new CopyWebpackPlugin(copyFiles, { logLevel: 'debug' }),
```

### コピーする画像ファイル

ビルド時にビルドディレクトリ(/dist)へコピーする画像ファイルをstatic/imagesディレクトリに配置します。

```text
exercise-webpack
 |
 `--- /src
 |      |
 |      `--- /images
 |             |
 |             `--- background-image.png
 |             `--- footer-image.png
 |             `--- logo.png
 |
 `--- /static
        |
        `--- /images
               |
               `--- access-map.png          ← 追加
```

### 画像ファイルをコピーする

buildを実行するとバンドルと同時に対象ファイルがビルドディレクトリへコピーされます。

```console
> npm run build
```

以下がバンドル後のプロジェクトディレクトリの状態です。

```text
exercise-webpack
 |
 `--- /dist
        |
        `--- /images
        |      |
        |      `--- access-map.png
        |      `--- background-image.png
        |      `--- footer-image.png
        |
        `--- /styles
               |
               `--- style.bundle.css
               `--- style.bundle.css.map
```