こんにちは!高野です。
Webpackレベルアップシリーズの第3弾はloaderです。これは .js
ファイル以外も最終的に .js
ファイルとして結合するためのパーサーのような機能を提供するもので、今やWebpackを利用する主目的と言っても過言ではありません。
loaderの設定方法を理解して、CSSファイルでも画像ファイルでもTypeScriptファイルでも、何でもバンドルできるようになってしまいましょう!
コンテンツ
- Webpack Level 1: 裸のWebpackのデフォルト動作を理解する
- Webpack Level 2: 設定ファイルをカスタマイズする
- Webpack Level 3: loadersを追加して.js以外のファイルを結合する
- Webpack Level 4: Webpack Pluginsを用いて静的サイトをS3に自動デプロイ <- 今ここ
事前準備
まずは webpack が動作する環境を整えます。
$ npm init -y
$ npm install -g webpack webpack-dev-server webpack-cli
設定ファイルを下記の通り配置します
module.exports = {
mode: 'production',
};
HTMLファイルをデフォルト位置に配置します。
<!DOCTYPE html>
<html>
<head>
<title>Webpack Level Up</title>
</head>
<body>
<h1>Webpack Level Up</h1>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
CSSファイルをバンドルする
まずはCSSファイルをバンドルしてみましょう。下記のファイルを用意します。
h1 {
color: red;
}
次にこのCSSファイルをJSファイルの中で読み込みます。
import css from './main.css';
console.log(css);
このままwebpackを実行すると、下記のようなエラーが発生します。
$ webpack
Hash: 93310d27d1fdb082600f
Version: webpack 4.16.3
Time: 302ms
Built at: 2018-08-05 15:23:11
1 asset
Entrypoint main = main.js
[0] ./src/index.js 53 bytes {0} [built]
[1] ./src/main.css 157 bytes {0} [built] [failed] [1 error]
ERROR in ./src/main.css 1:5
Module parse failed: Unexpected token (1:5)
You may need an appropriate loader to handle this file type.
> body {
| color: red;
| }
@ ./src/index.js 1:0-20
CSSの文法で書かれたコードを読み込むことができていないようですね。当たり前です。
そこで、WebpackがCSSをJavaScriptに変換するためのloaderを導入します。
$ npm install --save-dev css-loader
インストールしたloaderを設定ファイルに追加します。
module.exports = {
mode: 'production',
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
],
},
};
module
を追加し、中に rules
という配列で利用するloaderを指定しています。
test
に正規表現オブジェクトを与え、ファイル名がmatchした場合に利用するloaderを use
で指定しています。
この状態で webpack-dev-server
を実行してみましょう。
$ webpack-dev-server --open # --open を指定することで規定のブラウザを開く
処理はエラーなしで完了しましたが、スタイルは適用されていませんね。
コンソールを見てみると、 css
という変数の中身の一部にCSSファイルの中身が含まれていることは分かります。

実は css-loader
は、CSSファイルを読み込み可能な形式に変換するだけで、これをstyleタグに生成し直すにはまた別のloaderが必要なのです。次にそちらもインストールしましょう。
$ npm install --save-dev style-loader
上記を設定ファイルに追加します。
module.exports = {
mode: 'production',
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
],
},
};
use
の部分を配列にして、 style-loader
, css-loader
の順で指定しています。これは「 css-loader
で読み込み可能にしたCSSファイルの内容を用いて style-loader
でstyleタグに生成し直す」という処理を指定していることになります。
この状態で webpack-dev-server
を再度実行してみます。
$ webpack-dev-server --open # --open を指定することで規定のブラウザを開く

無事にスタイルが適用されていますね!

