知海

原生 CSS

webpackjsorg-main指南-教程

原生 CSS

本指南介绍如何使用 webpack 的 experiments.css 原生 CSS 处理能力,以及如何将现有配置从 css-loaderstyle-loadermini-css-extract-plugin 迁移到原生方案。

T> experiments.css 仍处于实验阶段。它预计将在 webpack v6 中成为默认行为,但在开发过程中行为仍可能发生变化。

开始使用

在 webpack 配置中启用原生 CSS 支持:

webpack.config.js

js 复制代码
export default {
  experiments: {
    css: true,
  },
};

启用此选项后,webpack 会将 .css 文件视为一级模块——解析 @importurl()、提取样式表、生成内容哈希,并支持 CSS Modules——无需 css-loaderstyle-loadermini-css-extract-plugin

导入 CSS

启用实验特性后,直接从 JavaScript 导入 .css 文件:

src/index.js

js 复制代码
import "./styles.css";

const element = document.createElement("h1");
element.textContent = "Hello native CSS";
document.body.appendChild(element);

src/styles.css

css 复制代码
h1 {
  color: #1f6feb;
}

Webpack 会处理 CSS 并将其包含在构建输出中。

CSS 模块类型

原生 CSS 引入了四种 Rule.type 值。了解当前使用的是哪一种类型是迁移的关键,因为每种类型都对应 css-loader 不同的 modules.mode:

类型 作用域 css-loader 等价配置
css 全局,不解析 CSS Modules modules: false
css/global 默认为全局选择器,但 :local() 会生效 modules.mode: 'global'
css/module 默认为局部作用域,:global() 可转回全局 modules.mode: 'local'
css/auto *.module.css / *.modules.css 文件使用 css/module,否则使用 css/global modules.auto: true

webpack 为 /\.css$/i 添加的默认规则是 css/auto,因此 *.module.css 文件会成为 CSS Modules,其余文件保持全局——开箱即用即可匹配最常见的 css-loader 配置。

CSS Modules

使用 css/auto 时,将文件命名为 *.module.css(或 *.modules.css)即可将其纳入 CSS Modules:

src/button.module.css

css 复制代码
.button {
  background: #0d6efd;
  color: white;
  border: 0;
  border-radius: 4px;
  padding: 8px 12px;
}

src/index.js

js 复制代码
import * as styles from "./button.module.css";

const button = document.createElement("button");
button.className = styles.button;
button.textContent = "Click me";
document.body.appendChild(button);

T> 默认情况下 namedExports 已启用,因此请以命名空间形式(import * as styles)或按名称导入(import { button } from "./button.module.css")导入局部类名。将其设为 false 可保留经典的默认导出对象。

你可以使用解析器和生成器选项自定义 CSS Modules 行为——参见下方所有选项及示例:

webpack.config.js

js 复制代码
export default {
  experiments: {
    css: true,
  },
  module: {
    parser: {
      "css/auto": {
        namedExports: true,
      },
    },
    generator: {
      "css/auto": {
        exportsConvention: "camel-case-only",
        localIdentName: "[uniqueName]-[id]-[local]",
      },
    },
  },
};

支持的 CSS Modules 特性

