原生 CSS
原生 CSS
本指南介绍如何使用 webpack 的 experiments.css 原生 CSS 处理能力,以及如何将现有配置从 css-loader、style-loader 和 mini-css-extract-plugin 迁移到原生方案。
T> experiments.css 仍处于实验阶段。它预计将在 webpack v6 中成为默认行为,但在开发过程中行为仍可能发生变化。
开始使用
在 webpack 配置中启用原生 CSS 支持:
webpack.config.js
js
export default {
experiments: {
css: true,
},
};
启用此选项后,webpack 会将 .css 文件视为一级模块——解析 @import 和 url()、提取样式表、生成内容哈希,并支持 CSS Modules——无需 css-loader、style-loader 或 mini-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-loader 的 exportType: 'string' |
"css-style-sheet" |
导出可构造的 CSSStyleSheet |
css-loader 的 exportType: '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") |
MiniCssExtractPlugin 的 filename / chunkFilename |
output.cssFilename / output.cssChunkFilename |
style-loader |
exportType: "style" |
css-loader |
内置 CSS 解析(无需 loader) |
css-loader 的 url / import |
module.parser.css.url / import(均默认为 true) |
css-loader 的 modules(.module.css 自动检测) |
css/auto 模块类型 |
css-loader 的 modules.mode |
css/module / css/global 类型 + pure |
css-loader 的 modules.localIdentName |
generator localIdentName |
css-loader 的 modules.exportLocalsConvention |
generator exportsConvention |
css-loader 的 modules.namedExport |
module.parser.css.namedExports(默认 true) |
css-loader 的 modules.exportOnlyLocals |
generator exportsOnly |
css-loader 的 esModule |
generator esModule(默认 true) |
css-loader 的 exportType: '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$/i → css/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.css 和 module.generator.css 下有对应的原生配置。常见的默认值(url、import、namedExports 均开启)已匹配典型的 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.hashFunction、output.hashDigest、output.hashDigestLength 和 output.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"(提取);attributes、insert 和 styleTagTransform 没有原生等价方案——如果依赖这些选项,请保留 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-loader、stylus-loader 等。
7. 服务端渲染(node + web)
对于 SSR,通常需要构建两次——一次 web 打包给浏览器,一次 node 打包给服务端——并且 CSS Modules 的类名必须一致,这样服务端渲染的标记才能顺利地在客户端进行水合。这正是 css-loader 的 getJSON 常被用于往返同步的场景;使用原生 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.parser 和 module.generator 下按模块类型配置选项。键名为 css、css/auto、css/global 和 css/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/module 和 css/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> 如果希望某个 @import 或 url() 保持原样(在输出中保留而不由 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 也接受返回 string 或 string[] 的函数——返回数组会以多个别名导出局部类名,与 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-loader的localIdentRegExp和过滤器回调,以及style-loader的attributes/insert/styleTagTransform。对于需要这些选项的文件,请保留对应 loader。(getLocalIdent对应localIdentName的函数形式,getJSON/SSR 则通过跨目标匹配类名解决。) importLoaders没有等价方案——链中的 loader 会自动应用于@import导入的文件。- 如果项目依赖复杂的高级 loader 链,请先验证每个部分再完全迁移。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
