CSS Modules
CSS Modules 解决的问题很直接:CSS 默认是全局作用域,组件一多,类名冲突、样式泄漏、覆盖顺序失控这些问题就会越来越明显。
它的做法也很直接:把 .module.css、.module.scss 这类文件里的类名做局部化处理,再通过 JavaScript 模块把映射关系导出来。
所以它不是浏览器原生特性,也不是某个框架独占能力,而是一种构建期约定。
先看 CSS Modules 在解决什么
传统全局 CSS 最常见的问题通常是:
.button、.title、.card这类类名很容易撞车- 页面越来越多之后,很难判断某个类名还会不会影响别处
- 重构组件时,不容易确认哪些样式还能删
- 样式依赖关系不清,最后只能靠命名规范硬撑
CSS Modules 的核心思路一般就是:
- 默认把类名局部化
- 让样式文件和组件文件形成一对一或一对少的依赖关系
- 通过 import 明确当前组件到底在用哪份样式
一个最小例子
/* button.module.css */
.root {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
padding: 0.5rem 1rem;
background: #111827;
color: white;
}
import styles from './button.module.css'
export function Button() {
return <button className={styles.root}>提交</button>
}
构建之后,root 不会直接原样落到最终产物里,而是会被处理成带 hash 的类名。
它的运行方式怎么理解
导入 CSS Module 时,打包器通常会导出一个对象:
{
root: 'button_root__3X9fA'
}
组件里拿到的 styles.root,其实就是这个映射后的类名。
这一层很重要,因为它说明了两件事:
- CSS Modules 的局部作用域是构建期行为
- 最终进入浏览器的,仍然是普通 CSS
CSS Modules 最常见的特性
1. 局部作用域
.title {
font-size: 1.25rem;
font-weight: 600;
}
在模块文件里,这个 .title 默认是局部的,不会直接污染整个站点。
2. 导出 class 映射
import styles from './card.module.css'
export function Card() {
return <article className={styles.title}>标题</article>
}
3. 还能配合预处理器使用
button.module.scsscard.module.lesslayout.module.styl
也就是说,CSS Modules 解决的是“作用域”,预处理器解决的是“写法体验”,这两层可以叠在一起。
4. :global 与 :local
默认情况下,模块里的类名是局部的;但有些场景还是得逃回全局。
:global(.markdown-body h1) {
margin-bottom: 1rem;
}
.wrapper :global(.third-party-button) {
border-radius: 999px;
}
如果需要显式声明局部,也能写:
:local(.card) {
padding: 1rem;
}
不过真实项目里,更常见的是默认局部,按需 :global。
5. 组合(composes)
CSS Modules 支持把一个类组合进另一个类。
.base {
display: inline-flex;
align-items: center;
}
.primary {
composes: base;
background: #2563eb;
color: white;
}
这类写法有用,但很多团队不会把它当主线能力,因为:
- 可读性不一定比直接拆组件更好
- 跨文件组合会让依赖变绕
所以它更像“可用能力”,不是所有项目都要大量使用。
在 React / Vite 里怎么接
Vite 官方文档里已经把 CSS Modules 作为内建能力支持。
命名方式
button.module.css
card.module.scss
layout.module.less
基础写法
import styles from './card.module.css'
export function Card() {
return (
<section className={styles.card}>
<h2 className={styles.title}>标题</h2>
</section>
)
}