kqito's 技術ブログ

技術やプログラミングについて思ったことを呟くブログ

Webpackのcss系のloader周りを調べてみた

はじめに

Webpackを設定する中でcssファイルに対するloader(css-loaderとかstyle-loaderraw-loaderなど)がよく利用されています。

しかし、それぞれの役割、挙動が自分の中であやふやだったのでそれぞれ調べた時の結果などをブログにまとめました。

loader

css-loader

一言で言えばurlやimport/require()の解決を行いオブジェクトとして生成するloader。

まず最初に以下のようなwebpack設定ファイルで*.cssファイルがどのように解決されるかを調べます。

(module部分のみ記述) (ts-loaderを用いてトランスパイルしていますが、省略)

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        {
          loader: "css-loader",
          options: {
            modules: true,
          },
        },
      ],
    },
  ],
}

そしてこのようなファイルを用意してwebpackでバンドルさせます。

import style from "./index.module.css";

console.log(style);
.test {
  color: blue;
}

これらの出力は以下のようになります。

0: (4) ["./src/index.module.css", "._34TPoweJRFabqRaBcU6yIt {↵  color: blue;↵}↵", "", {…}]
i: ƒ (modules, mediaQuery, dedupe)
locals: {test: "_34TPoweJRFabqRaBcU6yIt"}
toString: ƒ toString()

css-loaderによって生成されたこのオブジェクトは様々な用途で利用されます。

例えば

webpack.js.org

公式のGetting Startedでも例があるようにcss-loaderstyle-loaderなどといった様々なloaderと併用されることが多そうです。

style-loader

一言で言えば、cssをdomに注入する役割を持つloader。

上記で述べたcss-loaderと併用した以下のようなwebpack設定ファイルを用意します。

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        {
          loader: "style-loader"
        },
        {
          loader: "css-loader",
          options: {
            modules: true,
          },
        },
      ],
    },
  ],
}

加えて適当にreact + typescriptでクリックするとalert()が実行されるindex.tsxファイルとcssファイルを用意します

import React from "react";
import ReactDom from "react-dom";

import style from "./index.module.css";

type AlertButtonProps = {
  message: string;
};

const AlertButton: React.FC<AlertButtonProps> = ({ message }) => (
  <button className={style.test} type="button" onClick={() => alert(message)}>
    Let's alert
  </button>
);

ReactDom.render(<AlertButton message="hi" />, document.getElementById("app"));
.test {
  color: blue;
}

これを実行すると表示されたAlertButtonにハッシュされた_34TPoweJRFabqRaBcU6yItのようなクラスが付与&styleタグの生成が行われ、CSS-Moduleが実現できます。

CSS-Module意外にも普通にcssを生成する方法もあったり、

style-loaderのoptions.injectTypeを'linkTag'に設定し、file-loaderと併用することでlinkタグによるスタイルの適用を行なったりすることもできます。

raw-loader

一言で言うと、ファイルを文字列としてimportできるようにするloaderです。

webpack.js.org

公式ではtxtファイルを文字列として読み取る例が用意されています。

raw-loadercssファイルを文字列として読み込み & style-loaderでDOMに反映と言う流れでも上記と同じことができるようです。

ただ、文字列として読み取っているだけなのでCSS-Moduleなどは当然無理です。

{
  test: /\.css$/,
  use: [
    {
      loader: "style-loader",
    },
    {
      loader: "raw-loader",
    },
  ],
}

mini-css-extract-plugin

一言で言えば、cssをそれぞれのファイルに生成するプラグイン

下記のようなwebpack設定フィアルでビルドすると今までの処理結果とは異なりcssが別で生成され、linkタグにて読み込まれるようになります。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
{
  test: /\.css$/,
  use: [
    {
      loader: MiniCssExtractPlugin.loader,
    },
    {
      loader: "css-loader"
    },
  ],
}

react-scriptsのwebpack.config.jsでは以下のような*.cssファイルに対する設定が行われています。(一部抜粋)

    const loaders = [
      isEnvDevelopment && require.resolve('style-loader'),
      isEnvProduction && {
        loader: MiniCssExtractPlugin.loader,
        // css is located in `static/css`, use '../../' to locate index.html folder
        // in production `paths.publicUrlOrPath` can be a relative path
        options: paths.publicUrlOrPath.startsWith('.')
          ? { publicPath: '../../' }
          : {},
      },
      {
        loader: require.resolve('css-loader'),
        options: cssOptions,
      }

develop環境だとstyle-loaderを利用、production環境だとmini-css-extract-pluginを利用しています。

ここはおそらくパフォーマンスを考慮して切り替えていると思われるのですが、今後自分の方で試してみたいと思います。

まとめ

webpackのcss周りについての設定の理解を多少深めることができたと思います。

これはそもそもCSS-Moduleにおけるモジュールの構築順が書き換わってリファクタリングしたときに意図せぬスタイルが適用されたりするので純粋なBEMの方がよさそう...」のような記事をみたのがきっかけでした。

CSS-in-JSなのかCSS-Moduleなのか、手段は多くあるのでそれぞれ理解を深めていきたいです。

以上です。