UnoCSS @unocss/preset-legacy-compat 解析:通过后处理让原子 CSS 兼容老旧浏览器
【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss
@unocss/preset-legacy-compat是 UnoCSS 生态中一个“不生成任何规则、只做输出后处理”的特殊 preset。它的定位是集合了若干面向老旧浏览器的兼容性工具:把现代的颜色函数写法(空格分隔的rgb()/hsl())转回逗号分隔写法,并剥离现代颜色空间关键字(in oklch、in oklab)。读完后,你将掌握如何在不改动其他 preset 的前提下,通过两行配置让 UnoCSS 的产物在旧内核环境中正常渲染,并能从源码层面理解它的postprocess钩子是如何介入 UnoCSS 生成管线的。
定位:一个只做事后修正、不产出规则的 preset
这个包在仓库中的描述就是 “Collections of legacy compatibility utilities”(packages-presets/preset-legacy-compat/package.json)。官方文档页面 docs/presets/legacy-compat.md 明确指出:
This preset does not include any rules, it's applying postprocess to the generated CSS from other presets.
也就是说,它本身不注册任何原子规则,而是挂载到核心生成器的postprocess阶段,对其他 preset(如preset-wind3、preset-wind4)产出的 CSS 声明做字符串层面的修正。两个选项默认均为false,必须显式开启才会生效——这是一种“按需 opt-in”的设计,避免影响默认产物的现代化程度。
安装与基本用法
以 pnpm 为例(yarn/npm/bun 同理):
pnpm add -D @unocss/preset-legacy-compat然后在uno.config.ts中引入并配置:
import presetLegacyCompat from '@unocss/preset-legacy-compat' import { defineConfig } from 'unocss' export default defineConfig({ presets: [ // ...other presets presetLegacyCompat({ // options commaStyleColorFunction: true, legacyColorSpace: true }), ], })配置接口定义在 packages-presets/preset-legacy-compat/src/index.ts,只包含两个可选布尔字段:
export interface LegacyCompatOptions { /** * Convert modern space style color function to comma style. * * @example `rgb(255 0 0)` -> `rgb(255, 0, 0)` * @example `rgba(255 0 0 / 0.5)` -> `rgba(255, 0, 0, 0.5)` * * @default false */ commaStyleColorFunction?: boolean /** * Enable legacy color space conversion. * * @default false */ legacyColorSpace?: boolean }选项一:commaStyleColorFunction—— 把空格分隔的颜色函数转回逗号分隔
- 类型:
boolean - 默认值:
false
它解决什么问题。UnoCSS 自 v0.57.0 起把颜色函数从逗号分隔改为空格分隔(以对齐 Tailwind CSS 的写法),例如rgb(255, 0, 0)变成了rgb(255 0 0)。空格分隔语法依赖较新的 CSS Color 4 规范,老旧浏览器可能无法解析。开启commaStyleColorFunction后,产物会被转回传统写法:
rgb(255 0 0)→rgb(255, 0, 0)rgb(255 0 0 / 50%)→rgba(255, 0, 0, 50%)hsl(0 100% 50% / 50%)→hsla(0, 100%, 50%, 50%)
注意一个容易忽略的细节:当颜色带 alpha 通道(/之后有值)但函数名没有a后缀时,转换会自动补上a,即rgb→rgba、hsl→hsla,这正是旧写法表达透明度的标准形式。
源码实现。整个转换只有十几行,位于 packages-presets/preset-legacy-compat/src/comma-color.ts:
export function toCommaStyleColorFunction(str: string) { return str.replace(/((?:rgb|hsl)a?)\(([^)]+)\)/g, (_, fn: string, v: string) => { const [rgb, alpha] = v.split(/\//g).map(i => i.trim()) if (alpha && !fn.endsWith('a')) fn += 'a' const parts = rgb.split(/,?\s+/).map(i => i.trim()) if (alpha) parts.push(alpha) return `${fn}(${parts.filter(Boolean).join(', ')})` }) }从源码结构看,处理逻辑分三步:
- 用正则匹配所有
rgb()/rgba()/hsl()/hsla()调用; - 以
/拆出 alpha 部分,alpha 存在且函数名不带a时自动补a; - 把参数按“逗号或空白”切分(
/,?\s+/)后重新用,连接,因此无论输入原本是空格分隔还是逗号分隔,输出都统一为逗号分隔——这也意味着它对该 preset 之前的产物是幂等安全的。
测试印证。packages-presets/preset-legacy-compat/test/comma-color.test.ts 覆盖了上述所有分支,包括带 CSS 变量的 alpha:
expect(r('rgb(255 255 255 / 0.5)')).toBe('rgba(255, 255, 255, 0.5)') expect(r('hsl(0 0% 100% / 0.5)')).toBe('hsla(0, 0%, 100%, 0.5)') expect(r('rgb(248 113 113 / var(--un-bg-opacity)')) ).toBe('rgba(248, 113, 113, var(--un-bg-opacity))')选项二:legacyColorSpace—— 剥离现代颜色空间关键字
- 类型:
boolean - 默认值:
false
它解决什么问题。UnoCSS 的现代色板(尤其是 preset-wind4 中基于 oklch 的调色板,以及 wind3 中部分使用in oklab/in oklch的渐变插值写法)会产出形如color: oklch(0.65 0.2 200 in oklch)或带in oklch插值语法的 CSS。in <colorspace>这种“指定颜色空间”的语法在旧浏览器中不支持。开启legacyColorSpace后,声明中形如in oklch、in oklab的片段会被整体删除,以换取旧内核的兼容性。
源码实现。这个选项对应 packages-presets/preset-legacy-compat/src/index.ts 中的一行正则替换:
if (legacyColorSpace) { i[1] = i[1].replace(/\s*in (oklch|oklab)/g, '') }它精确匹配“可选空白 +in+ 空格 +oklch/oklab”,因此只会移除颜色空间关键字,不会误伤其他文本;且g标志保证一条声明中出现多次时全部替换。仓库中确实存在产出这类语法的规则来源,例如 packages-presets/preset-wind3/src/rules/background.ts 与 packages-presets/preset-wind4/src/rules/background.ts 中就含有in oklch相关声明,可推断该选项主要面向这类渐变/背景规则产出的插值语法。
底层机制:preset 如何通过postprocess钩子介入生成管线
理解这个 preset 的关键,是明白 UnoCSS 核心的后处理机制。在核心类型定义中(packages-engine/core/src/types.ts):
export type Postprocessor = (util: UtilObject) => void | UtilObject | (UtilObject | null | undefined)[]postprocess作用于“解析完变体、生成出 UtilObject(选择器 + 声明条目)之后、序列化为 CSS 字符串之前”的每个条目。preset 的实现就是返回这样一个钩子:
export const presetLegacyCompat = definePreset((options: LegacyCompatOptions = {}) => { const { commaStyleColorFunction = false, legacyColorSpace = false, } = options return { name: '@unocss/preset-legacy-compat', postprocess: (util) => { util.entries.forEach((i) => { let value = i[1] if (typeof value !== 'string') return if (commaStyleColorFunction) value = toCommaStyleColorFunction(value) if (value !== i[1]) i[1] = value if (legacyColorSpace) { i[1] = i[1].replace(/\s*in (oklch|oklab)/g, '') } }) }, } })从源码结构看,几个设计点值得注意:
- 只处理字符串型声明值:
typeof value !== 'string'直接跳过,避免误改非字符串条目; - 原地修改 UtilObject 的 entries:
postprocess回调允许直接变更传入对象(这也是类型签名允许返回void的原因); - 两个选项按固定顺序执行:先做逗号化,再剥离颜色空间关键字,二者互不干扰。
而这个钩子是在哪里被调用的?在 packages-engine/core/src/generator.ts 中,每个工具类应用完变体后都会经过:
return this.config.postprocess.reduce<UtilObject[]>( (utilities, p) => { const result: UtilObject[] = [] for (const util of utilities) { const processed = p(util) // 支持返回数组(拆分/丢弃条目)或单条 ... } return result }, [obj], )即所有 preset 与用户配置声明的postprocess函数按配置合并顺序(见 packages-engine/core/src/config.ts 中的getMerged('postprocess'))依次reduce到每个条目上。这意味着presetLegacyCompat在presets数组中的位置会决定它相对于其他后处理函数的执行顺序——通常放在数组末尾即可确保它看到“最终形态”的声明值。
端到端验证:真实生成产物的快照测试
仓库的集成测试 test/preset-legacy-compat.test.ts 演示了该 preset 与presetWind3组合后的实际输出。对text-red这类颜色工具,开启commaStyleColorFunction: true后产物为:
.text-red{--un-text-opacity:1;color:rgba(248, 113, 113, var(--un-text-opacity));}可以看到color值使用了旧式的rgba(248, 113, 113, ...)逗号写法,而同一选择器中的 CSS 变量声明不受影响——再次印证了它只对命中的颜色函数片段做定点替换。
使用建议与注意事项
- 它不会改写颜色本身:
legacyColorSpace只删除in oklch/in oklab关键字,不会把 oklch 色值换算成 rgb。若目标浏览器连oklch()函数本身都不支持,需要额外手段(如配合 docs/processors/lightningcss.md 提到的 LightningCSS processor 做降级编译),本 preset 解决的是“语法关键字层面”的兼容问题。 - 按需开启,默认全关:如果你的目标环境都支持 Color 4 空格语法与现代颜色空间,保持默认关闭即可,产物保持最新写法。
- 顺序敏感:它工作在 CSS 序列化的最后一刻,若你在自定义规则中直接写死
in oklch之类文本,也会被同样处理;反过来,任何在它之后(presets数组更靠后位置)运行的 postprocess 看到的就是修正后的值。 - 依赖面极小:从 packages-presets/preset-legacy-compat/package.json 看,该包唯一的依赖是
@unocss/core,无其他运行时负担。
小结
@unocss/preset-legacy-compat用一个极小的实现体量(一个正则转换函数 + 一个 postprocess 钩子)解决了 UnoCSS 现代化产物与老旧浏览器之间的两个典型摩擦点:空格分隔颜色函数和现代颜色空间关键字。理解它,顺带就理解了 UnoCSS preset 体系中最灵活的一个扩展点——postprocess:任何“对所有生成产物做统一事后修正”的需求,都可以参照 packages-presets/preset-legacy-compat/src/index.ts 的模式来实现。
【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考