css modules 要求 class 名由 js 动态注入,html 中硬编码 class="button" 必然失效,因其不参与构建时重命名;必须通过 import styles from './button.module.css' 并用 styles.button 赋值。

纯 HTML 里写 class="card" 和 CSS Modules 完全不兼容,这不是命名规范问题,而是运行时 class 名根本不存在——构建后它已变成 _card_abc123 这类哈希值。想让 class 属性真正“模块化”,必须切断 HTML 硬编码 class 的路径,改由 JS 控制注入。
为什么直接在 HTML 中写 class="button" 必然失效
CSS Modules 不是运行时解析机制,它依赖构建工具(如 Vite/Webpack)在编译阶段重命名所有本地 class。HTML 模板里的字符串 class="button" 不参与该过程,浏览器渲染时只会查找字面量为 button 的 CSS 规则,而实际生效的选择器早已是 .button_kj2m9n。
- 常见错误现象:
document.querySelector('.button')返回null,开发者工具中元素上显示class="button",但 Styles 面板无匹配样式 - 即使你手动把
.button写进Button.module.css,构建后该文件导出的是对象{ button: '_button_kj2m9n' },不是字符串 - HTML 中的
class属性值不会被 JS 自动替换,除非你显式读取 styles 对象并赋值
class 属性在模块化场景下只能作为 JS 的“输出通道”
HTML 的 class 全局属性本身不具备模块能力,它的作用退化为一个“挂载点容器”,真实 class 名必须由 JS 动态写入。这意味着:结构和样式解耦,但 HTML 和 JS 形成强绑定。
- 在 JSX/TSX 中:
import styles from './Button.module.css'; <button classname="{styles.button}"></button> - 在纯 HTML + 原生 JS 中:
import('./Button.module.css').then(css => { btn.classList.add(css.button); }),但需自行管理卸载、重复添加、SSR 同构等边界 - 禁止
import './Button.module.css'(无解构),这只会执行样式注入,但返回值无法提取 class 映射 - 若用
require,必须const styles = require('./Button.module.css'),require返回对象,不是字符串
BEM 命名 + CSS Modules 是目前最可控的组合方案
很多人以为 BEM 是“命名习惯”,其实它是为模块化提供语义锚点:block 名即模块名,card__title 编译后变成 _card__title_xm7f3q,既保留可读性又确保隔离。关键不在写法,而在约束力。
- 块名(block)必须对应一个独立可复用 UI 单元,如
search-form,禁用泛称如main或section - 元素(element)仅限直属子节点,
search-form__input合法,search-form__input__icon违规,应拆为icon独立 block - 修饰符(modifier)只表达状态,如
search-form--compact,不描述布局位置(那是父容器职责) - Vite 中启用需文件名含
.module.css,且localIdentName设为"[name]_[local]_[hash:6]",否则调试时无法反查源文件
纯静态页或无 JS 入口时,class 必须退守命名空间前缀
当页面完全不走构建流程、或 JS 被禁用(如邮件模板、打印页),CSS Modules 彻底失效。此时唯一可行路径是人工加前缀 + 构建时自动补全,把模块边界“硬编码”进 class 名。
- 统一使用
u-(utility)、cmp-(component)、js-(behavior-only)等前缀,如cmp-card__header - 配合 PostCSS 插件(如
postcss-prefixwrap),将整块 CSS 包裹进:where(.cmp-card) { ... },避免手误漏写 - 禁止在全局 CSS 文件中定义裸
.card,所有规则必须带前缀或包裹在作用域选择器内 - HTML 中仍写
class="cmp-card",但这个 class 名就是最终生效名,不再被重命名
真正难的从来不是技术选型,而是让 class 属性在团队里承担固定角色:要么是 JS 注入的动态键,要么是带前缀的静态标识。混用两者——比如部分组件走 Modules、部分靠 BEM 手写——会导致 class 名在 DOM 里同时存在哈希值和原始名,特异性冲突、调试困难、上线后样式随机丢失。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











