#はじめに
Webpackは、JavaScript, CSS, JPGなどのリソースを一つのjsファイルにバンドルする(まとめる)ツールです。
バンドルすることで、各リソースの依存関係を気にする必要がなくなり、不適切な順序でファイルが実行されるような心配がなくなります。
モダンなJS開発でほぼ必須のWebpackですが、設定ファイルwebpack.config.js
の中身についていまいちよくわかっていませんでした。
設定ファイルを自由にカスタムできるようになれば開発の幅も広がる気がしたので、以下の基本項目について学びなおしました。
- Entry & Output
- Asset Modules
- Loaders
- Plugins
- Mode
#Entry & Output
バンドル設定の手始めとして、entry
に入力ファイルのエントリーポイント、output
にバンドル後の出力ファイル名filename
と作成パスpath
を指定してあげる必要があります。
作成パスは絶対パスで記述する必要があるため、Node.jsのpath
モジュールを使い、path.resolve(__dirname, './dist')
のようにします。
const path = require('path');
module.exports = {
entry: './src/index.js', // 入力ファイルのエントリーポイント
output: {
filename: 'bundle.js', // 出力ファイル名
path: path.resolve(__dirname, './dist'), // 出力ファイルのパス
},
mode: 'none',
};
#Asset Modules
AssetはWebpack5から追加されたモジュールです。
指定した形式のアセット(txt, jpgなどの静的ファイル)について、バンドルの仕方(バンドルファイル内にまとめるのか、切り離すのか、など)を以下の4つの方法で指定することができます。
- asset/resource
- asset/inline
- asset
- asset/source
##asset/resource
asset/resource
をmodule
のrules
で指定すると、ビルド時にリソース(以下の場合だとpng
とjpg
)がbundle.js
とは別に出力されます。
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
mode: 'none',
module: {
rules: [
{
test: /\.(png|jpg)$/,
type: 'asset/resource'
}
]
}
};
##asset/inline
asset/resource
だと指定した形式のリソースがbundle.js
とは別に出力されるため、画像がたくさんある場合などについては、読み込む際に都度HTTPリクエストが飛ぶことになります。
asset/inline
を利用すると、リソースはbase64形式でbundle.js
にひとまとめにされます。
SVGなどのサイズが小さいファイルがたくさんある場合など、ひとまとめにした方が通信コストを抑えることができるときにasset/inline
を指定します。
##asset
asset
を指定すると、バンドルするファイルサイズが8kB以上だとasset/resource
、8kB以下だとasset/inline
と同様の扱いになります。
この閾値は以下のように変更することができます。
module: {
rules: [
{
test: /\.(png|jpg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 3 * 1024, // 3kB
},
},
},
],
},
##asset/source
asset/source
では読み込んだファイルをJavaScriptのソースコードとして扱います。
{
test: /\.txt/,
type: 'asset/source',
},
#Loaders
LoadersはAsset Modulesで取り扱えないリソース(SASS, CSS, XMLなど)をインポートするときに使用します。
##CSS
CSSファイルをバンドルするためには、css-loader
とstyle-loader
が必要です。
css-loader
はCSSをJSで扱える形に変換するloaderで、style-loader
はHTMLのheadタグ内にインラインスタイルとしてCSSを挿入するloaderです。
npm install css-loader style-loader --save-dev
{
test: /\.css$/,
use: [
'style-loader', 'css-loader'
]
}
##SASS
SASSをバンドルするためには、SASSをCSSに変換するsass-loader
を追記します。
{
test: /\.scss$/,
use: [
'style-loader', 'css-loader', 'sass-loader'
]
}
##Babel
babel-loader
でJavaScriptファイルを下位のES仕様にトランスパイルすることができます。
例えば、ES6のコード(クラス構文、アロー関数、async/awaitなど)はIEで動かないため、このような変換が必要となります。
以下のモジュールをインストールし、babel-loaderの設定を行います。
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-proposal-class-properties --save-dev
presets: [ '@babel/env' ]
でES5へトランスパイルします。
ES6のクラス構文をトランスパイルするために、plugins: [ '@babel/plugin-proposal-class-properties' ]
というプラグインが別途必要になります。
{
test: /\.js$/,
exclude: /node_modules/, //node_modules以下のファイルは対象から除く
use: {
loader: 'babel-loader',
options: {
presets: [ '@babel/env' ], // ES5に変換
plugins: [ '@babel/plugin-proposal-class-properties' ] // クラス構文を変換可能にするプラグイン
}
}
}
#Plugins
Plugins(プラグイン)はLoadersでできないことを実現するためのJSライブラリです。
プラグインの種類によってはバンドルファイルのサイズを減らしたりすることもできます。
##terser-webpack-plugin
terser-webpack-plugin
はバンドルファイルのサイズを小さくするためのプラグインです。
以下のように記述します。
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
publicPath: 'dist/'
},
mode: 'none',
module: {
rules: [
// 省略
]
},
plugins: [
new TerserPlugin()
]
};
##mini-css-extract-plugin
バンドルファイルのサイズが大きくなってしまったときに、CSSファイルのみを分離するためのプラグインがmini-css-extract-plugin
です。
rules
に記述したstyle-loader
をMiniCssExtractPlugin.loader
に置き換えます。
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// 省略
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader'
]
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'
]
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles.css'
})
]
};
また、CSSファイルが分離されるので、htmlのheadタグに<link rel="stylesheet" href="./dist/styles.css" />
を忘れずに追加します。
##clean-webpack-plugin
Webpackでbundle.jsにひとまとめにすると、ブラウザキャッシュによって、コードに変更を加えても画面に反映されないケースがあります。
そんなときは、出力ファイルをfilename: 'bundle.[contenthash].js'
とすることによって、ビルドするたびに新しい名前でバンドルファイルが出力されるようにします。
ただ、このままだとビルドを繰り返していくうちにバンドルファイルが溜まってしまいます。
必要なファイル以外を削除するために使用するのがclean-webpack-plugin
です。
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.[contenthash].js',
path: path.resolve(__dirname, './dist'),
publicPath: ''
},
plugins: [
new CleanWebpackPlugin()
]
};
##html-webpack-plugin
contenthashでビルドするたびに新しいバンドルファイルが生成されると、htmlで読み込む際にもファイル名を書き換えなければなりません。
それだと面倒なので、html-webpack-plugin
を使用して、正しい読込ファイルが記述されたhtmlファイルも同時に生成します。
htmlファイルはバンドルファイルと共にdistフォルダに出力されるので、publicPath: ''
とします。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.[contenthash].js',
path: path.resolve(__dirname, './dist'),
publicPath: ''
},
plugins: [
new HtmlWebpackPlugin()
]
};
また、出力するhtmlの内容を以下のようにカスタマイズすることもできます。
new HtmlWebpackPlugin({
title: 'Hello world',
meta: {
description: 'Some description'
}
})
handlebarsでhtmlのテンプレートを用意して出力する方法もあります。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.hbs$/,
use: [
'handlebars-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Hello world',
template: 'src/index.hbs',
description: 'Some description'
})
]
};
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{htmlWebpackPlugin.options.title}}</title>
<meta name="description" content="{{htmlWebpackPlugin.options.description}}">
</head>
<body>
</body>
</html>
#Mode
ビルドにはproductionとdevelopmentの2つのモードがあります。
productionでビルドしたファイルはできるだけ小サイズで最適化されるようにし、developmentの場合はビルドの時間ができるだけ短いようにしてあげる必要があります。
configファイルをwebpack.dev.config.js
webpack.production.config.js
のように別々に作成します。
例えば、webpack.dev.config.js
では、ビルドができるだけ早くなるように、contenthashやMiniCssExtractPluginを使用しません。
devServer
でwebpack-dev-serverの設定も行っています。
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js', // contenthashは使用しない
path: path.resolve(__dirname, './dist'),
publicPath: ''
},
mode: 'development', // development
devServer: {
contentBase: path.resolve(__dirname, './dist'),
index: 'index.html',
port: 9000
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader', 'css-loader' // MiniCssExtractPluginを使用しない
]
},
]
},
};
ビルドの実行コマンドは以下のようにします。
dev
の方については、ビルドと同時にwebpack-dev-serverが起動するようになっています。
--hot
オブションをつけることで、コード修正を行うたびにビルドが行われ、ブラウザに反映されるようになります。
"scripts": {
"build": "webpack --config webpack.production.config.js",
"dev": "webpack serve --config webpack.dev.config.js --hot"
},
#参考資料
https://qiita.com/one-kelvin/items/b810aafb6b5ef90789a3