LoginSignup
19
16

More than 5 years have passed since last update.

Webpack Level 3: loaderを追加してjs以外のファイル以外を結合する

Last updated at Posted at 2018-08-11

こんにちは!高野です。
Webpackレベルアップシリーズの第3弾はloaderです。これは .js ファイル以外も最終的に .js ファイルとして結合するためのパーサーのような機能を提供するもので、今やWebpackを利用する主目的と言っても過言ではありません。
loaderの設定方法を理解して、CSSファイルでも画像ファイルでもTypeScriptファイルでも、何でもバンドルできるようになってしまいましょう!

コンテンツ

事前準備

まずは webpack が動作する環境を整えます。

$ npm init -y
$ npm install -g webpack webpack-dev-server webpack-cli

設定ファイルを下記の通り配置します

webpack.config.js
module.exports = {
  mode: 'production',
};

HTMLファイルをデフォルト位置に配置します。

index.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ファイルをバンドルしてみましょう。下記のファイルを用意します。

src/main.css
h1 {
  color: red;
}

次にこのCSSファイルをJSファイルの中で読み込みます。

src/index.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を設定ファイルに追加します。

webpack.config.js
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ファイルの中身が含まれていることは分かります。

Webpack_Level_Up.png

実は css-loader は、CSSファイルを読み込み可能な形式に変換するだけで、これをstyleタグに生成し直すにはまた別のloaderが必要なのです。次にそちらもインストールしましょう。

$ npm install --save-dev style-loader

上記を設定ファイルに追加します。

webpack.config.js
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 を指定することで規定のブラウザを開く

Webpack_Level_Up.png

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

Webpack_Level_Up.png

Chrome Developer ToolでElementsを見てみると、headerの中にstyleタグが挿入されていることが分かります。この部分が style-loader の仕事であるわけですね。

最後に、 src/index.js 内でCSSを import する箇所ですが、特に変数に代入する必要は無いので下記のように修正します。

src/index.js
import './main.css';

console.log('Hello webpack.');

CSSファイルをWebpackで扱うためには、ここまでで十分です。とても簡単ですね!

SCSSファイルをバンドルする

さて、Webpackの便利ポイントの中でも特に役割が大きいものがトランスパイラの導入です。今回はSCSSファイルをCSSファイルにトランスパイル後、それをstyleタグとしてDOM内に挿入するまでを試してみましょう。

まずは src/main.csssrc/main.scss に変更します。

src/main.scss
$color: blue;

h1 {
  color: $color;
}

必要な loader をインストールします。

$ npm install --save-dev sass-loader node-sass

node-sasssass-loader が依存しているライブラリで、インストールされていないとトランスパイル中に Error: Cannot find module 'node-sass' というエラーが発生します。

次に webpack.config.js を更新します。

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

Webpack_Level_Up.png

無事に変数 $color が展開され、文字色が青になっていることが確認できます。

Webpack_Level_Up.png

レンダリングされたDOMを見ても、無事に color: blue; が反映されていることを確認できます。

画像ファイルをバンドルする

ここまでで loader 追加の基本的な流れは分かりましたね。あとは同じ要領になりますが、次は画像ファイルを Base64 形式で読み込む url-loader を追加してみましょう。まずはライブラリをインストールします。

$ npm install --save-dev url-loader

設定ファイルに画像ファイルの扱いを追加します。

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: 'url-loader' }, // 追加
    ],
  },
};

src/assets/ ディレクトリに適当な画像ファイルを追加して src/index.js で読み込みます。

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を見てみましょう。

Webpack_Level_Up.png

画像ファイルが無事に Base64 形式にエンコードされて img タグに収まっていることが確認できます。

Base64 エンコードではなくファイルパスとして読み込む設定も可能です。その場合は url-loader の代わりに file-loader を使います。

$ npm install --save-dev file-loader
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' }, // 変更
    ],
  },
};
$ webpack-dev-server --open

Webpack_Level_Up.png

ファイル名が ハッシュ値 + 拡張子 となってロードされ、そのパスから画像ファイルがサーブされていることが分かります。
ちなみに webpack.config.js 内で設定を書き足すことでファイル名の生成規則を操作できます。

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

Webpack_Level_Up.png

無事に 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 が必要なので、とりあえず動かすために空の設定ファイルをプロジェクトルートに追加します。

tsconfig.json
{}

webpack.config.js にTypeScript用の設定を追加します。

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]' },
      { test: /\.ts$/, use: 'ts-loader' }, // 追加
    ],
  },
};

バンドル対象のTypeScriptファイルを追加します。

src/user.ts
export default class User {
  name: string;

  constructor(name) {
    this.name = name;
  }

  sayHi(): void {
    console.log('Hi, I am ' + this.name);
  }
}

src/index.js 内で上記のクラスを読み込みます。

src/index.js
import User from './user.ts';

const user = new User('Tom');

user.sayHi();

webpack-dev-server を実行してみます。

$ webpack-dev-server --open

Webpack_Level_Up.png

無事にTypeScriptがトランスパイルされ、 user.sayHi() が実行されていることが分かります。
なお、エントリーポイントのJSファイルから部分的にTSファイルを読み込むといういびつな構成になってしまっていますが、もちろん webpack.config.js を修正してエントリーポイントから全てTSファイルにすることは可能です。

webpack.config.js
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' },
    ],
  },
};
src/index.ts
import User from './user.ts';

const user: User = new User('Tom');

user.sayHi();

こんな感じですね。

設定ファイルの書き方のバリエーション

webpack.config.js の書き方は実は色々あって、同じ内容でも異なる書き方がいくつかあって混乱することがあります。loader関連でよくある別の書き方を1つ例として挙げます。

webpack.config.js
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についてです!お楽しみに!

19
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
16