CSS Modules 的根本区别在于将样式作为模块导入 JS,类名构建时哈希化并强制局部作用域;必须使用 .module.css 后缀、配置 loader、通过 import styles from './X.module.css' 使用,脱离构建工具链即失效。

现代前端工程中,CSS Modules 的引入方式与传统 CSS 的根本区别在于:它不是“把样式塞进页面”,而是“把样式作为模块导入 JS”,类名在构建时被重写、作用域被强制隔离。传统方式(如 @import 或全局 .css)无法实现局部作用域,也不参与模块依赖图;而 CSS Modules 是构建链路中的一环,必须配合文件命名、loader 配置和 JS 导入三者协同生效。
文件名必须带 .module.css 后缀
Webpack 和 Vite 都靠后缀识别模块化意图,不是靠内容或配置开关。写成 Button.css 就是全局样式,哪怕内容一模一样;写成 Button.module.css 才触发哈希类名生成和导出对象。
- Webpack:即使配置了
css-loader的modules: true,没后缀也不会启用模块化 - Vite:原生支持,但仅对
.module.css(或.module.scss等)生效;.css文件永远走全局注入 - Vue 单文件组件里写
<style module></style>是另一套机制,和.module.css文件不互通
import styles from './X.module.css' 是唯一合法用法
CSS Modules 的样式只能通过 JS 模块导入使用,不能靠 HTML class 属性硬写原始类名——因为编译后的真实类名类似 Button_button_abc123,你根本写不出来。
- 错误写法:
<button class="button"></button>→ 这个button是全局类,未被模块化处理,大概率没样式 - 正确写法:
<button classname="{styles.button}"></button>→styles.button是构建后映射的真实哈希类名 - 动态拼接要小心:
className={"btn " + styles.primary}会把btn当作全局类,且空格可能引发渲染问题;应改用数组或clsx
@import 在模块化工程中已失效
不要在 .module.css 里写 @import "./base.css" —— 它不会被解析为依赖,不触发 HMR,不参与 Tree Shaking,还可能造成重复打包或加载阻塞。
立即学习“前端免费学习笔记(深入)”;
- 替代方案:用
composes关键字继承同模块内规则,或在 JS 中分别import多个模块再组合 - Sass 用户应改用
@use或@forward,它们能被构建工具识别并纳入依赖图 - 全局基础样式(如重置、变量)应单独引入到入口 JS 或
_app.tsx,而非被模块文件@import
最容易被忽略的点是:CSS Modules 的“模块性”完全依赖构建时处理,一旦脱离 Webpack/Vite 等现代工具链(比如直接用 <link> 引入),它就退化成普通 CSS,所有隔离、哈希、导出能力全部消失。所以它从来不是一种“纯 CSS 方案”,而是一种“JS 与样式协同的构建约定”。


















