ウェブのフロントエンドエンジニア開発で人気を集めるモジュールバンドラーのwebpack(ウェブパック)。webpackにはJavaScriptファイルのバンドルだけではなく、スタイルシート(CSSやSass)のバンドルもできます。ICS MEDIAの記事「最新版で学ぶwebpack 4入門」では、webpackの基本的な使い方を解説しましたが、この記事ではスタイルシートに焦点をあてて解説します。

※webpackを利用するには事前にNode.jsをインストールしておいてください。この記事では2018年3月現在最新のNode.js v9.7、npm 5.6と、webpack 4.1をもとに解説しています。

この記事で説明していること

CSSをバンドルする利点

webpackでは、JavaScriptだけではなくスタイルシート、画像、ウェブフォント等あらゆるリソースをモジュールとして取り扱えます。JavaScriptファイル以外のファイルを扱うには、webpackのLoader機能を用います。

さまざまなファイルをバンドルする利点は読み込みの高速化が挙げられます。HTTP/1.1接続ではブラウザとウェブサーバーの同時接続数が限られるため、複数のファイルの転送に時間がかかります。複数のJSファイルを一つにまとめてしまうことが一般的な解決案として知られています。

手前ミソですが、webpack 4を利用して制作したウェブサイト「CreateJS入門サイト」を紹介します。スタイルシートで画像もバンドルしているため、リクエスト数が少なくなりページの表示高速に役立ちました。ブラウザキャッシュのない状態でも300ミリ秒をきるほど高速に読み込みできています。

webpack+CSSの構成を作成しよう

CSSファイルの読み込み例を紹介します。サンプルは次のリンクにあるので、手順がわからなければ参照ください。

モジュールのインストール

前回の記事「最新版で学ぶwebpack 4入門」を済まし、プロジェクトフォルダーでコマンド「npm init -y」を打ち込んだ状態から解説をはじめます。

CSSの読み込みに必要なローダーは、Style LoaderCSS Loaderなので、この2つのプラグインをプロジェクトフォルダーにインストールします。コマンドラインで次のコマンドを入力ください。

npm i -D webpack webpack-cli style-loader css-loader

設定ファイル

続いて、webpack.config.jsファイルに次の設定を追記します。本来は細かいオプションを設定できますが、最低限の内容としては次のコードとなります。CSSのバンドルには「style-loader」と「css-loader」の二種類が必要だと覚えてください。

▼webpack.config.js (簡略版)

module.exports = {
  // モード値を production に設定すると最適化された状態で、
  // development に設定するとソースマップ有効でJSファイルが出力される
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.css/,
        use: [
          'style-loader',
          {loader: 'css-loader', options: {url: false}},
        ],
      },
    ]
  }
};

拡張子が.cssのファイルに対して、use配列で指定したLoaderが後ろから順番に適用されます。処理を図示すると次のような流れになります。

  • style-loader: 動的にstyleタグが作られ、head要素内に差し込まれることでcssが適用されます
  • css-loader: css内のurl()メソッドや@import文をJavaScriptのrequire()メソッドに変換。file-loaderやurl-loaderと組み合わせて依存解決します

とりあえずwebpackを動かすために最小限の構成で説明しています。現場で使えるwebpack.config.jsファイルの書き方は後述します。

コンテンツ側のソースコード

srcフォルダー内にindex.jsstyle.cssの2つのファイルを格納します。index.jsファイルにはimport文を用いて、CSSファイルを読み込むように記述します。

▼src/index.js

// import 文を使ってstyle.cssファイルを読み込む。
import './style.css';

ビルド

npx webpackコマンドを使ってビルドすると、ビルドされたdist/main.jsの中にCSSコードが埋め込まれます。

npx webpack

▲ビルドコマンドによってmain.jsファイルにバンドルされる

HTML側でdist/main.jsファイルを読み込むと、スタイルが適用されます。JSファイルしか読み込んでいない状態ですが、main.jsファイルには自動的にCSSに展開される機能が入っているためです。

▲ビルドコマンドによってmain.jsファイルにバンドルされる

実戦向きのwebpack.config.jsファイル

制作の現場ではソースマップの出力は必須です。ソースマップとは変換前のコードの情報を残すことで開発時に役立てることができる機能です。変換前後ではコードの形が大きく変わってしまうため、元のコードの何行目が影響しているのかわかりやすくする効果があります。

