正确做法是 localIdentName=[name]__[local]--[hash:base64:5],保留语义且压缩类名;Modifier 必须显式枚举、禁止动态拼接;禁用 SCSS mixin 生成 BEM 字符串;优先用 CSS 自定义属性替代冗余 Modifier 类名。

localIdentName 配置不能只设成 [hash]
用 css-loader 的 localIdentName 压缩类名,最常见错误是设成 [hash] 或 [folder]_[hash]。这样生成的类名如 abc7f 或 card_1x9z,DevTools 里完全看不到原始语义,团队协作和线上 debug 直接失效。
正确做法是保留结构信息:localIdentName=[name]__[local]--[hash:base64:5]。它把 card__title--large 编译为 card__title--large_abc12,长度压了约 30%,同时仍能一眼对应源码。
不推荐用 [path]:项目迁移或目录重命名后,哈希值会变,导致缓存失效率飙升;[name] 更稳定,也更贴近 BEM 的 block 命名本意。
Modifier 必须收口,不能动态拼接
HTML 中写 className={`${styles.card} ${isLarge ? styles['card--large'] : ''}` 是安全的;但写 className={`card__title--${size}--${theme}`} 就是反模式——PurgeCSS 和 Lightning CSS 无法识别这种字符串拼接,会保守保留所有可能的 card__title--* 规则,CSS 体积不降反升。
立即学习“前端免费学习笔记(深入)”;
Modifier 应该被 JS 层显式枚举、收敛:
- 一个 Block 的 Modifier 总数建议 ≤ 5 个(如
card--featured、card--compact、card--loading) - 禁止
card--bg-blue-500这类“样式直译”类名,改用 CSS 自定义属性控制 - 所有类名必须来自
styles对象解构,确保构建工具可静态分析
别用 SCSS @mixin 生成 BEM 字符串
@mixin bem-class($block, $elem, $mod) 看似省事,实则破坏工具链。它让类名变成运行时拼接,PurgeCSS 无法追踪,Webpack 的 tree-shaking 也会漏删未用修饰符。
SCSS 混合宏适合封装视觉逻辑,比如:
-
@mixin button-variant($bg, $padding)→ 复用样式声明 -
@mixin responsive-grid($cols)→ 抽离响应式断点逻辑
不适合封装类名生成逻辑。BEM 结构应在 CSS 文件中静态声明,在 JS 中条件组合,而非在预处理器里造字符串。
CSS 自定义属性才是真正的“压缩层”
当一个 Modifier 实际只控制颜色、圆角、间距等可变量时,硬写类名等于把配置逻辑重复塞进 HTML。例如 button--theme-primary--size-xl--variant-outline 完全可以简化为 button,然后通过 CSS 设置:
.button {
padding: var(--button-padding, 0.75rem 1.5rem);
background-color: var(--button-bg, #007bff);
border-radius: var(--button-radius, 0.375rem);
}
关键点:
- 变量只定义在 Block 根级(如
.button),Element 和 Modifier 不另开变量 - JS 层只需切换
data-theme="primary"或设置style="--button-padding: 1rem 2rem" - 避免变量爆炸:不要为每个状态都建一套
--button-loading-bg,而应复用基础变量 + 状态类控制
真正难的不是缩短字符串,而是敢把“按钮尺寸”从类名里拿掉,交给 CSS 变量和父级上下文去承载——这一步卡住很多人,因为需要重构组件 API 和样式契约。


















