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-loader
は CSS を HTML の head タグ内にインラインスタイル (<style></style>
) を出力する loader
css-loader
The
css-loader
interprets@import
andurl()
likeimport/require()
and will resolve them. cf. GitHub - webpack-contrib/css-loader: CSS Loader
css-loader は機能が多いのですが、これも雑にまとめると
css-loader
は CSS を JS で扱える形に変換する loader
SCSS / SASS の変換 rules を見返すと…
// webpack.config.js module.exports = { // ... rules: [ test: /\.(scss|sass)$/, use: [ 'style-loader', 'css-loader', 'sass-loader', ] ], }
この設定は次のように変換をしていると読み取ることができました。
sass-loader
で scss / sass を css に変換scss-loader
で JS で読み込める形式に変換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 も大きくなるでしょうから、CSS が bundle.js
に含まれているとそれだけファイルサイズが大きくなり script の実行で処理がブロックされる時間が長くなりそうな気がします。(測定したわけではないのであくまでイメージですが…)
[参考]