webpack.config.jsファイルのコードが長くなりますが、慣れてきたら次のような設定をするといいでしょう。webpackの仕様として、mode値がdevelopmentであってもCSSのソースマップは自動的に有効にはなりません。逆に、mode値がproductionであってもCSSのソースマップは自動的に無効にはなりません。そのため、ソースマップの有効無効は自前の変数で管理しています

▼webpack.config.js (現場で利用する設定例)

// [定数] webpack の出力オプションを指定します
// 'production' か 'development' を指定
const MODE = 'development';

// ソースマップの利用有無(productionのときはソースマップを利用しない)
const enabledSourceMap = (MODE === 'development');

module.exports = {
  // モード値を production に設定すると最適化された状態で、
  // development に設定するとソースマップ有効でJSファイルが出力される
  mode: MODE,

  module: {
    rules: [
      // CSSファイルの読み込み
      {
        // 対象となるファイルの拡張子
        test: /\.css/,
        // ローダー名
        use: [
          // linkタグに出力する機能
          'style-loader',
          // CSSをバンドルするための機能
          {
            loader: 'css-loader',
            options: {
              // オプションでCSS内のurl()メソッドの取り込みを禁止する
              url: false,
              // CSSの空白文字を削除する
              minimize: true,
              // ソースマップを有効にする
              sourceMap: enabledSourceMap,
            },
          },
        ],
      },
    ]
  }
};

中間言語の変換 – webpack+Sassの構成を作成しよう

フロントエンド開発では、Sassなどの中間言語を使うことが多いでしょう。webpackではこれらの中間言語を変換できます。アンケートをしてみたところ、多くのCSSコーダーはSassを利用していることがわかりました。

SassのSCSSファイルを読み込む手順を解説します。サンプルは次のリンクにあるので、手順がわからなければ参照ください。

▲これから説明するサンプル。ビルドコマンドによってmain.jsファイルにバンドルする

モジュールのインストール

Sassの読み込みに必要なローダーは、sass-loaderです。また、Sass LoaderはSassのコンパイル用モジュールnode-sassに依存しているので、あわせてインストールします。コマンドラインで次のコマンドを入力します。

npm i -D webpack webpack-cli sass-loader node-sass style-loader css-loader

設定ファイル

続いて、webpack.config.jsファイルに次の設定を追記します。

▼webpack.config.jsファイル

// [定数] webpack の出力オプションを指定します
// 'production' か 'development' を指定
const MODE = 'development';

// ソースマップの利用有無(productionのときはソースマップを利用しない)
const enabledSourceMap = (MODE === 'development');

module.exports = {
  // モード値を production に設定すると最適化された状態で、
  // development に設定するとソースマップ有効でJSファイルが出力される
  mode: MODE,

  module: {
    rules: [
      // Sassファイルの読み込みとコンパイル
      {
        test: /\.scss/, // 対象となるファイルの拡張子
        use: [
          // linkタグに出力する機能
          'style-loader',
          // CSSをバンドルするための機能
          {
            loader: 'css-loader',
            options: {
              // オプションでCSS内のurl()メソッドの取り込みを禁止する
              url: false,
              // ソースマップの利用有無
              sourceMap: enabledSourceMap,

              // 0 => no loaders (default);
              // 1 => postcss-loader;
              // 2 => postcss-loader, sass-loader
              importLoaders: 2
            },
          },
          {
            loader: 'sass-loader',
            options: {
              // ソースマップの利用有無
              sourceMap: enabledSourceMap,
            }
          }
        ],
      },
    ],
  },
};

拡張子が.scssのファイルに対して、use配列で指定したLoaderが後ろから順番に適用されます。処理を図示すると次のような流れになります。

コンテンツ側のソースコード

ローダーを用いてSassを読み込むには、エントリーポイント内でimport文を用いて次のように記述します。srcフォルダー内にindex.jsファイルとstyle.cssファイルを格納しているとして解説します。

▼src/index.js

// import文を使ってSassファイルを読み込む。
import './style.scss';

ビルド

webpackコマンドを使ってビルドすると、webpackはSassをコンパイルします。

npx webpack

▲ビルドコマンドによってmain.jsファイルにバンドルされる。このサンプルでは、画像は取り込んでいないのがポイント。

Sass内の画像もバンドルする方法