Chrome Developer ToolでElementsを見てみると、headerの中にstyleタグが挿入されていることが分かります。この部分が style-loader
の仕事であるわけですね。
最後に、 src/index.js
内でCSSを import
する箇所ですが、特に変数に代入する必要は無いので下記のように修正します。
import './main.css';
console.log('Hello webpack.');
CSSファイルをWebpackで扱うためには、ここまでで十分です。とても簡単ですね!
SCSSファイルをバンドルする
さて、Webpackの便利ポイントの中でも特に役割が大きいものがトランスパイラの導入です。今回はSCSSファイルをCSSファイルにトランスパイル後、それをstyleタグとしてDOM内に挿入するまでを試してみましょう。
まずは src/main.css
を src/main.scss
に変更します。
$color: blue;
h1 {
color: $color;
}
必要な loader をインストールします。
$ npm install --save-dev sass-loader node-sass
node-sass
は sass-loader
が依存しているライブラリで、インストールされていないとトランスパイル中に Error: Cannot find module 'node-sass'
というエラーが発生します。
次に webpack.config.js
を更新します。
module.exports = {
mode: 'production',
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }, // 追加
],
},
};
.scss
で終わるファイルを読み込む場合は sass-loader
でCSS形式で変換し、 css-loader
でJavaScriptが読み込み可能な状態に変換し、最後に style-loader
でstyleタグを生成してDOM内に挿入するという処理を設定しています。
実際に変換できるかどうか確認してみましょう。
$ webpack-dev-server --open
無事に変数 $color
が展開され、文字色が青になっていることが確認できます。
レンダリングされたDOMを見ても、無事に color: blue;
が反映されていることを確認できます。
画像ファイルをバンドルする
ここまでで loader 追加の基本的な流れは分かりましたね。あとは同じ要領になりますが、次は画像ファイルを Base64 形式で読み込む url-loader
を追加してみましょう。まずはライブラリをインストールします。
$ npm install --save-dev url-loader
設定ファイルに画像ファイルの扱いを追加します。
module.exports = {
mode: 'production',
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.(gif|png|jpe?g|)$/, use: 'url-loader' }, // 追加
],
},
};
src/assets/
ディレクトリに適当な画像ファイルを追加して src/index.js
で読み込みます。
import url from './assets/matchlab.png';
const img = document.createElement('img');
img.src = url;
document.body.appendChild(img);
では webpack-dev-server
を起動してみましょう。
$ webpack-dev-server --open
Chrome Developer ToolでElementsを見てみましょう。

画像ファイルが無事に Base64 形式にエンコードされて img タグに収まっていることが確認できます。
Base64 エンコードではなくファイルパスとして読み込む設定も可能です。その場合は url-loader
の代わりに file-loader
を使います。
$ npm install --save-dev file-loader
module.exports = {
mode: 'production',
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.(gif|png|jpe?g|)$/, use: 'file-loader' }, // 変更
],
},
};
$ webpack-dev-server --open

ファイル名が ハッシュ値 + 拡張子
となってロードされ、そのパスから画像ファイルがサーブされていることが分かります。
ちなみに webpack.config.js
内で設定を書き足すことでファイル名の生成規則を操作できます。
module.exports = {
mode: 'production',
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.(gif|png|jpe?g|)$/, use: 'file-loader?name=assets/[name]-[hash].[ext]' }, // 変更
],
},
};
?name=xxx
のように、クエリパラメータの形式で設定を付け足すことで操作できます。 [name]
が元のファイル名、 [hash]
がハッシュ値、 [ext]
が拡張子を表します。
$ webpack-dev-server --open