原生 CSS Modules 支持与 css-loader 相同的书写特性,因此大多数样式表无需修改即可迁移:

  • composes — 用一个局部类组合另一个局部类(包括 composes: foo from "./other.module.css");导出结果解析为以空格分隔的类名列表。
  • @value — 声明和导入可复用的值(@value primary: #1f6feb;, @value primary from "./vars.module.css")。
  • :export — 公开任意键/值对给 JavaScript。
  • :local() / :global() — 在任何模块类型内内联切换作用域。
css 复制代码
/* button.module.css */
@value brand: #1f6feb;

.base {
  padding: 8px 12px;
}
.primary {
  composes: base;
  background: brand;
}
:export {
  brandColor: brand;
}

输出模式(exportType)

单个 CSS 模块可以通过四种方式输出。exportType 解析器选项用于选择具体方式,每种方式替代传统工具链中的不同部分:

exportType 行为 替代方案
"link" (默认) 提取 .css 文件,通过 <link> 加载 mini-css-extract-plugin
"style" 通过运行时注入 <style> 元素 style-loader
"text" 将 CSS 作为字符串导出 css-loaderexportType: 'string'
"css-style-sheet" 导出可构造的 CSSStyleSheet css-loaderexportType: 'css-style-sheet'

按模块类型全局设置:

js 复制代码
export default {
  experiments: { css: true },
  module: {
    parser: {
      "css/auto": {
        exportType: "style",
      },
    },
  },
};

或按规则针对部分文件设置:

js 复制代码
export default {
  experiments: { css: true },
  module: {
    rules: [
      {
        test: /\.css$/i,
        type: "css/auto",
        parser: { exportType: "style" },
      },
    ],
  },
};

迁移指南

总览

传统配置 原生等价方案
mini-css-extract-plugin(MiniCssExtractPlugin.loader) 内置提取(默认 exportType: "link")
MiniCssExtractPluginfilename / chunkFilename output.cssFilename / output.cssChunkFilename
style-loader exportType: "style"
css-loader 内置 CSS 解析(无需 loader)
css-loaderurl / import module.parser.css.url / import(均默认为 true)
css-loadermodules(.module.css 自动检测) css/auto 模块类型
css-loadermodules.mode css/module / css/global 类型 + pure
css-loadermodules.localIdentName generator localIdentName
css-loadermodules.exportLocalsConvention generator exportsConvention
css-loadermodules.namedExport module.parser.css.namedExports(默认 true)
css-loadermodules.exportOnlyLocals generator exportsOnly
css-loaderesModule generator esModule(默认 true)
css-loaderexportType: 'string' / 'css-style-sheet' exportType: "text" / "css-style-sheet"

一次只迁移一个 loader——下面的章节按顺序进行,确保每一步构建都能保持正常。

1. 从经典配置开始

webpack.config.js

js 复制代码
import MiniCssExtractPlugin from "mini-css-extract-plugin";

export default {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

2. 启用原生 CSS

webpack.config.js

js 复制代码
export default {
  experiments: {
    css: true,
  },
};

内置的 /\.css$/icss/auto 规则现在会处理 .css 导入。确认以下各节中的每个选项都有对应方案后,再移除自定义规则和插件。

3. 替换 mini-css-extract-plugin

原生 CSS 默认会提取样式表并为其添加内容哈希(exportType: "link"),因此不再需要该插件及其 loader:

webpack.config.js

diff 复制代码
-import MiniCssExtractPlugin from "mini-css-extract-plugin";
-
 export default {
+  experiments: {
+    css: true,
+  },
-  module: {
-    rules: [
-      {
-        test: /\.css$/i,
-        use: [MiniCssExtractPlugin.loader, "css-loader"],
-      },
-    ],
-  },
-  plugins: [new MiniCssExtractPlugin()],
 };

映射其余插件选项:

mini-css-extract-plugin 选项 原生等价方案
filename output.cssFilename
chunkFilename output.cssChunkFilename
loader 的 publicPath output.publicPath
loader 的 esModule generator esModule(默认 true)
ignoreOrder 不适用——原生 CSS 不会产生顺序冲突警告

webpack.config.js

js 复制代码
export default {
  experiments: { css: true },
  output: {
    cssFilename: "[name].[contenthash].css",
    cssChunkFilename: "[id].[contenthash].css",
  },
};

4. 替换 css-loader

大多数 css-loader 选项在 module.parser.cssmodule.generator.css 下有对应的原生配置。常见的默认值(urlimportnamedExports 均开启)已匹配典型的 css-loader 配置,因此许多项目根本不需要配置解析器。

css-loader 选项 原生等价方案
url module.parser.css.url — 默认 true
import module.parser.css.import — 默认 true
importLoaders 不适用——链中的 loader 会自动应用于 @import 导入的文件
sourceMap devtool 控制(支持按类型的 css 条目)
esModule module.generator.css.esModule — 默认 true
exportType: 'string' parser exportType: "text"
exportType: 'css-style-sheet' parser exportType: "css-style-sheet"
modules(自动检测) css/auto 模块类型(内置)
modules.mode: 'local' css/module 类型
modules.mode: 'global' css/global 类型
modules.mode: 'pure' parser pure: true
modules.localIdentName generator localIdentName
modules.exportLocalsConvention generator exportsConvention
modules.namedExport parser namedExports — 默认 true
modules.exportOnlyLocals generator exportsOnly
modules.localIdentHashSalt generator localIdentHashSalt
modules.localIdentHashFunction generator localIdentHashFunction

例如,这个 css-loader 的 CSS Modules 配置:

js 复制代码
export default {
  module: {
    rules: [
      {
        test: /\.module\.css$/i,
        use: [
          {
            loader: "css-loader",
            options: {
              modules: {
                localIdentName: "[local]-[hash:base64:6]",
                exportLocalsConvention: "camel-case-only",
                namedExport: true,
              },
            },
          },
        ],
      },
    ],
  },
};

变成:

webpack.config.js

js 复制代码
export default {
  experiments: { css: true },
  module: {
    parser: {
      "css/auto": {
        namedExports: true,
      },
    },
    generator: {
      "css/auto": {
        localIdentName: "[local]-[hash:base64:6]",
        exportsConvention: "camel-case-only",
      },
    },
  },
};

T> localIdentName 支持哈希占位符,如 [hash:base64:6]。可通过 output.hashFunctionoutput.hashDigestoutput.hashDigestLengthoutput.hashSalt 全局调整哈希行为,或通过 localIdentHash* 生成器选项按模块类型调整。

一些 css-loader 选项的工作方式不同:

  • getLocalIdent — 原生 CSS 不通过自定义函数,而是通过 localIdentName 模板驱动命名,该模板也接受函数。
  • getJSON — 类名映射由 CSS 模块本身导出,可从编译的模块图中读取,因此当框架需要磁盘上的文件时,可以通过一个小插件将其序列化为 JSON。对于服务端渲染,通常完全不需要它——参见服务端渲染
  • localIdentRegExp 和过滤器样式的 url/import 回调没有原生等价方案;可以为受影响的文件保留 css-loader,或使用 IgnorePlugin 排除特定请求。

5. 替换 style-loader

如果你使用 style-loader 在运行时注入样式而不是提取文件,请设置 exportType: "style":

webpack.config.js

js 复制代码
export default {
  experiments: { css: true },
  module: {
    parser: {
      "css/auto": {
        exportType: "style",
      },
    },
  },
};

这会从 webpack 运行时注入 <style> 元素,覆盖默认的 style-loader(injectType: "styleTag")用例。如果只有部分文件应被注入而其余文件被提取,可将其限定到单个规则:

js 复制代码
export default {
  experiments: { css: true },
  module: {
    rules: [
      {
        test: /\.inline\.css$/i,
        type: "css/auto",
        parser: { exportType: "style" },
      },
    ],
  },
};

关于 style-loader 选项的说明:injectType: "linkTag" 对应默认的 exportType: "link"(提取);attributesinsertstyleTagTransform 没有原生等价方案——如果依赖这些选项,请保留 style-loader

6. 继续使用预处理器(Sass、Less、PostCSS)

原生 CSS 替代的是 CSS loader,而非预处理器 loader。在 use 中保留预处理器 loader,并将规则的 type 设置为 css/auto,让 webpack 将 loader 的输出视为 CSS:

webpack.config.js

js 复制代码
export default {
  experiments: { css: true },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: ["postcss-loader", "sass-loader"],
        type: "css/auto",
      },
    ],
  },
};