webpackでは画像もJSファイルにバンドルできます。画像はBase64文字列として変換され、コンパイル後のJSファイルに含まれます。何でもかんでもJSファイルにするのは違和感があるかもしれませんが、HTTP/1.1環境下でリクエスト数を極限まで下げるには一つの最適解だと思います。

サンプルは次のリンクにあるので、手順がわからなければ参照ください。

▲これから説明するサンプル。ビルドコマンドによってmain.jsファイルにバンドルする

モジュールのインストール

基本的には先述のSassの手順と同じですが、url-loaderモジュールを追加でインストールします。

npm i -D webpack webpack-cli sass-loader node-sass style-loader css-loader url-loader

設定ファイル

その上で、webpackの設定ファイルには次のように指定します。JPGやPNGなどの画像形式の場合にurl-loaderが適用されるように指定しています。

▼webpack.config.js

// [定数] webpack の出力オプションを指定します
// 'production' か 'development' を指定
const MODE = 'development';

// ソースマップの利用有無(productionのときはソースマップを利用しない)
const enabledSourceMap = (MODE === 'development');

module.exports = {
  // モード値を production に設定すると最適化された状態で、
  // development に設定するとソースマップ有効でJSファイルが出力される
  mode: MODE,

  module: {
    rules: [
      // Sassファイルの読み込みとコンパイル
      {
        test: /\.scss/, // 対象となるファイルの拡張子
        // ローダー名
        use: [
          // linkタグに出力する機能
          'style-loader',
          // CSSをバンドルするための機能
          {
            loader: 'css-loader',
            options: {
              // オプションでCSS内のurl()メソッドを取り込む
              url: true,
              // ソースマップの利用有無
              sourceMap: enabledSourceMap,

              // 0 => no loaders (default);
              // 1 => postcss-loader;
              // 2 => postcss-loader, sass-loader
              importLoaders: 2
            },
          },
          // Sassをバンドルするための機能
          {
            loader: 'sass-loader',
            options: {
              // ソースマップの利用有無
              sourceMap: enabledSourceMap,
            }
          }
        ],
      },
      {
        // 対象となるファイルの拡張子
        test: /\.(gif|png|jpg|eot|wof|woff|woff2|ttf|svg)$/,
        // 画像をBase64として取り込む
        loader: 'url-loader'
      }
    ],
  },
};

拡張子が.scssのなかでURL参照をしている画像ファイルがあれば、url-loaderが適用されます。処理を図示すると次のような流れになります。

エントリーポイントのsrc/index.jsの内容は前述のSassのサンプルと同じです。

ビルド

webpackコマンドを使ってビルドすると、webpackはSassをコンパイルします。すると、画像も取り込まれてdist/main.jsファイルが生成されます。

npx webpack

▲ビルドコマンドによってmain.jsファイルにバンドルされる。このサンプルでは、画像も取り込んでいるのがポイント。

画像をバンドルしない場合/一部だけバンドルする場合

CSSの場合、画像の取り扱いがポイントです。url-loaderだと一括で全てのファイルを埋め込みますが、逆に画像を埋め込みたくないという方もいるでしょう。そのやり方を「file-loaderのサンプル」にまとめてますので参照ください。file-loaderを使うのがポイントです。

容量の閾値でバンドル有無を制御する方法

高度なテクニックとして、一定のサイズまでは画像をバンドルして、閾値を超えた場合は埋め込まず外部参照にするといった柔軟な設定もできます。

▲画像ファイルの容量で埋め込み有無を分岐することも可能

JavaScriptに画像をバンドルすると①JSファイルの評価時間の増大 ②本来のファイル容量に比べてBase64化することで1.33倍に大きくなるといったデメリットがあります。ただし、サーバーリクエスト処理のコストに比べて①②のほうが小さい場合もあります。容量の大小で制御するのは一つの現実解でしょう。その設定方法は「url-loaderでlimitを設定したサンプル」にまとめたので参照ください。

JS内にCSSをバンドルしたくない場合

JavaScript内にバンドルすることがwebpackの利点であると紹介してきましたが、一般的なウェブサイト制作ではcssファイルを用意してstyleタグで読ませる場合が多いでしょう。

<!-- スタイルシートを読み込む -->
<link rel="stylesheet" href="style.css" />

<!-- JavaScriptを読み込む -->
<script src="main.js"></script>

▲一般的なスタイルシートの読み込み方

