かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

webpack css-loader と style-loader の違いについて学んだ

CSS を webpack で扱う時にセットで使いがちな css-loader と style-loader の違いをちゃんと理解してなかったので CSS Modules について考えるに当たって調べ直したのでメモ

webpack の loader の処理順

e.g. SCSS を webpack で扱う際の設定

// webpack.config.js
module.exports = {
  // ...
  rules: [
    test: /\.(scss|sass)$/,
    use: [
      'style-loader', 
      'css-loader',
      'sass-loader',
    ]
  ],
}

webpack の loader は下から順番に実行するので下記のような順で処理が実行される

sass-loader > scss-loader > style-loader

style-loader

Inject CSS into the DOM.
cf. GitHub - webpack-contrib/style-loader: Style Loader

webpack のページのサンプルを見るとわかりやすかったです。
雑にまとめると style-loaderCSS を HTML の head タグ内にインラインスタイル (<style></style> ) を出力する loader

css-loader

The css-loader interprets @import and url() like import/require() and will resolve them. cf. GitHub - webpack-contrib/css-loader: CSS Loader

css-loader は機能が多いのですが、これも雑にまとめると
css-loaderCSS を JS で扱える形に変換する loader

SCSS / SASS の変換 rules を見返すと…

// webpack.config.js
module.exports = {
  // ...
  rules: [
    test: /\.(scss|sass)$/,
    use: [
      'style-loader', 
      'css-loader',
      'sass-loader',
    ]
  ],
}

この設定は次のように変換をしていると読み取ることができました。

  1. sass-loader で scss / sass を css に変換
  2. scss-loader で JS で読み込める形式に変換
  3. style-loader で JS として読み込んだ CSS を HTML の にインラインスタイルとして挿入

style-loader をもう少し詳しく

style-loader は HTML の <head> 内にインラインスタイルを出力するけど、build しても CSS ファイルは作成されず bundle.js ファイルが作成されるだけです。

つまり、style-loader は css-loader で JS で扱える形にした CSS.js ファイルの中に持っておいて HTML が読み込まれ script が実行された時に <style> タグを生成して HTML の <head> に挿入するという処理を行うということのようです。

CSS ファイルとして出力したい場合は MiniCssExtractPlugin を使う

MiniCssExtractPlugin | webpack

This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.
cf. GitHub - webpack-contrib/mini-css-extract-plugin: Lightweight CSS extraction plugin

このプラグインを使えば CSS ファイルが生成され <link> で読み込まれるようになります。

// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  // ...
  rules: [
    test: /\.(scss|sass)$/,
    use: [
      MiniCssExtractPlugin.loader,
      'css-loader',
      'sass-loader', 
    ]
  ],
  plugins: [
    // plugins 内で出力されるファイル形式などのオプションを指定できる
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
    }),
  ],
}

この設定で build すると、CSS のファイルが生成されるようになりました!

webpack-dev-server を使う dev モードでも onMemory で CSS ファイルが作成され問題なく動作するので、 MiniCssExtractPlugin を使うのであれば style-loader は不要。

build した時だけ MiniCssExtractPlugin で CSS ファイルを生成したい場合

webpack の mode を利用して使用する loader を分岐させればOK!
コマンドから --mode development の様に送る場合は argv.mode で値を取得できる。

// webpack.modules.js
module.export = function (env, argv) {
  const mode = process.env.NODE_ENV || argv.mode || 'development';
  const isProduction = mode === 'production';

  return {
    //...
     module: {
      rules: [
        {
          test: /\.(scss|sass)$/,
          use: [
            !isProduction && 'style-loader',
            isProduction && MiniCssExtractPlugin.loader,
            'css-loader',
            'sass-loader', 
          ].filter(Boolean), // false が残るとエラーになるので filter(Boolean) で除去する
        }
      //...
  };
}

₍ ᐢ. ̫ .ᐢ ₎❤︎

まとめと感想

あいまいだった style-loader と css-loader の違いがクリアになった気がします。

  • インラインスタイルを JS から出力したい場合は style-loader
  • .css ファイルを生成して読み込ませたい場合は MiniCssExtractPlugin

という使い分けをすれば良さそうです。

create-react-app を eject して webpack.config.js を見てみると development の時は style-loader を使い production でビルドする際は MiniCssExtractPlugin を使うような設定になっていました。
Hot Reload で頻繁にファイルが変わる development では都度 css ファイルを生成しない style-loader の方が高速で、実際のプロダクトの状態では css ファイルを読み込ませる方がパフォーマンスが良いのからそうしているのかな〜と感じました。

個人的にも JS から <style> タグを生成するのは script でその他の処理がブロックされている状態で CSS が出力され script の実行を待ってレイアウトのレンダリングがされるというイメージです。
プロジェクトが大きくなると JS も CSS も大きくなるでしょうから、CSSbundle.js に含まれているとそれだけファイルサイズが大きくなり script の実行で処理がブロックされる時間が長くなりそうな気がします。(測定したわけではないのであくまでイメージですが…)


[参考]

速習 webpack 第2版 速習シリーズ

速習 webpack 第2版 速習シリーズ

毛穴撫子 お米のマスク 10枚入

毛穴撫子 お米のマスク 10枚入

  • メディア: ヘルスケア&ケア用品