sass-loader 将代码编译为 CSS,postcss-loader 进行后处理,原生 CSS 则负责提取、url() 和 CSS Modules。同样的模式适用于 less-loaderstylus-loader 等。

7. 服务端渲染(node + web)

对于 SSR,通常需要构建两次——一次 web 打包给浏览器,一次 node 打包给服务端——并且 CSS Modules 的类名必须一致,这样服务端渲染的标记才能顺利地在客户端进行水合。这正是 css-loadergetJSON 常被用于往返同步的场景;使用原生 CSS 时,可以通过让 localIdentName 在不同目标间保持确定性来完全避免往返同步。

使用基于路径的模板(不包含编译级哈希),以便每个目标都生成相同的类名:

webpack.config.js

js 复制代码
const common = {
  experiments: { css: true },
  module: {
    rules: [
      {
        test: /\.module\.css$/i,
        type: "css/module",
        generator: {
          // `[file]__[local]` 在不同目标间保持一致——无需 getJSON 同步。
          localIdentName: "[file]__[local]",
        },
      },
    ],
  },
};

export default [
  { ...common, name: "web", target: "web" },
  { ...common, name: "node", target: "node" },
];

node 目标上,CSS 生成器默认设置 exportsOnly: true,因此服务端构建仅导出类名映射而不输出样式表——这正是 SSR 渲染器所需的行为。浏览器构建仍会提取真正的 CSS。如果你更倾向于单个配置,target: ["web", "node"] 可以构建一个在两个环境中都能运行的通用 bundle。

