やっぱりwebpackがわからない(エピソード2)
はじめに
やっぱりwebpackがわからない(エピソード1)からの続きです。そもそもnpmからわからないも参考にしてください。
ローダー(Loader)とは
webpackは、基本的にはJavaScriptのデータしか扱うことができません。そこで、CSSや画像などをJavaScriptのオブジェクトにして、webpackで扱えるようにするモジュールがローダーです。リソースそれぞれに、ローダーが存在します。
CSSはともかく、画像をJavaScriptにするというのは何だか不思議な話でもありますが、画像も基本的にはコンピューターのデータですので、JavaScriptで使用できるデータに変換することは可能です。
ローダー及びバンドルの注意点
これらのリソースをJavaScriptのデータにしてどうするかですが、webpackはモジュールバンドラーですので、もちろんこれらをJavaScriptのデータとして1つにまとめます。ただし、ここで注意点があるのですが、何でもかんでもバンドルするのは好ましくありません。
確かにデータをまとめて通信回数を減らすことにより、パフォーマンス(表示速度)をあげることができるのですが、バンドルは諸刃の剣であって、代わりにデータ量が増えます。JavaScriptにまとめるので、JavaScriptの容量が増えるのはもちろんですが、バンドルするCSSや画像などのデータは、元の容量より変換後の容量の方が多くなりがちです。 では、なぜバンドルするのかですが、それでも複数回通信するよりかはパフォーマンスがいいからです。だたし、これは場合によりけりとなります。例えば、多数の小さな画像を複数回通信するのなら、JavaScriptにバンドルしたほうがパフォーマンスにいいですが、大きな画像を1つ通信するのにバンドルすると、その1回の通信コストより、データ量のコストの方が大きくなってしまいます。これらを見極めるのが技術者の腕の見せ所でもありますが、webpackである程度設定することも可能です。
CSS
では、代表的なローダーであるcss-loader
を使用して、ローダーの使い方を説明します。
CSSをwebpackで扱えるようにするには、cssをJavaScriptにバンドルするcss-loader
と、バンドルされたCSSをHTMLでスタイルシートとして読み込まれるようにするstyle-loader
が必要になります。
まずは、インストールをしましょう。
$ npm i -D style-loader css-loader
webpack.config.js
module
プロパティを追加しルールを設定します。
module.exports = {
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
}
}
前回の続きから記述すると、次になります。
const path = require('path');
module.exports = {
context: path.join(__dirname, "src"),
entry: `./index.js`,
output: {
path: path.join(__dirname, "dist"),
filename: "main.js"
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
},
};
さて、本コンテンツの題名「やっぱりwebpackがわからない」ですが、わからなくなるのはここからです。 この辺りからwebpack.config.js
の記述が複雑になってきます。
この設定が、どのような構造になっているかがわかりますか?エピソード1でも説明しましたが、webpackの設定はどこまでいってもプロパティに値を設定しているだけとなります。
この例でいうと、以下となります。1つずつじっくりと確認してみてください。
-
module
オブジェクトのexports
プロパティにmodule
プロパティを設定している。 - その
module
プロパティに配列としてrules
プロパティを設定してオブジェクトを代入し、test
プロパティとuse
プロパティを設定している。 -
test
プロパティには正規表現を設定し、use
プロパティには配列を設定している。
つまり、ドット演算子で表現すると以下のようになります。
module.exports.module.rules[0].test = /\.css$/;
module.exports.module.rules[0].use[0] = 'style-loader';
module.exports.module.rules[0].use[1] = 'css-loader';
随分とわかりやすくなったのではないでしょうか。これが分からない人はJavaScriptのオブジェクトが理解できていないので、そこから学んだほうがいいです。Gulpがどうも読めないという人も、大抵JavaScriptのオブジェクトが理解できていない場合が多いです。
では、各プロパティの説明をします。
-
module
ローダーなどのモジュールの設定をするプロパティです。 -
module.rules
各ローダーを設定するプロパティです。配列となっており、その各要素に各ローダーのルールを設定して行きます。 -
rules.test
プロパティ名からは想像しにくいですが、正規表現などで該当するファイルを指定します。 -
rules.use
使用するローダーを指定するプロパティです。
ちなみにwebpackは、このtest
やらuse
などという、プログラミング初心者が遊び半分で取って付けたようなプロパティ名が多いのも、分かりにくくしている要因の1つです。つまり、webpackが分かりにくいというのは、あなたがそこそこプログラミングができるからでもありますので、安心してください。
ビルド
では、実際にファイルを用意してビルドしてみましょう。
※ index.html
はビルドしませんので、直接出力先へ設置します。
import './style.css';
body {
color: red;
font-weight: bold;
}
<!doctype html>
<html lang="ja">
<head>
<script src="./main.js"></script>
</head>
<body>
<p>TEST</p>
</body>
</html>
これでビルドすると、index.html
ではCSSファイルを読み込んでいませんが、main.js
にバインドされているためスタイルシートが反映されます。
options
sourceプロパティでソースマップ出力の設定をしましたが、各ローダーのソースマップを含めるには、sourceMap
プロパティを設定する必要があります。true
で出力、false
または設定なしで出力しません。
このような使用するオプションはoptions
プロパティを設置します。次のようにoptions
プロパティを用意して、sourceMap
プロパティを設定しましょう。
module: {
rules: [
{
test: /\.css/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
sourceMap: true,
}
}
]
}
]
},
さて、module
プロパティにはcss-loader以外にもローダーを設定していくことになるのですが、その都度sourceMap
プロパティの設定を行うことになります。したがって、developmentモードではローダーのソースマップは必要ないなどといった場合、各設定を変更しなければなりません。このような場合、様々な方法はありますが、例えば次のような方法でまとめて設定できます。
// MODE変数でmodeの値を設定する。
const MODE = "development";
// MODE変数がdevelopmentならsourceMapStatusをtrueにする。
const sourceMapStatus = MODE === "development";
module.exports = {
mode: MODE,
module: {
// 省略
options: {
sourceMap: sourceMapStatus
}
};
css-loader
でよく使うオプションをもう1つ、ご紹介しておきます。url
オプションです。url
オプションは、CSS内のurl()
の有効無効を設定します。デフォルトではtrue
となっています。
options: {
url: false,
sourceMap: sourceMapStatus,
}
Sass
CSSの次はSassですよね。ウェブデザイナーからフロントエンドエンジニアまで、現在スタイルシートのコーディングには、ほとんどの方がSassを使用していると思います。
SassをCSSに変換するローダーにはsass-loaderを使用します。また、コンパイルを行うモジュールとしてsassが必要ですので、共にインストールをします。
$ npm i -D sass-loader node-sass
webpack.config.js
の設定をしていきましょう。とりあえずは、test
プロパティをsassとscssファイルが読めるようにし、use
プロパティで使用するローダーを設定するだけでOKです。なお、使用するローダーを設定は、後ろから順番に適用されます。
module: {
rules: [
{
test: /\.(sass|scss|css)$/,
use:['style-loader','css-loader','sass-loader']
}
]
},
では、実行に必要なファイルを用意します。
import './style.scss';
$color: red;
$weight: bold;
body {
color: $color;
font-weight: $weight;
}
<!doctype html>
<html lang="ja">
<head>
<script src="./main.js"></script>
</head>
<body>
<p>TEST</p>
</body>
</html>
これでビルドを行うと style.scss
がCSSに変換され、かつmain.js
にバインドされます。
また、CSSで記述したように、ローダーに分けて設定もできます。
module: {
rules: [
{
test: /\.(sass|scss|css)$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
url: false,
sourceMap: true,
}
},
{
loader: "sass-loader",
options: {
sourceMap: true,
}
}
]
}
]
},
この場合も、use
プロパティの後ろから設定した順に、適用されて行きます。つまり、次の順番となります。
- sass-loader: SassをCSSに変換
- css-loader: CSSをJavaScriptにバンドル
- style-loader: HTMLのlinkタグにCSSを展開
CSS内の画像をバンドル
これまで、url
プロパティの値をfalse
にしていましたので、CSS内のurl()
は無効となり、画像は読み込まれませんでした。これをtrue
にすると画像もビルドされ、それが読み込まれるのですが、JavaScriptにバンドルさせるには別の設定が必要となります。
画像をJavaScriptにバンドルするということは、画像をJavaScriptで使用可能なデータに変換しなければいけないのですが、そのために画像をbase64という形式に変換します。ここでは詳しく説明しませんが、base64はアルファベットの大文字小文字、数字、記号を含めた64文字で構成されるエンコード方式となります(実際は65文字となる)。JavaScriptは、Base64のエンコードとデコードに対応した関数を持っています。
webpack4までは、画像をBase64にエンコードして埋め込むのにurl-loaderを使用していましたが、webpack5からは搭載されているtype
プロパティで対応できます。
rules
プロパティの配列に新しいエレメントを設けて、test
プロパティで対象ファイルを設定、type
プロパティの値をasset/inline
にすることにより、一括で全てのファイルをバンドルします。また、css-loader
のurl
プロパティをtrue
するのを忘れないで下さい。
module: {
rules: [
{
test: /\.(sass|scss|css)$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
// url()を機能させる。
url: true,
sourceMap: true,
}
},
{
loader: "sass-loader",
options: {
sourceMap: true,
}
},
]
},
// 追加
{
test: /\.(gif|png|jpg|svg)$/,
type: "asset/inline",
}
]
},
実行するsassファイルにbackground-image
プロパティを追加して、同じ階層に画像を設置しましょう。
$color: red;
$weight: bold;
body {
color: $color;
font-weight: $weight;
background-image: url(img.png);
}
これで実行すると、画像がバンドルされます。
バンドルする画像を分ける
ローダー及びバンドルの注意点では、何でもかんでもバンドルするのは好ましくないと説明しました。例えば、画像をBase64にエンコードした場合、容量は約1.33倍になってしまいます。したがって、通信コストと比較して、それでもバンドルしたほうがいい小さな画像はバンドルして、通信コストより容量の増加コストが高い画像は、そのまま使用したりします。
まず説明したいのが、type
プロパティの値をasset/inline
にしてバンドルをしていましたが、これをasset/resource
にすると、画像は出力されますがバンドルはされません。つまり、画像によってasset/inline
かasset/resource
かを切り替えればいいわけです。
まず、type
プロパティの値をasset
と、どっちつかずの設定にします。そして、parser.dataUrlCondition.maxSize
でバンドルする最大ファイル値を設定します。つまり、これを超える画像はバンドルされません。
{
test: /\.(gif|png|jpg|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
// これで100KB以上という設定になる。
maxSize: 100 * 1024,
},
},
}
プラグイン(Plugins)
ローダーは、リソースをwebpackで扱えるようにするためのものでした。そのローダーでは実現できない機能を提供するのがプラグインです。
mini-css-extract
mini-css-extract
プラグインは、バンドルしたJavaScriptからスタイルシートの箇所をcssファイルとして出力します。つまり、通常のウェブサイト制作のように、CSSファイルをlink
タグで読み込むことができます。
プラグインを使用するには、使用したいプラグインをインストールする必要があります。
$ npm i -D mini-css-extract-plugin
使い方ですが、require()
でプラグインを読み込み、ローダーでプラグインを有効にします。そして、プラグインの設定を行います。
以下、わかりやすくするように、他の設定は必要最低限にしています。
// プラグインの読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: `./src/index.js`,
output: {
path: `${__dirname}/dist`,
filename: "index.js"
},
module: {
rules: [
{
test: /\.(sass|scss|css)$/,
use: [
// CSSファイルを書き出すオプションを有効にする
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
},
// sassを使用しない場合は不必要
{
loader: 'sass-loader',
}
]
}
]
},
// プラグインの設定
plugins: [
new MiniCssExtractPlugin({
// 出力先の設定
filename: './css/[name].css',
}),
],
};
では、よく使用するプラグインを2つ紹介しておきます。
html-webpack-plugin
これまで、出力先のディレクトリ内に直接htmlファイルを置いていました。もちろんですが、このように出力元と先に差がある開発は好ましくありません。そこで、htmlファイルも同じようにビルドしたいのですが、それを行うのがhtml-webpack-plugin
です。対象とするhtmlファイルをビルドしてくれます。その際、JavaScriptとCSSの読み込み設定もしてくれます。
インストールします。
$ npm i -D html-webpack-plugin
同じようにrequire()
でプラグインを読み込み、プラグインの設定をしています。今回は特にローダー側の設定はしません。
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: `./src/index.js`,
output: {
path: `${__dirname}/dist`,
filename: "index.js"
},
module: {
rules: [
{
test: /\.(sass|scss|css)$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: './css/[name].css',
}),
// html-webpack-pluginの設定
new HtmlWebpackPlugin({
// 対象のテンプレートを設定
template: `${__dirname}/src/index.html`,
// 書き出し先
filename: `${__dirname}/dist/index.html`,
// ビルドしたjsファイルを読み込む場所。デフォルトはhead
inject: 'body'
}),
],
};
template
プロパティですが、設定しないと自動でhtmlファイルが出力されます。また、src配下にindex.ejs
があれば、それを使用します。EJSの使い方はここでは説明しませんが、JavaScriptで使用するテンプレートとして、ヘッダーやフッターなどに分割して管理することが可能となります。
EJS
copy-webpack-plugin
copy-webpack-plugin
は、指定したファイルをそのままコピーして出力します。これも、出力元と先を合わせるのに役立ちます。
$ npm i -D copy-webpack-plugin
以下、/src/img/
内のファイルを全て/dist/img/
にコピーします。
const CopyWebpackPlugin = require('copy-webpack-plugin');
省略
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: `${__dirname}/src/img/`,
to: `${__dirname}/dist/img/`,
}
]
}),
],
imagemin-webpack-plugin
copy-webpack-plugin
はファイルを圧縮します。
$ npm i -D imagemin-webpack-plugin
各ファイル形式に対応したパッケージもインストールします。
// jpg
$ npm i -D imagemin-pngquant
// png
$ npm i -D imagemin-mozjpeg
// gif
$ npm i -D imagemin-gifsicle
// svg
$ npm i -D imagemin-svgo
詳しい説明は省略しますが、次は設定例となります。
const ImageminMozjpeg = require('imagemin-mozjpeg');
省略
plugins: [
new ImageminPlugin({
test: /\.(jpe?g|png|gif|svg)$/i,
pngquant: {
quality: '70-85'
},
gifsicle: {
interlaced: false,
optimizationLevel: 9,
colors: 256
},
plugins: [
ImageminMozjpeg({
quality: 85,
progressive: true
})
],
svgo: {},
})
]
最後に
エピソード2はとりあえずここで終わりにしておきます。これ以上ここで説明してしまうと、恐らくややこしいまま、モヤモヤした気持ちで終わることになるだろうからです。エピソード2も反響があれば、エピソード3を書きたいと思います。
今までReactやらTypeScriptなどでwebpackの設定を見て「?!っ」となっていた人も、エピソード1、2を読んだ後に再度webpackの設定を見ると、まるで氷が溶けたかのようにスーっと頭の中に内容が入ってくると思います。
最後の最後に、よければ業務ができる中級者になるためのJavaScript入門(文法編)、DOM編を公開しておりますので、無料公開ページだけでも読んでやってください。
Discussion