☰
CSS Modules 完全指南:用 Lightning CSS 实现局部作用域样式
2026/9/27 21:24:59 网站建设 项目流程
  • 前端
  • 开发工具

【免费下载链接】lightningcss

An extremely fast CSS parser, transformer, bundler, and minifier written in Rust.

项目地址:https://gitcode.com/gh_mirrors/li/lightningcss
点击查看免费下载

CSS 默认是全局命名空间:不同文件里同名类、id、自定义属性或@keyframes会互相覆盖,这在大型项目中极易引发样式冲突。Lightning CSS 原生支持 CSS modules 为骨架,结合 src/css_modules.rs 等源码深入讲解启用方式、exports数据结构、composes组合、:global例外、局部 CSS 变量、自定义命名 pattern、pure 模式以及 feature scoping 关闭等全部能力。

为什么需要 CSS modules:全局标识符的冲突问题

在默认情况下,CSS 中的所有标识符都是全局的。如果两个文件定义了相同的类名、id、自定义属性或@keyframes动画名,它们就会互相冲突、互相覆盖,最终样式取决于加载顺序。CSS modules 的解决思路是:把每个文件中定义的类名与标识符视为唯一——每个类名或标识符都会被重命名,加入一个唯一的 hash;同时导出一份映射表给 JavaScript,以便在模板或脚本中引用这些编译后的类名。

在 Lightning CSS 的实现中,这一机制由 src/css_modules.rs 支撑:文件顶部的模块注释明确写道——CSS modules 是“局部作用域 CSS 文件中名字的一种方式”,涵盖类名、id、keyframe 动画名以及任何使用CustomIdent类型的位置。启用 CSS modules 后,打印样式表时会为声明的名字追加 hash,并同步更新对这些名字的引用,最终返回原名到编译名的映射。

启用 CSS modules:API 选项与 CLI 标志

启用方式有两种:使用 JS API 时传入cssModules选项,使用 CLI 时加--css-modules标志。