T> 避免为 SSR 使用生产默认值 localIdentName: "[fullhash]"——完整的编译哈希在 web 和 node 构建之间是不同的,因此类名无法对齐。请在两个配置中都固定一个确定性模板(基于路径或 [local],可选地加上每文件的 [hash])。

8. 保持导入不变并进行验证

JS 导入保持不变:

js 复制代码
import "./styles.css";
import * as styles from "./button.module.css";

然后检查:

  • 样式在开发环境中能正确应用,
  • 生产环境中能输出提取的 .css 文件,
  • CSS Modules 导出与现有用法一致。

所有选项及示例

module.parsermodule.generator 下按模块类型配置选项。键名为 csscss/autocss/globalcss/module;下面的示例使用 css/auto,因为它支撑默认规则。

解析器选项

以下所有布尔解析器选项默认均为 true

选项 类型 默认值 描述
import boolean true 处理 @import 规则。
url boolean true 处理 url() / image-set() / src() / image()
namedExports boolean true 将 CSS Modules 局部类名导出为 ES 模块命名导出。
exportType "link" | "style" | "text" | "css-style-sheet" "link" CSS 的输出方式(参见输出模式)。
pure boolean false 严格纯模式——每个选择器必须包含局部类/ID。仅 css/modulecss/auto
as "stylesheet" | "block-contents" "stylesheet" 将源码解析为完整样式表或块的内容。
animation boolean true 重命名局部 @keyframes 名称。
container boolean true 重命名局部 @container 名称。
customIdents boolean true 重命名自定义标识符。
dashedIdents boolean true 重命名虚线标识符(自定义属性)。
function boolean true 重命名局部 @function 名称。
grid boolean true 重命名网格线/区域标识符。
js 复制代码
export default {
  experiments: { css: true },
  module: {
    parser: {
      "css/auto": {
        import: true,
        url: true,
        namedExports: true,
        exportType: "link",
        pure: false,
        // 仅重命名 @keyframes;@container / 网格标识符保持原样。
        animation: true,
        container: false,
        grid: false,
      },
    },
  },
};

T> 如果希望某个 @importurl() 保持原样(在输出中保留而不由 webpack 解析),可以在其前面添加 /* webpackIgnore: true */ 注释——这在 import/url 全局启用的情况下,对于 CDN URL 或运行时解析的资源非常方便。

生成器选项

