26
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

webpack 4 の入門メモ

Last updated at Posted at 2020-04-24

概要

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

参考

プロジェクトの作成

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

> npm init -y

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

package.json

initコマンドで出力されたpackage.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の両方が必要なのでインストールします。

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

version

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

webpack-cli/init (optional)

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

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

initの実行

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

> 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の実行後

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

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がインストールされていますが、使用しないのでアンインストールします。

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

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

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もインストールされています。

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

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

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

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

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

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

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”というエラーが発生します。

webpack

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

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が有効になります。このプラグインのオプションでより細かくソースマップの出力設定を行うことができます。

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

webpack-dev-server

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

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

version

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

webpack.config.jsの編集

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

module.exports = {

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

axios

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

> npm install axios --save

webpackの動作確認

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

プロジェクトの構造

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を作成します。

<!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を下記の内容で書き換えます。

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を下記の内容で作成します。

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

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

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

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

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

webpack.config.jsの編集

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

// ↓ 追加
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オプションを付けてファイル変更の監視を行っていると、ファイルのコピーが失敗することがありました。

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

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

webpack.config.jsの編集

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

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

module.exports = {

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

}

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

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

package.jsonを編集

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

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

--help

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

webpack --help

--mode

バンドルのモードを指定します。指定できる値はproductiondevelopmentnoneです。

webpack --mode production

--config

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

webpack --config webpack.config.js

--watch

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

webpack --watch

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

--help

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

webpack-dev-server --help

--mode

バンドルのモードを指定します。指定できる値はproductiondevelopmentnoneです。

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の内容は下記の通りです。

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でバンドルします。

> npm run build

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

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

devサーバで動作確認する

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

> npm start

ESLint

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

ESLintのインストール

> npm install eslint --save-dev

version

> npx eslint --version
v6.8.0

.eslintrc.jsの生成

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

> 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です。

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

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

> npm install babel-eslint --save-dev

.eslintrc.jsの修正

下記の行を追加します。

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

> npm install eslint-loader --save-dev

webpack.config.jsの編集

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

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'
			},
			// ↑ 追加
		]
	},

}

ルールの追加

.eslintrc.jsの修正

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

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.loadersytle-loadercss-loaderがすでに組み込まれ、webpack.config.jsにローダーの設定が記述されています。

プロジェクトの構造

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

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

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

src/sytles/reset.css

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

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

src/sytles/style.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文を追加するだけです。

import '../styles/style.css'

webpack.config.jsの編集

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

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

src/index.htmlの編集

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

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

style-loader

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

> npm uninstall style-loader --save-dev

webpack.config.jsの編集

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

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でバンドルします。

> npm run build

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

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

URL関数を使う

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

プロジェクトの構造

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

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

src/sytles/style.css

style.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でエンコードします。

> npm install url-loader --save-dev

webpack.config.jsの編集

url-loaderを追加します。

module.exports = {

  module: {
    rules: [

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

}

バンドルする

> npm run build

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

.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エンコードされずに画像ファイルとして出力されます。

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に変換し、その画像ファイルをビルドディレクトリへコピーする機能を持っています。

> npm install file-loader --save-dev

バンドルする

> npm run build

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

.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ディレクトリへ出力されています。

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

画像ファイルのバンドル

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

プロジェクトの構造

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

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

バンドルするファイル

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

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-loaderfile-loaderが必要です。インストールと設定はCSSファイルのバンドルで説明した通りです。

バンドルする

> npm run build

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

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

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

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

webpack.config.jsの編集

// ↓ 追加
const copyFiles = [
  { from: 'src/index.html' },
  { from: 'static/images/*', to: './images/[name].[ext]' }
]
// ↓ 修正
// new CopyWebpackPlugin([{ from: 'src/index.html' }], { logLevel: 'debug' }),
new CopyWebpackPlugin(copyFiles, { logLevel: 'debug' }),

コピーする画像ファイル

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

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

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

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

> npm run build

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?