はじめに
前回の記事で webpack を利用したフロントエンド開発環境の構築手順や、 webpack を使う理由についてまとめました。
また、実際に JavaScript をバンドルするところまで構築したので今回は CSS や画像を扱うところまで進めて行きたいと思います。
CSS を読み込むためのローダーをインストールする
まずは webpack で CSS や SASS を読み込めるようにローダーをインストールします。
$ yarn add -D sass sass-loader css-loader postcss-loader autoprefixer mini-css-extract-plugin
また、CSS フレームワークを読み込んだときの確認用に Bootstrap をインストールしておきます
$ yarn add bootstrap
インストールしているプラグインの用途について表にまとめました。
プラグイン名 | 用途 |
---|---|
sass | sass-loader を利用するために必要なパッケージ。Dart Sassとも呼ばれている。 |
sass-loader | sass を css にコンパイルするローダー |
css-loader | css を JavaScript で使用できる形に変換するローダー |
postcss-loader | postcss を使用するローダー。postcss は JavaScript で CSS を変換するプラグインを作るためのツール。 |
autoprefixer | css にベンダープレフィクスが追加された css を出力するツール。postcss 製のプラグイン |
mini-css-extract-plugin | JavaScript に取り込んだ css を個別の css ファイルに出力するためのローダー。このプラグインを使用した場合はstyle-loader は不要。 |
注意点として、記事によってはsass
ではなくnode-sass
を指定している場合がありますが、node-sass
は非推奨のパッケージとなっています。
実際に GitHub の README にも以下のように記述されています。
Warning: LibSass and Node Sass are deprecated. While they will continue to receive maintenance releases indefinitely, there are no plans to add additional features or compatibility with any new CSS or Sass features. Projects that still use it should move onto Dart Sass.
読み込んだローダーやプラグインを追加します。
const { merge } = require('webpack-merge');
const path = require('path');
const dist = path.resolve(__dirname, 'dist');
const commonConfig = require('./webpack.config.common.js');
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = merge(commonConfig, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
open: true,
port: 9000,
static: {
directory: dist,
},
},
module: {
rules: [
{
test: /\.(sass|scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
+ loader: 'css-loader', // css ファイルを JavaScript ファイルに変換する
+ loader: 'postcss-loader', // 変換された css に対してベンダープレフィックスを付与する処理を実行する
+ loader: 'sass-loader', // sass を css に変換する
+ // ↑ローダー末尾に指定した方から順番に実行されます。
/*
ローダーごとに個別にソースマップを指定できますが、
デフォルトでは devtool の値に依存しているので、devtool で設定されていれば指定する必要はありません
{
loader: 'css-loader'
オプションを指定した場合の書き方
options: {
sourceMap: true
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
},
*/
],
},
],
},
+ // 各種プラグインを読み込む
+ plugins: [
+ // jsファイルとcssファイルを分割するためのプラグイン
+ new MiniCssExtractPlugin({
+ // outputオプションのfilenameと同じ動作をする
+ filename: `./css/style.[contenthash].min.css`,
+ }),
+ ]
});
ローダーの実行は末尾に指定した方から順番に実行されるので記述の順番に注意する必要があります。
module.rules allows you to specify several loaders within your webpack configuration. This is a concise way to display loaders, and helps to maintain clean code. It also offers you a full overview of each respective loader.
Loaders are evaluated/executed from right to left (or from bottom to top). In the example below execution starts with sass-loader, continues with css-loader and finally ends with style-loader. See “Loader Features” for more information about loaders order.
ローダーで指定できるオプション
各種ローダーで指定できるオプションの一覧は公式ドキュメントから確認できます。
JavaScript 同様、ソースマップを指定することでビルド後のファイルとビルド前のファイルが対応付けられデバッグがしやすくなります。ソースマップの指定は各ローダーでも指定できますが、devtool
で指定することで有効化することも出来ます。
なぜ MiniCssExtractPlugin で CSS を別ファイルに出力するのか?
webpack では CSS ファイルも JavaScript に変換してバンドルされますが、全てのファイルを一つにまとめるとファイルサイズが膨大になって読み込みに時間がかかったり、CSS のキャッシュ対策が出来かったりするデメリットがあります。
JavaScript でバンドルしたものを CSS ファイルに出力することで個別にキャッシュ対策などが出来るようになります。
MiniCssExtractPlugin のオプション
MiniCssExtractPlugin
プラグインで指定しているfilename
ですが、webpack のエトリーポイントを設定した箇所の output.filename
オプションと同様の動作をします。
output.filename
オプションでは出力先のファイル名を指定しますが、ディレクトリ構造を作成することもでき、./js/bundle.mim.js
のように指定することも可能です。
また、出力先のディレクトリはoutput.path
で指定した先に出力されます。webpack.config.common.js
では以下のように指定していました。
const path = require('path');
const dist = path.resolve(__dirname, 'dist');
const src = path.resolve(__dirname, 'src');
module.exports = {
entry: './src/js/index.js',
output: {
path: dist, // 出力先ディレクトリ(絶対パスで指定)
filename: './js/bundle.mim.js',
},
// ~ 中略 ~
}
そのため、JavaScript ファイルでインポートしたsass
ファイルは、dist
ディレクトリ配下のcss
ディレクトリに出力されます。
出力先のファイル名に[contenthash]
と指定することで、ブラウザのキャッシュ対策としてランダムな文字列を指定することが出来ます。ソースファイルに変更がされ、ビルドされるたびに新しいハッシュ値が指定されたフ ァイルが出力されます。
sass ファイルを読み込む
実際に sass ファイルを読み込んでバンドルするところまで行っていきます。前回、src/scss/
ディレクトリ配下に作成したstyle.scss
ファイルを修正します。
@import "~bootstrap/scss/bootstrap";
h1 {
color: red;
}
index.js
で sass ファイルを読み込みます。
+ import '@scss/style.scss'
// console.log(a);
webpack.config.common.js
でエイリアスを指定しているので、@scss
と指定するだけでソースファイルのディレクトリまでパスが割り当てられます。
テンプレ ートの HTML ファイルも以下のように修正します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="container">
<h1>Hello! webpack!</h1>
</div>
</body>
</html>
開発用サーバを起動して css ファイルが出力され、スタイリングが適応されていることを確認します。
また、ハッシュ値が記述された css ファイルが読み込まれていることが分かります。
“
ソースとなる sass ファイルの内容が変わったときに、ハッシュ値も変化しているのか確認してみます。style.sass
を以下のように修正します。
@import "~bootstrap/scss/bootstrap";
h1 {
color: red;
+ text-align: center;
}
ブラウザをリロードして読み込まれている css ファイルを確認します。
バンドルされた css のハッシュ値が変わっていることが分かります。
ソースマップを確認する
JavaScript ファイルでもソースマップを確認しましたが、sass
からcss
に変換されたファイルのソースマップを確認します。
デベロッパーツールからHello! webpack!
という赤文字にフォーカスを当てます。
style.scss:3
をクリックすると、元ファイルの何行目に書かれたスタイリングなのか確認することが出来ます。
_reboot.scss:93
というのは読み込んだ Bootstrap で使用されているsass
ファイルになります。
devtool: 'inline-source-map'
をfalse
に変更すると、JavaScript のときと同様にバンドルされたファイルを参照するようになるので、CSS フレームワークを使用していた場合に自分のソースファイルと区別がしづらくなってしまいます。
出力先のフォルダをクリーンアップする
ソースファイルのstyle.scss
が変更される度にハッシュ値が割り当てられてたバンドルファイルが出力されることを確認しましたが、yarn run dev
を実行する度に以前のファイルが残った状態になっています。
一度出力先のディレクトリに残っているファイルを削除してから新しいファイルを出力するようにclean-webpack-plugin
を導入します。
$ yarn add -D clean-webpack-plugin
webpack.config.common.js
を編集します。
// ~ 中略~
+ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// ~ 中略 ~
// 各種プラグインを読み込む
plugins: [
// HTMLのテンプレートファイルからバンドルされたモジュールを読み込んだHTMLファイルを出力する
new HtmlWebpackPlugin({
template: './src/html/index.html',
filename: 'index.html',
}),
// 指定した出力ディレク トリ内のファイルをクリーンアップ(削除)する
+ new CleanWebpackPlugin(),
],
}
new CleanWebpackPlugin()
で引数を指定しない場合は、デフォルトで出力先で指定したディレクトリ配下の全てのファイルが削除されます。
dist
ディレクトリ配下で使用するjson
ファイルなど、削除したくない場合のファイルがある場合は以下のように引数にオブジェクトを指定します。
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['**/*', '!*.json', '!directoryToExclude/**'],
});
指定できるパターンも複数種類があるので一度ドキュメントを確認してみて下さい。
ビルドを実行すると、先程複数あったバンドルされた css ファイルが 1 つになっていると思います。
webpack-dev-server 実行時にはバンドルファイルは出力されない
開発用サーバ実行時に起動されたブラウザではバンドルされたファイルを読み込んでいますが、dist
ディレクトリ配下にバンドルされたファイルは出力しないので注意が必要です。
開発用サーバ実行時でもファイルを出力したい場合は、ターミナルを複数開いてビルドコマンドを実行します。
ベンダープレフィックスを付与する
ベンダープレフィックスとはベンダー接頭辞のことで、試験的及び非標準な CSS プロパティに対して付けられます。各ブラウザで css が正しく適応されるようにベンダープレフィックスを付与します。
ベンダープレフィックスの種類
ベンダープレフィックス | 対応ブラウザ |
---|---|
-webkit- | Google Chrome、Safari |
-moz- | Firefox |
-o- | 古い Opera |
-ms- | Internet Explorer、Microsoft Edge |
postcss.config.js を作成する
postcss-loader
を読み込んだので設定ファイルを追加します。トップディレクトリにpostcss.config.js
を作成して以下の用に記述します。
module.exports = {
plugins: [require('autoprefixer')],
};
autoprefixer
は指定したブラウザで必要なベンダープレフィックスのみを出力してくれます。ブラウザの指定の仕方は.browserslistrc
ファイルを作成するか、package.json
にbrowserslist
を指定する方法がありますが、今回は.browserslistrc
ファイルを作成して以下の設定内容を記述します。
last 4 version
これは過去 4 つ前までのバージョンのブラウザをターゲットにしていることを意味します。Browserslistというサイトで構文がどのブラウザターゲットになっているのか確認することが出来ます。
last 4 version
でターゲットになっているブラウザは以下のようになります。
一度、適当なスタイリングを追加して動作しているか確認します。
@import "~bootstrap/scss/bootstrap";
h1 {
color: red;
text-align: center;
+ user-select: none;
}
user-select
はユーザーがテキストを範囲選択できるかどうかを制御する css プロパティです。開発用サーバを起動してプロパティを確認します。
ベンダープレフィックスが記述された css プロパティが割り当てられていることが分かります。
not dead の設定もしたほうが良い
last 4 version
で指定したブラウザだと、IE11、IE10、などサポートが終了したブラウザも含まれています。not dead
を指定することで、24 ヶ月以上公式サポートやアップデートがないブラウザを除外することができます。
現在は、IE 11、IE Mobile 11、BlackBerry 10、BlackBerry 7、Samsung 4、Opera Mobile 12.1、および Baidu の全バージョンが含まれています。
.browserslistrc
を以下のように修 正します。
last 4 version and not dead
複数の条件を記述する場合はand
で繋げます。not dead
を追加した場合のターゲットブラウザは以下のようになります。
IE などが消えていることが分かります。css プロパティも確認します。
-ms-
というベンダープレフィックスが付与された css プロパティが無くなっていることが確認できました。
.browserslistrc
で設定できるターゲットブラウザは様々な範囲を指定できるので、一度Browserslistで確認してみて下さい。
設定項目 | 内容 |
---|---|
> 5% | 全世界で 5% 以上使用されているブラウザ |
>= 5% in JP | 日本で 5%以上使用されているブラウザ |
> 0.2% and not dead | 全世界で 0.2% 以上使用されて且つ更新が停止していないブラウザ |
画像ファイルを読み込む
ここからは画像ファイルを読み込めるように設定していきます。
webpack5 からAsset Modules
が導入されました。Asset Modules は、ローダーを追加することなく画像やアイコンなどを利用できるようにするためのモジュールです。
webpack5 以前でファイルを読み込むために使用していたローダーの一覧です 。
モジュール名 | 用途 |
---|---|
file-loader | ファイルを文字列としてインポートする |
url-loader | ファイルを DataURL としてバンドルする |
raw-loader | ファイルを出力ディレクトリに出力する |
新しく追加されたモジュールを以下の表にまとめました。
モジュール名 | 用途 |
---|---|
asset/resource | 個別のファイルを出力し、その URL をエクスポートする。file-loader に相当する。 |
asset/inline | アセットのデータ URI をエクスポートする。url-loader に相当する。 |
asset/source | アセットのソースコードをエクスポートする。raw-loader に相当する。 |
asset | データ URI をエクスポートするか、別のファイルを生成するかを自動的に選択する。url-loader に相当する。 |
実際に画像を webpack でバンドルして HTML に出力した結果です。フリー画像と webpack の SVG ファイルを読み込んでいます。必要なファイルをsrc/images
ディレクトリに追加しておきます。
webpack.config.common.js
に Asset Modules に関する記述を追加します。
const path = require('path');
const dist = path.resolve(__dirname, 'dist');
const src = path.resolve(__dirname, 'src');
module.exports = {
entry: './src/js/index.js',
output: {
path: dist, // 出力先ディレクトリ(絶対パスで指定)
filename: './js/bundle.mim.js',
},
module: {
+ rules: [
+ // 画像ファイルをビルドフォルダにコピーする
+ {
+ // 拡張子の大文字も許容するように最後尾に i を加える
+ test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
+ type: 'asset/resource',
+ },
+
+ // フォントSVGファイルなどインライン化するファイル
+ {
+ test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
+ type: 'asset/inline'
+ },
+ ],
+ },
// ~ 中略 ~
}
テンプレートの HTML ファイルを修正します
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="container">
<h1>Hello! webpack!</h1>
+ <div id="root"></div>
</div>
</body>
</html>
index.js
ファイルを画像を読み込む処理と、img
タグなどを作成する記述を追加します。console.log
の記述は不要なので削除しておきます。
import '@scss/style.scss'
- // console.log(a);
+ // @imagesはエイリアスで指定したパス
+ import webpackLogo from '@images/webpack.svg'
+ import cat from '@images/cat.jpg'
+ const logo = document.createElement('img');
+ logo.src = webpackLogo;
+ const image = document.createElement('img');
+ image.src = cat;
+ const app = document.querySelector('#root');
+ app.append(logo, image);
開発用サーバを起動後、別のターミナルでビルドを実行します。
$ yarn run start
# 別のターミナルを起動して実行
$ yarn run dev
読み込んだ jpg ファイルがdist
ディレクトリに出力されて、svg ファイルが Data URI でエクスポートされていることが分かります。
asset/inline について
asset/inline
では、リソースは base64 形式でバンドルさ れた JavaScript ファイルに出力されるので、SVG などファイルサイズが小さくファイル数が多い場合、JavaScript ファイルにひとまとめにしたほうが通信コストを抑えることが出来ます。
開発モードでバンドルされたファイルを確認してみても、Data URI が読み込まれていることが確認できます。
asset/resource について
asset/resource
では特に指定しない限り、デフォルトでは[hash][ext][query]
というファイル名で出力ディレクトリ直下に出力されます。
[hash]
:元ファイルから計算し たハッシュ値[ext]
:元ファイルの拡張子で、.始まりの値[query]
:?始まりのクエリ値
webpack の出力先の指定でassetModuleFilename
を追加するか、asset/resource
でgenerator.filename
でモジュールタイプでのみ機能するように指定することが出来ます。
const path = require('path');
const dist = path.resolve(__dirname, 'dist');
const src = path.resolve(__dirname, 'src');
module.exports = {
entry: './src/js/index.js',
output: {
path: dist, // 出力先ディレクトリ(絶対パスで指定)
filename: './js/bundle.mim.js',
// こっちに記述してもOK
// [name]は元のファイル名
// assetModuleFilename: 'images/[name].[hash][ext][query]'
},
module: {
rules: [
// 画像ファイルをビルドフォルダにコピーする
{
// 拡張子の大文字も許容するように最後尾に i を加える
test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
type: 'asset/resource',
+ generator: {
+ // [name]は元のファイル名
+ filename: 'images/[name].[hash][ext][query]'
+ }
},
// フォントSVGファイルなどインライン化するファイル
{
test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
type: 'asset/inline'
},
],
},
// ~ 中略 ~
}
ファイルを出力し直してみます。
$ yarn run dev
指定したディレクトリ構造で出力されました。
asset について
asset
を指定するとバンドルするファイルサイズが指定した値より大きければasset/resource
で別ファイルとして出力し、小さければ Data URI として JavaScript ファイルに組み込む動作をします。
ルールの記述方法はRule.parser.dataUrlCondition
という項目を参考にしてみて下さい。
おわりに
今回は、webpack で CSS ファイルや画像を読み込む設定についてまとめました。
次回は JavaScript に関するローダーや Linter、コードフォーマッターについてまとめていこうと思います。