选项 类型 默认值 描述
localIdentName string | function "[uniqueName]-[id]-[local]"(开发)/ "[fullhash]"(生产) 生成的局部类名模板。
exportsConvention "as-is" | "camel-case" | "camel-case-only" | "dashes" | "dashes-only" | function "as-is" 导出局部类名的命名约定。
exportsOnly boolean 在没有 document 的目标上(如 node)为 true,否则为 false 仅导出局部类名;跳过样式表输出(SSR)。
esModule boolean true 为生成的 JS 输出 ES 模块语法。
localIdentHashFunction string output.hashFunction localIdentName 哈希使用的哈希函数。
localIdentHashDigest string "base64url" 局部标识符的哈希摘要编码。
localIdentHashDigestLength number 6 局部标识符的哈希摘要长度。
localIdentHashSalt string output.hashSalt 局部标识符的哈希盐。
js 复制代码
export default {
  experiments: { css: true },
  module: {
    generator: {
      "css/auto": {
        localIdentName: "[uniqueName]-[id]-[local]",
        exportsConvention: "camel-case-only",
        esModule: true,
        exportsOnly: false,
        localIdentHashDigest: "base64url",
        localIdentHashDigestLength: 6,
      },
    },
  },
};

exportsConvention 也接受返回 stringstring[] 的函数——返回数组会以多个别名导出局部类名,与 css-loader 的行为一致。

常见示例

带命名导出的 CSS Modules

src/app.module.css

css 复制代码
.primary {
  color: #1f6feb;
}
.large-text {
  font-size: 2rem;
}

src/index.js

js 复制代码
import { largeText, primary } from "./app.module.css";

document.body.classList.add(primary, largeText);

webpack.config.js

js 复制代码
export default {
  experiments: { css: true },
  module: {
    generator: {
      "css/auto": {
        exportsConvention: "camel-case-only",
      },
    },
  },
};

为生产环境提取带哈希的 CSS 文件

webpack.config.js

js 复制代码
export default {
  mode: "production",
  experiments: { css: true },
  output: {
    cssFilename: "css/[name].[contenthash].css",
    cssChunkFilename: "css/[id].[contenthash].css",
  },
};

在运行时注入 <style> 标签(style-loader 风格)

webpack.config.js

js 复制代码
export default {
  experiments: { css: true },
  module: {
    parser: {
      "css/auto": {
        exportType: "style",
      },
    },
  },
};

导入可构造样式表

src/index.js

js 复制代码
import sheet from "./theme.css" with { type: "css" };

document.adoptedStyleSheets = [sheet];

Webpack 会将 with { type: "css" } 导入断言自动解析为 exportType: "css-style-sheet",从而得到一个 CSSStyleSheet 实例。

将 CSS 作为字符串导入

webpack.config.js

js 复制代码
export default {
  experiments: { css: true },
  module: {
    parser: {
      "css/auto": {
        exportType: "text",
      },
    },
  },
};

src/index.js

js 复制代码
import css from "./styles.css";

const style = new CSSStyleSheet();
style.replaceSync(css);

全局样式与局部模块并存

使用默认的 css/auto 规则,*.module.css 会被限定作用域,其余文件保持全局——无需额外配置:

js 复制代码
import "./reset.css"; // 全局
import * as card from "./card.module.css"; // 局部

实验状态与已知限制

experiments.css 明确处于实验阶段——请将其视为可选功能,在广泛使用前仔细测试。

  • API 和行为在 webpack v6 默认启用之前仍可能发生变化。
  • 部分 loader 选项没有直接替代方案:css-loaderlocalIdentRegExp 和过滤器回调,以及 style-loaderattributes / insert / styleTagTransform。对于需要这些选项的文件,请保留对应 loader。(getLocalIdent 对应 localIdentName 的函数形式,getJSON/SSR 则通过跨目标匹配类名解决。)
  • importLoaders 没有等价方案——链中的 loader 会自动应用于 @import 导入的文件。
  • 如果项目依赖复杂的高级 loader 链,请先验证每个部分再完全迁移。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。