無事に assets/[ファイル名]-[ハッシュ値].[拡張子]
形式に変換されていることが分かります。
webpack-dev-server
ではなく webpack
を実行すると、 dist/
以下に assets/image1-xxxxxxxxxx.png
のようなファイル名で画像ファイルがコピーされます。もちろんバンドル結果の main.js
内のパスも同じハッシュ値になっているので、デプロイ時は dist/
以下をそのままの構成でサーバーにアップロードすればデプロイが完了するという優れものですね。
本番環境サーバーの設定やデプロイフローとセットで考える
HTMLファイルやJSファイル、画像ファイルといった静的ファイルは、本番ではCDNを介して配信することが多いと思います。
その場合よく問題になるのがデプロイ時のキャッシュクリア(invalidation)です。webpackはこの問題に非常に良く対応してくれています。
例えば file-loader
を用いてファイル名にハッシュ値を付与する場合、ビルドの度にハッシュ値が変わるので画像ファイル側のキャッシュクリアの手間が不要になります。
url-loader
を使う場合も、画像ファイルは全てJSファイルの中にインクルードされているわけですから、キャッシュクリアを意識する必要はありません。
両者ともに画像ファイルのパスを指定したJSファイルだけキャッシュクリアすれば十分というわけです。これは便利ですね。
使い分けですが、個人的には、LP制作など1枚のHTMLファイルを作る場合は url-loader
、SPAなど本格的なフロントエンド開発の場合は file-loader
を使うことが多いような印象がありますね。やはりLPの表示には速度が求められるため、極限までリクエスト回数を減らそうとするということなのでしょうか。
TypeScriptファイルをトランスパイルしてバンドルする
さて、TypeScript等のトランスパイルが必要な言語も、webpackのloaderがあれば簡単に導入できます。まずは必要なライブラリをインストールします。
$ npm install --save-dev typescript ts-loader
TypeScriptには tsconfig.json
が必要なので、とりあえず動かすために空の設定ファイルをプロジェクトルートに追加します。
{}
webpack.config.js
にTypeScript用の設定を追加します。
module.exports = {
mode: 'production',
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.(gif|png|jpe?g|)$/, use: 'file-loader?name=assets/[name]-[hash].[ext]' },
{ test: /\.ts$/, use: 'ts-loader' }, // 追加
],
},
};
バンドル対象のTypeScriptファイルを追加します。
export default class User {
name: string;
constructor(name) {
this.name = name;
}
sayHi(): void {
console.log('Hi, I am ' + this.name);
}
}
src/index.js
内で上記のクラスを読み込みます。
import User from './user.ts';
const user = new User('Tom');
user.sayHi();
webpack-dev-server
を実行してみます。
$ webpack-dev-server --open

無事にTypeScriptがトランスパイルされ、 user.sayHi()
が実行されていることが分かります。
なお、エントリーポイントのJSファイルから部分的にTSファイルを読み込むといういびつな構成になってしまっていますが、もちろん webpack.config.js
を修正してエントリーポイントから全てTSファイルにすることは可能です。
module.exports = {
mode: 'production',
entry: './src/index.ts', // 追加
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.(gif|png|jpe?g|)$/, use: 'file-loader?name=assets/[name]-[hash].[ext]' },
{ test: /\.ts$/, use: 'ts-loader' },
],
},
};
import User from './user.ts';
const user: User = new User('Tom');
user.sayHi();
こんな感じですね。
設定ファイルの書き方のバリエーション
webpack.config.js
の書き方は実は色々あって、同じ内容でも異なる書き方がいくつかあって混乱することがあります。loader関連でよくある別の書き方を1つ例として挙げます。
module.exports = {
mode: 'production',
module: {
rules: [
{ test: /\.css$/, use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
] },
{ test: /\.scss$/, use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader' },
] },
{ test: /\.(gif|png|jpe?g|)$/, use: {
loader: 'file-loader',
options: {
name: 'assets/[name]-[hash].[ext]',
},
} },
],
},
};
use: 'xxx-loader'
という書き方は、実は use: { loader: 'xxx-loader' }
の省略形だったりします。
また、 use: 'xxx-loader?key=value'
のようなオプションの書き方も、本来的には options: { key: 'value' }
のような書き方があります。
この辺は説明するにも細かすぎるので、気になる方は 公式ドキュメント の参照をオススメします。
さて、loaderの設定についてはここまでにしましょう。今回利用したソースコードは こちら です。
次回はwebpackに様々な便利機能を提供してくれるpluginについてです!お楽しみに!