css modules 必须使用 import styles from './x.module.css' 导入,且文件名严格匹配 .module.css(或 .module.scss 等),否则样式全局生效;动态拼接类名、错误导入方式或非法 :global() 用法均导致模块化失效。

必须用 import styles from './X.module.css' 导入,且文件名严格匹配 .module.css(或 .module.scss 等),否则样式仍是全局的——这不是配置问题,是构建工具的硬性文件名约定。
文件名不带 .module. 后缀,样式就不是局部的
CRA、Vite、Webpack 默认只对 /\.module\.(css|scss|sass|less|styl)$/ 这类文件启用模块化逻辑。写成 Button.css 或 Button.modules.css,哪怕内容完全一样,构建时也不会哈希类名。
-
Button.module.css✅ 正确,styles.button返回类似Button_button__axY7的哈希字符串 -
Button.css❌ 全局注入,import styles from './Button.css'得到空对象或原始类名 -
button.Module.CSS❌ 大小写不敏感但推荐全小写;拼写错误(如modules)同样失效 - 路径大小写也需一致(尤其 macOS/Linux),
./button.module.css与实际文件Button.module.css不匹配 →styles为空
import styles from 是唯一合法导入方式
import './Button.module.css' 看似省事,结果是样式生效但类名没哈希,所有组件共享原始类名,等于没开 CSS Modules。
- ✅ 正确:
import styles from './Button.module.css',然后className={styles.button} - ❌ 错误:
import './Button.module.css'(全局注入) - ❌ 错误:
import * as styles from './Button.module.css'(CRA 中返回空对象) - ❌ 错误:
className="button"(绕过 modules,退化为全局)
动态拼接 styles.xxx 会失效,运行时找不到键
styles 是编译期生成的 plain object,键名固定,没有运行时属性查找机制。TS 会报错,JS 运行时返回 undefined。
- ❌ 错误:
className={styles['btn-' + type]}→ 极大概率undefined,DOM 上无 class - ✅ 正确:提前定义好所有可能类名,用条件判断:
className={type === 'primary' ? styles.primary : styles.secondary} - ✅ 条件组合多个类:用
clsx或classnames:className={clsx(styles.button, props.large && styles.large)} - ⚠️ 注意:
composes只能继承同项目内其他.module.*文件的类,不能跨模块直接引用otherStyles.xxx
:global() 只能在顶层或后代选择器中使用
:global() 是穿透模块作用域的唯一标准语法,但它有严格位置限制,嵌套在局部规则块里会被忽略。
- ✅ 合法:
:global(.third-party-modal) { z-index: 9999; }(顶层) - ✅ 合法:
.container :global(.icon) { ... }(作为后代选择器) - ❌ 非法:
.container { :global(.icon) { ... } }(嵌套在局部块内,无效) - ⚠️
:local()是默认行为,冗余且部分 loader 不识别,别写
最容易被忽略的是:CSS Modules 的隔离能力完全依赖构建时的文件名识别和导入方式,而不是运行时逻辑——一旦命名或导入出错,它不会报错,而是静默退化为全局样式,排查时容易绕远路。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











