概要
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
- [Plugins | webpack] (https://v4.webpack.js.org/plugins/)
- To v4 from v3
プロジェクトの作成
プロジェクト名は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-cli/init] (https://github.com/webpack/webpack-cli/tree/next/packages/init)
このパッケージは必須ではありませんが、このパッケージを使うと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もインストールされています。
- 参考: [What is Babel?] (https://babeljs.io/docs/en/index.html)
下記の内容で.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
をインストールします。
- アンインストール
- [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)
> 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”というエラーが発生します。
- 参考: [ReferenceError regeneratorRuntime is not defined #9849] (https://github.com/babel/babel/issues/9849)
webpack
webpack.config.jsにoutput
とdevtool
を追加します。
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
が有効になります。このプラグインのオプションでより細かくソースマップの出力設定を行うことができます。
- 参考: [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
は開発用途のサーバーで、バンドル対象ファイルを編集すると自動的に反映されるライブリロードを提供します。
> 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
- 参考: [axios / axios] (https://github.com/axios/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
- 参考: [CopyWebpackPlugin] (https://webpack.js.org/plugins/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
オプションを付けてファイル変更の監視を行っていると、ファイルのコピーが失敗することがありました。
参考: [CleanWebpackPlugin] (https://github.com/johnagan/clean-webpack-plugin)
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
バンドルのモードを指定します。指定できる値は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の内容は下記の通りです。
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] (https://eslint.org/)
- 参考: [Configuring ESLint] (https://eslint.org/docs/user-guide/configuring)
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
- 参考: [babel-eslint] (https://github.com/babel/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
- 参考: [eslint-loader] (https://webpack.js.org/loaders/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'
},
// ↑ 追加
]
},
}
ルールの追加
- 参考: [List of available rules - ESLint] (https://eslint.org/docs/rules/)
.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.loader
、sytle-loader
、css-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
- 参考: [ReferenceError: window is not defined #288] (https://github.com/webpack-contrib/mini-css-extract-plugin/issues/288)
MiniCssExtractPlugin
とstyle-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でエンコードします。
- 参考: [url-loader] (https://webpack.js.org/loaders/url-loader/)
> 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に変換し、その画像ファイルをビルドディレクトリへコピーする機能を持っています。
- 参考: [file-loader] (https://webpack.js.org/loaders/file-loader/)
> 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-loader
とfile-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