import {transform} from 'lightningcss'; let {code, map, exports} = transform({ // ... cssModules: true, code: Buffer.from(` .logo { background: skyblue; } `), });

在 TypeScript 定义 node/index.d.ts 中,cssModules的类型是boolean | CSSModulesConfig——既可以传true直接启用,也可以传一个配置对象来细化行为(见下文各节)。cssModules为true时,内部会使用 src/css_modules.rs 中Config::default(),其默认值为:

  • pattern:[hash]_[local]
  • dashed_idents:false
  • animation、grid、container、custom_idents:true
  • pure:false

CLI 侧的处理在 src/main.rs:当传入--css-modules时,会读取--css-modules-pattern解析 pattern,读取--css-modules-dashed-idents开启局部变量,其余字段走默认值。

理解exports对象:原名字到编译名的映射

调用transform后,除了编译后的代码和 source map,还会额外返回一个exports对象。exports中的每个属性把源码 CSS 中的原始名字映射到编译后(已 hash 的)名字。你可以在 JavaScript 或模板文件中利用这份映射来引用编译后的类名与标识符。

针对上面示例,exports对象大致如下:

{ logo: { name: '8h19c6_logo', isReferenced: false, composes: [] } }

这个结构对应 Rust 侧的 CssModuleExport:

  • name:编译后的局部名字;
  • composes:该导出组合(compose)的其他名字列表;
  • is_referenced:该导出在当前文件中是否被引用。

CssModuleExport在开启serde/nodejsfeature 时会序列化为 camelCase(见 src/css_modules.rs 的serde(rename_all = "camelCase")),因此 JS 侧字段就是name、isReferenced、composes。TypeScript 侧的类型定义在 node/index.d.ts。

值得注意的细节:默认 pattern[hash]_[local]下,hash 由相对项目根目录的文件路径计算得出(见 src/css_modules.rs 的注释“Make paths relative to project root so hashes are stable”),这样 hash 在不同机器上保持一致,便于缓存与长期复用。

类组合(composes):实现样式混入

CSS modules 中的样式规则可以通过composes属性引用其他类,被引用的类会在组合类被使用的同时一并生效,这实际上提供了一种“样式混入”(style mixins)机制。

.bg-indigo { background: indigo; } .indigo-white { composes: bg-indigo; color: white; }

上例中,只要indigo-white类被应用,bg-indigo类也会一并应用。这在 Lightning CSS 返回的exports对象中体现如下:

{ 'bg-indigo': { name: '8h19c6_bg-indigo', isReferenced: true, composes: [] }, 'indigo-white': { name: '8h19c6_indigo-white', isReferenced: false, composes: [{ type: 'local', name: '8h19c6_bg-indigo' }] } }

composes引用有三种类型,对应 Rust 侧的 CssModuleReference 枚举:

  • local:本文件内的局部引用,name是编译后的名字;
  • global:引用全局(不 hash)名字;
  • dependency:引用其他文件导出的名字,带specifier指明依赖文件。

多个类可以一次性组合,用空格分隔:

.logo { composes: bg-indigo padding-large; }

在解析实现 src/properties/css_modules.rs 中,composes的值被解析为一系列CustomIdent(遇到from关键字停止),然后可选地跟随from+Specifier。组合的登记逻辑在 src/css_modules.rs 的handle_composes:它要求composes只能出现在单一简单类选择器中,否则返回InvalidComposesSelector错误;对于本地引用,编译后的名字会直接写入导出。

跨文件依赖:composes ... from

composes还可以通过from关键字引用另一个 CSS 文件中定义的类名:

.logo { composes: bg-indigo from './colors.module.css'; }

这会输出带有依赖信息的 exports 对象:

{ logo: { name: '8h19c6_logo', isReferenced: false, composes: [{ type: 'dependency', name: 'bg-indigo', specifier: './colors.module.css' }] } }

这里有一个重要的分工:使用transformAPI 时,解析这个依赖并应用目标类名是调用方(你的打包器或应用代码)的责任;而使用bundleAPI 时,依赖的解析与内联会自动完成。对应源码中,Specifier::SourceIndex(u32)这一变体正是“捆绑过程中用于标记来源索引”的(见 src/properties/css_modules.rs),bundler 会据此自动展开跨文件组合(见 src/bundler.rs 对 css modules 依赖的收集与内联)。

全局组合:composes ... from global

没有被 hash 的全局类也可以被组合,使用global关键字:

.search { composes: search-widget from global; }

对应Specifier::Global变体(src/properties/css_modules.rs),生成的引用类型为global。

全局例外::global伪类

在 CSS module 中,默认所有类选择器和 id 选择器都是局部的。你可以用:global伪类为单个选择器退出局部作用域:

.foo :global(.bar) { color: red; } .foo .bar { color: green; }

编译结果为:

.EgL3uq_foo .bar { color: red; } .EgL3uq_foo .EgL3uq_bar { color: #ff0; }

可以看到::global(.bar)中的.bar保持原样,而.foo被 hash;第二行中两个类都被 hash。#ff0是green的 minify 压缩结果,说明 CSS modules 与压缩可以同时工作。

局部 CSS 变量:dashedIdents选项

默认情况下,类名、id 选择器、@keyframes、@counter-style的名字以及 CSS grid 的线名和区域名都会被作用域化到定义它们的模块内。CSS 变量以及其他<dashed-ident>名字的作用域化则可以通过dashedIdents选项开启(JS API);CLI 对应--css-modules-dashed-idents标志。

let {code, map, exports} = transform({ // ... cssModules: { dashedIdents: true, }, });

开启后,CSS 变量会被重命名,避免与其他文件中的同名变量冲突。引用变量仍使用标准var()语法,Lightning CSS 会自动把它更新为局部作用域的变量名:

:root { --accent-color: hotpink; } .button { background: var(--accent-color); }

变为:

:root { --EgL3uq_accent-color: hotpink; } .EgL3uq_button { background: var(--EgL3uq_accent-color); }

注意 dashed ident 的重命名同样遵循 pattern(前缀会带上--),这一点可以从 src/css_modules.rs 的add_dashed看到:它把 pattern 应用到去掉--前缀的名字上,再补回--。

你还可以用from关键字引用其他文件中定义的变量:

.button { background: var(--accent-color from './vars.module.css'); }

以及用from global引用全局变量:

.button { color: var(--color from global); }

同一套语法也适用于其他使用<dashed-ident>语法的 CSS 值。例如,@font-palette-values规则和font-palette属性就用<dashed-ident>来定义与引用自定义字体配色,它们会像 CSS 变量一样被作用域化与引用改写。

在 Rust 配置中,对应的开关是Config.dashed_idents(见 src/css_modules.rs)。跨文件 dashed ident 引用在 src/css_modules.rs 的reference_dashed中实现:对于from 'file'的引用,会生成一个基于“源 hash + 变量名 + specifier”的占位名字,并登记到references映射中,由 bundler 或调用方解析。

自定义命名 pattern

默认情况下,Lightning CSS 会把文件名的 hash 前缀到每个类名和标识符前。你可以用pattern配置(JS API)或--css-modules-pattern(CLI)自定义这个命名规则。

pattern 是一个包含占位符的字符串,Lightning CSS 会按占位符填充,从而支持自定义前缀或调整作用域类名的命名约定:

let {code, map, exports} = transform({ // ... cssModules: { pattern: 'my-company-[name]-[hash]-[local]', }, });

当前支持的占位符如下:

占位符含义
[name]文件的基础名(不含扩展名)
[hash]完整文件路径的 hash
[content-hash]文件内容的 hash
[local]原始的类名或标识符

这些占位符对应 Rust 侧 Segment 枚举 的Name、Hash、ContentHash、Local四个变体,外加字面量Literal。pattern 的解析在 Pattern::parse:未知占位符会报UnknownPlaceholder错误,未闭合的方括号会报UnclosedBrackets错误。填充逻辑在 Pattern::write,其中[name]会取文件 stem,并把其中的.替换为-(my.module.css的 stem 会变成my-module)。

需要留意的是,[content-hash]只会在打包(bundle)场景中生效:如 src/bundler.rs 所示,只有当 pattern 包含 content-hash 时,bundler 才会为每个源文件计算内容 hash 并填充。

CSS Grid 与 pattern 的注意事项

注意:CSS grid 的线名可能是歧义的,因为浏览器会自动为每个 grid template area 生成以-start和-end结尾的线名。使用 CSS grid 时,你的pattern配置必须以[local]占位符结尾,这些自动生成的线名才能被正确引用。

let { code, map, exports } = transform({ // ... cssModules: { // ❌ [local] 必须在末尾,否则 // 自动生成的 grid 线名无法工作 pattern: '[local]-[hash]' // ✅ 应该这样写 pattern: '[hash]-[local]' } });
.grid { grid-template-areas: 'nav main'; } .nav { grid-column-start: nav-start; }

Pure 模式

和 webpackcss-loader的pure选项一样,Lightning CSS 也有pure选项,它强制每条规则至少使用一个 id 或类选择器:

let {code, map, exports} = transform({ // ... cssModules: { pure: true, }, });

启用后,Lightning CSS 会对没有至少一个 id 或类选择器的 CSS 规则(比如div)直接抛错。这很有用,因为像div这样的选择器不会被作用域化,会影响页面上的所有元素。对应配置字段是 Config.pure,默认关闭(false,见 src/css_modules.rs)。

关闭特性作用域:animation / grid / customIdents / container

grid、动画和自定义标识符的作用域化可以单独关闭。默认情况下,这些全部是开启的:

let {code, map, exports} = transform({ // ... cssModules: { animation: true, grid: true, customIdents: true, }, });

此外,TypeScript 定义中还包含container选项,用于控制@container名字的 hash(见 node/index.d.ts);Rust 侧对应的默认配置同样全部为 true(src/css_modules.rs 中的animation、grid、container、custom_idents均默认为 true)。若某个选项设为false,对应的名字将保持原样、不做 hash。

当前未支持的特性

Lightning CSS 目前并未实现其他 CSS modules 实现中的所有特性,其中一些未来可能会被加入:

  • 非函数形式的:local和:global伪类语法(即:local .foo这种老式写法);
  • @value规则——它已被标准 CSS 变量取代;
  • :import与:export这两种 ICSS 规则。

需要时,可以分别通过标准 CSS 变量、var()引用以及导出映射(exports)来替代上述能力。

从 JS 到 Rust 的完整调用链小结

把上文串联起来,一次 CSS modules 转换的完整链路是:

  1. JS 侧transform({cssModules: true | config})把配置映射到 Rust 的css_modules::Config(src/css_modules.rs);
  2. 解析样式表时,Composes属性被解析为名字列表 + 可选的Specifier(src/properties/css_modules.rs);
  3. 打印阶段,CssModule依据 pattern 计算每个名字的编译名,登记CssModuleExport,并更新var()、composes、grid、keyframes 等引用(src/css_modules.rs);
  4. 结果以 camelCase 结构序列化回 JS 的exports对象(node/index.d.ts);
  5. 使用bundleAPI 时,跨文件的composes from与var(--x from ...)依赖会被 bundler 自动解析与内联(src/bundler.rs)。

若你想在 Parcel 中全局启用 CSS modules(而不只针对.module.css文件),可以在package.json中配置@parcel/transformer-css的cssModules选项,或传入一个选项对象(参见 website/pages/docs.md)。CLI 下则使用--css-modules、--css-modules-pattern、--css-modules-dashed-idents三个标志组合(src/main.rs)。

  • 前端
  • 开发工具

【免费下载链接】lightningcss

An extremely fast CSS parser, transformer, bundler, and minifier written in Rust.

项目地址:https://gitcode.com/gh_mirrors/li/lightningcss
点击查看免费下载

相关推荐

上一篇:PDF补丁丁:一站式PDF处理解决方案,轻松解决文档编辑难题
下一篇:ASP.NET Core中的Blazor模板:Razor组件模板使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询