◆

Webpack 打包样式资源:style-loader 与 css-loader

2023-02-02 01:370

打包样式资源

style-loader

把 CSS 插入到 DOM 中。

css-loader

对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样。

  • url: 启用/禁用 url() 解析。

    • 如果设置为 false,css-loader 将不会解析 url 或者 image-set 中的任何路径。
  • import:启用/禁用 @import 规则进行处理。

  • sourceMap:启用/禁用生成 SourceMap

  • importLoaders:指定在 css-loader 之前还要经过几个 loader 处理 @import 引入的文件。

这个选项是最容易配错的一个。它的含义不是「应用几个 loader」,而是「@import 进来的那个文件,需要回头再走几个前置 loader」。

问题的来源在于:css-loader 处理 @import 时是自己解析、自己内联的,它并不知道被引入的文件原本也需要 sass-loader 或 postcss-loader 处理。所以 a.scss 里 @import './b.scss',b.scss 的内容会被 css-loader 直接塞进来,其中的 Sass 语法就没人编译了。importLoaders: 2 告诉 css-loader:把 b.scss 交回给我前面的 2 个 loader(postcss-loader 和 sass-loader)先处理。

数字怎么数:从 css-loader 往后(也就是配置数组里往下)数几个。

{
  test: /\.scss$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      // 后面还有 postcss-loader 和 sass-loader 两个,所以是 2
      options: { importLoaders: 2 },
    },
    'postcss-loader',
    'sass-loader',
  ],
}

只有 postcss-loader 时写 1,纯 css-loader 时写 0(默认值)。

实践中的替代做法是改用 @use / @import 让 Sass 自己处理引入——Sass 编译阶段就把文件合并了,轮到 css-loader 时已经是一整份 CSS,没有 @import 需要它解析。所以 SCSS 项目里这个选项其实很少真正起作用,真正需要它的是原生 CSS 里的 @import 配 PostCSS 的场景。

loader 的执行顺序

use 数组是从右到左、从下到上执行的。上面那份配置的实际流程:

b.scss / a.scss
  → sass-loader     编译 Sass 语法,产出 CSS 字符串
  → postcss-loader  跑 autoprefixer 等插件
  → css-loader      解析 @import / url(),产出一个 JS 模块
  → style-loader    产出往 document 插 <style> 的 JS 代码
  → 打进 bundle

理解这个顺序的关键是:每个 loader 的输入是后一个 loader 的输出。css-loader 拿到的必须已经是合法 CSS,所以 sass-loader 必须排在它后面。顺序写反了会报一堆 Unexpected character '@' 之类的语法错误。

style-loader 做了什么

它把 CSS 变成一段运行时 JS:

// style-loader 产出的代码大意
const style = document.createElement('style');
style.textContent = '.app{color:red}';
document.head.appendChild(style);

也就是说 CSS 最终是打进 JS bundle 里、运行时才插入 DOM 的。这带来两个后果:

  • 好处是支持 HMR,改 CSS 不刷新页面就能生效。
  • 坏处是 CSS 要等 JS 下载执行完才生效,首屏会有一小段无样式内容闪烁(FOUC);而且 JS 体积变大,CSS 无法被浏览器并行下载和独立缓存。

所以生产环境应该换成 mini-css-extract-plugin,把 CSS 抽成独立的 .css 文件:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 开发用 style-loader(有 HMR),生产用抽取
          isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css' }),
  ],
};

注意 MiniCssExtractPlugin.loader 和 style-loader 是互斥的,同时用会两份样式都生效。

css-loader 的 modules 选项

原文没提但这是 css-loader 最常用的能力之一——CSS Modules,通过类名哈希做样式隔离:

{
  loader: 'css-loader',
  options: {
    modules: {
      // 生成的类名格式,生产环境常用纯 hash
      localIdentName: '[name]__[local]--[hash:base64:5]',
      // 导出的 key 转成驼峰,JS 里可以写 styles.myClass
      exportLocalsConvention: 'camelCase',
      // 只对 .module.css 结尾的文件启用
      auto: /\.module\.\w+$/i,
    },
  },
}
// 用法
import styles from './App.module.css';
// styles.title 是哈希后的类名,如 'App__title--a1b2c'
element.className = styles.title;

auto 这个选项很有用,配一次就能让 .module.css 走模块化、普通 .css 走全局,不用为两者写两条 rule。

url() 解析的坑

css-loader 会把 url(./img/a.png) 当成一个模块依赖去解析,这意味着路径必须是相对当前 CSS 文件的、真实存在的文件。两种情况会出问题:

/* 会被解析,文件不存在就报 Module not found */
background: url('./img/a.png');

/* 绝对路径和以 / 开头的路径默认不解析,原样输出 */
background: url('/static/a.png');

/* 想让某个 url 不被处理,用 ~ 或配 url.filter */
background: url('~some-pkg/img/a.png'); /* ~ 表示从 node_modules 解析 */

运行时才拼出来的路径(比如 url(var(--icon)))css-loader 处理不了,这类资源要放到 public 目录走绝对路径。需要豁免某些路径时用 url.filter:

{
  loader: 'css-loader',
  options: {
    url: {
      filter: (url) => !url.startsWith('/static/'),
    },
  },
}

webpack 5 里图片资源不需要 loader 了

顺带提一句版本差异:webpack 4 时代处理 url() 引入的图片还要配 file-loader / url-loader,webpack 5 内置了 Asset Modules,直接用 type 声明就行:

{
  test: /\.(png|jpe?g|gif|svg)$/i,
  type: 'asset',
  parser: {
    // 小于 8kb 转 base64 内联,大于则输出文件
    dataUrlCondition: { maxSize: 8 * 1024 },
  },
}

type: 'asset' 对应旧的 url-loader + limit,'asset/resource' 对应 file-loader,'asset/inline' 对应 url-loader 强制内联。老项目升级 webpack 5 时这几个 loader 应该一并移除,留着会和内置逻辑冲突。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEWebpack 打包样式资源:style-loader 与 css-loader加载中