webpackのExtractTextPluginエキストラクト・テキスト・プラグインを使うと、それが実現できます。このプラグインはバンドルしたJavaScriptから任意のテキスト(スタイルシートの部分)を別ファイルとして(CSSファイルとして)出力できます。詳しくはサンプルファイルを用意したので、次のリンクを参照ください。

2018年3月20日時点では、「extract-text-webpack-plugin」をwebpack 4で利用しようとするとエラーが起きます。そのため、webpack 4に対応したバージョンの「extract-text-webpack-plugin@next」をnpmでインストールするようにしてください。モジュール名の末尾の「@next」の部分が大事です。上記のサンプルは「extract-text-webpack-plugin@next」で解説しています。

PostCSS(Autoprefixer)のバンドル方法

スタイルシートに関してはベンダープレフィックスを付与するのが一般的でしょう。Android 4.4やIE 11への対応を考慮すると、ベンダープレフィックスを付与するのが安全です。webpack単体ではその機能は存在しないので、PostCSSの機能を読み込んで実現します。AutoprefixerはPostCSSの中の一つのモジュールであるため、利用するにはPostCSSとAutoprefixerを両方インストールする必要があります。

サンプルは次のリンクにあるので、手順がわからなければ参照ください。

モジュールのインストール

コマンドラインで次のコマンドを入力ください。似たような名前のモジュールが多くて不安になりますが、最低限必要なものです。

npm i -D webpack webpack-cli style-loader css-loader sass-loader node-sass postcss-loader autoprefixer

インストールしたら、package.jsonサンプルのファイルと同じような内容となるはずです。

設定ファイル

続いて、webpack.config.jsファイルに次の設定を追記します。ソースマップを有効にしています。

module.exports = {
  // モード値を production に設定すると最適化された状態で、
  // development に設定するとソースマップ有効でJSファイルが出力される
  mode: 'production',
  // source-map 方式でないと、CSSの元ソースが追跡できないため
  devtool: 'source-map',
  module: {
    rules: [
      {
        // 対象となるファイルの拡張子
        test: /\.scss/,
        // Sassファイルの読み込みとコンパイル
        use: [
          // linkタグに出力する機能
          'style-loader',
          // CSSをバンドルするための機能
          {
            loader: 'css-loader',
            options: {
              // CSS内のurl()メソッドの取り込みを禁止する
              url: false,
              // ソースマップの利用有無
              sourceMap: true,
              // 空白文字やコメントを削除する
              minimize: true,
              // Sass+PostCSSの場合は2を指定
              importLoaders: 2
            },
          },
          // PostCSSのための設定
          {
            loader: 'postcss-loader',
            options: {
              // PostCSS側でもソースマップを有効にする
              sourceMap: true,
              plugins: [
                // Autoprefixerを有効化
                // ベンダープレフィックスを自動付与する
                require('autoprefixer')({grid: true})
              ]
            },
          },
          // Sassをバンドルするための機能
          {
            loader: 'sass-loader',
            options: {
              // ソースマップの利用有無
              sourceMap: true,
            }
          }
        ],
      },
    ],
  }
};

PostCSSの設定はpostcss-loader内のオプションで指定しています。以上で完成です。

コマンドラインを叩いて確認してみましょう。このコマンドで、dist/main.jsに書き出されます。

npx webpack

なお、PostCSSの人気のモジュールの一つにcssnanoというものがあります。minifyと呼ばれる空白文字を取り除くだけでなく、構造解析をしたうえで重複したCSSを整理してくれるモジュールです。cssnanoはインストールしなくても、css-loaderのminimizeオプションをtrueに設定すれば、自動的にcssnanoと同等の結果が得られます。

まとめ

JavaScriptだけではなくCSSもwebpackで一元管理すると、webpackの周辺技術をふんだんに使えるため開発が楽になります。例えば、CSSの編集で、webpack-dev-serverでローカルサーバーを立てれるのはとても便利です。webpackの設定ファイルははじめはややこしいかもしれませんが、コピペで動かして少しずつ理解していくといいでしょう。書き慣れてくると、痒いところまで手がとどくのでwebpackの柔軟性に利点を感じるようになってきます

ウェブ制作の現場では、CSSにベンダープレフィックスを付与することも多いでしょう。そのときにはAutoPrefixerなどPostCSSの技術を利用します。この記事は「Bootstrapをバンドルする方法」に続きます。

連載記事一覧