一、处理顺序
Webpack 里 loader 从右到左执行:
// webpack.config.js
{
test: /.scss$/,
use: [
'style-loader', // 3. 把 CSS 注入到 DOM
'css-loader', // 2. 处理 CSS 文件(含 CSS Modules)
'postcss-loader', // 2. PostCSS 插件处理
'sass-loader', // 1. 先把 SCSS 编译成 CSS
]
}
二、核心 Loader
sass-loader / less-loader / stylus-loader
把预处理器语法编译成标准 CSS,调用对应编译器:
sass-loader → 调用 sass / node-sass
less-loader → 调用 less
stylus-loader → 调用 stylus
css-loader
负责两件事:
① 解析 CSS 内部依赖
@import './variables.scss'; // 解析 @import
background: url('./logo.png'); // 解析 url()
把依赖收集起来交给 Webpack 统一处理。
② CSS Modules hash 编译
把类名基于文件路径 + 类名生成唯一 hash,保证组件间不冲突:
// button.module.scss
.button { color: red; }
.primary { background: blue; }
编译后:
.button_a1b2c3 { color: red; }
.primary_d4e5f6 { background: blue; }
JS 侧同步生成映射:
const styles = {
button: 'button_a1b2c3',
primary: 'primary_d4e5f6',
}
<button className={styles.button}>Click</button>
hash 生成细节:
// css-loader 内部
const hash = loaderUtils.getHashDigest(
Buffer.from(`${filePath}\x00${localName}`), // 路径和类名用 \x00 分隔
'md4', // 哈希算法
'base64', // 编码
5 // 取前 5 位,64^5 ≈ 10 亿种组合,足够用
)
localIdentName 模板(可自定义):
{
loader: 'css-loader',
options: {
modules: {
// 开发环境:保留路径和类名,方便调试
localIdentName: '[path][name]__[local]',
// 输出:src-components-Button__button
// 生产环境:只保留 hash,类名更短
localIdentName: '[hash:base64:5]',
// 输出:a1b2c
}
}
}
编译规则:
| 选择器类型 | 是否加 hash | 示例 |
|---|---|---|
类选择器 .button | ✅ | .button_a1b2c3 |
ID 选择器 #header | ✅ | #header_d4e5f6 |
元素选择器 h1 | ❌ 原样输出 | h1 |
通配符 * | ❌ 原样输出 | * |
:root | ❌ 原样输出 | :root |
⚠️ 元素选择器即使写在
.module.scss里也是全局生效的,规范上建议.module.scss里只写类选择器。
style-loader
把 CSS 字符串通过 JS 动态插入到 <style> 标签:
// style-loader 简化原理
const style = document.createElement('style')
style.innerHTML = `.button_a1b2c3 { color: red }`
document.head.appendChild(style)
热更新时直接替换 style 标签内容,不需要刷页面,适合开发环境。
❌ 生产环境不用,改用
MiniCssExtractPlugin提取成独立文件。
三、PostCSS 相关
PostCSS 是独立的 CSS 处理平台,通过插件机制工作,postcss-loader 是接入 Webpack 的桥梁。
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer'),
require('postcss-preset-env'),
]
}
常用插件
autoprefixer
自动添加浏览器兼容前缀,不用手写 -webkit-:
/* 你写的 */
.box { display: flex; }
/* 处理后 */
.box {
display: -webkit-box;
display: -webkit-flex;
display: flex;
}
postcss-preset-env
让你用新版 CSS 语法,自动降级到兼容写法,类似 Babel 对 JS 做的事:
/* 你写的(CSS 嵌套,部分浏览器不支持) */
.parent {
& .child { color: red; }
}
/* 处理后 */
.parent .child { color: red; }
postcss-prefix-selector
给所有选择器加前缀,微前端场景下用来替代 qiankun 运行时 CSS 沙箱:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-prefix-selector')({
prefix: '.app-finance',
exclude: [/global.scss/], // 排除真正需要全局的样式
}),
],
}
/* 原始 */
.button { color: red; }
h1 { font-size: 24px; }
/* 处理后 */
.app-finance .button { color: red; }
.app-finance h1 { font-size: 24px; }
四、生产环境:MiniCssExtractPlugin
把所有 CSS 从 JS bundle 里抽出来,生成独立的 .css 文件:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// webpack.config.js
{
test: /.scss$/,
use: [
MiniCssExtractPlugin.loader, // 替换掉 style-loader
'css-loader',
'postcss-loader',
'sass-loader',
]
}
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css', // 文件名带 hash,利于缓存
})
]
相比 style-loader 的优势:
| style-loader | MiniCssExtractPlugin | |
|---|---|---|
| CSS 独立文件 | ❌ 内联在 JS 里 | ✅ |
| 浏览器缓存 | ❌ | ✅ JS/CSS 分别缓存 |
| JS/CSS 并行加载 | ❌ | ✅ |
| 热更新 | ✅ | ❌ 需刷页面 |
| 适用环境 | 开发 | 生产 |
五、完整处理链路
.scss 文件
│
▼
sass-loader
把 SCSS / LESS 编译成标准 CSS
│
▼
postcss-loader
autoprefixer 自动加浏览器前缀
postcss-preset-env 语法降级
postcss-prefix-selector 微前端前缀(按需)
│
▼
css-loader
解析 @import / url() 依赖
CSS Modules hash 编译
│
▼
┌─────────────────┬──────────────────────────┐
│ 开发环境 │ 生产环境 │
▼ ▼ │
style-loader MiniCssExtractPlugin │
动态插入 提取为独立 .css 文件 │
<style> 标签 支持浏览器缓存和并行加载 │
└─────────────────┴──────────────────────────┘
六、CSS Modules 的局限(结合微前端场景)
CSS Modules 解决的是组件级别的类名冲突,微前端需要的是应用级别的隔离,两者不是同一层面。
| 问题 | CSS Modules 能解决? |
|---|---|
组件间类名冲突(.button 重名) | ✅ |
子应用间全局样式冲突(body、h1) | ❌ 元素选择器不处理 |
| 第三方库样式冲突(antd) | ❌ 管不到第三方 |
| 弹窗挂到 body 的样式丢失 | ❌ 和类名无关 |
推荐组合方案:
CSS Modules → 解决组件间类名冲突
postcss-prefix-selector → 解决应用间全局样式冲突
getPopupContainer → 解决第三方弹窗样式丢失