/icon() CSS 指令转换原理与实战)
UnoCSS transformer-directives 详解apply、screen 与 theme()/icon() CSS 指令转换原理与实战【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss本文基于 UnoCSS 官方文档 docs/transformers/directives.md 及unocss/transformer-directives包的源码实现编写。读完本文后你将掌握在 CSS 文件中使用apply、screen、theme()和icon()四类指令的完整用法、可配置选项如applyVariable、throwOnMissing以及该 transformer 基于 css-tree AST 与 MagicString 的底层转换机制。UnoCSS 的核心用法是在模板中写原子类但很多场景下我们仍然需要在 CSS 文件里维护自定义样式。unocss/transformer-directives就是在构建期介入 CSS 源码、把类似 Tailwind 语法的指令“编译”成真实 CSS 的源码转换器apply展开原子类、screen把断点名转成媒体查询、theme()读取主题配置值、icon()把图标名转成内联 SVG data URI。安装与启用按包管理器选择安装命令将其作为开发依赖加入项目pnpm add -D unocss/transformer-directives # 或 yarn add -D unocss/transformer-directives npm install -D unocss/transformer-directives bun add -D unocss/transformer-directives然后在uno.config.ts的transformers数组中注册import transformerDirectives from unocss/transformer-directives import { defineConfig } from unocss export default defineConfig({ // ... transformers: [ transformerDirectives(), ], })该 transformer 也内置在聚合包unocss中可以直接从那里导入入口导出 中有一行export { default as transformerDirectives } from unocss/transformer-directivesimport { transformerDirectives } from unocss选项一览从源码中的 TransformerDirectivesOptions 类型定义 可以看到完整可配置项比文档正文描述更完整选项类型默认值说明applyVariablefalse \| string \| string[][--at-apply, --uno-apply, --uno]把哪些 CSS 自定义属性当作apply指令处理传false关闭该特性throwOnMissingbooleantruetheme()/icon()引用不存在的值时是否抛错enforcepre \| post-控制 transformer 的执行阶段pre/post其中applyVariable的默认值在 transform.ts 的resolveApplyVariables()中解析未显式配置时取上述三个别名配置false时归一化为空数组。转换的触发条件在 index.ts 中transformer 定义了两层过滤idFilter使用unocss/core导出的cssIdRE只对 CSS 类文件生效codeFilter代码中包含apply、screen、theme(、icon(或任一applyVariable别名时才真正执行转换。仓库测试 test/transformer-directives.test.ts 中的source filter用例验证了这一行为普通样式.button { color: red }被过滤返回false而含apply或theme(colors.red)的代码会命中自定义别名applyVariable: --custom-apply同样能被codeFilter检测。这种“先粗筛再解析”的设计保证了不相关文件的 CSS 不会付出 AST 解析的成本。apply在 CSS 中展开原子类最基础的用法是在任意选择器内用apply引用 UnoCSS 工具类.custom-div { apply text-center my-0 font-medium; }构建后会展开为真实 CSS 声明.custom-div { margin-top: 0rem; margin-bottom: 0rem; text-align: center; font-weight: 500; }--at-apply兼容原生 CSS 语法的变体apply是 at-rule部分严格的 CSS 工具链校验器、格式化器可能不认可它。为此 UnoCSS 提供“把 CSS 自定义属性当作 apply 指令”的替代写法.custom-div { --at-apply: text-center my-0 font-medium; }该特性默认开启且默认识别三个别名--at-apply、--uno-apply、--uno这也是文档中screen示例大量使用--uno: grid-cols-2的原因。可通过选项定制或关闭transformerDirectives({ // 默认值 applyVariable: [--at-apply, --uno-apply, --uno], // 或者完全关闭 // applyVariable: false })需要包含:的规则必须加引号当要应用的工具类包含变体冒号时需要用引号包住整个值.custom-div { --at-apply: hover:text-red hover:font-bold; /* 或者 */ apply hover:text-red hover:font-bold; }对于apply引号是可选的——这是为了兼容部分格式化器会自动加/去引号的格式行为。从 apply.ts 的removeQuotes()可以看到无论输入是否带引号解析前都会先剥离成对的首尾引号因此两种写法等价。源码视角apply 是如何展开的apply的核心逻辑在 apply.ts 的parseApply()中可以归纳为五步提取工具类串从apply的 prelude 或自定义属性声明中取原始文本css-tree 会对声明值做表达式解析所以这里直接用code.original.slice()按 AST 位置截取原文去引号、去注释后按空白拆分展开 variant 分组先调用expandVariantGroup()展开v-hover:...之类的简写解析 token对每个类名调用uno.parseToken()把原子类解析为带位置信息的StringifiedUtil元组再按来源位置排序保证展开后的声明顺序稳定合并与落位普通声明直接拼接到apply语句之后而带父选择器parent如hover:产生的嵌套、带特殊选择器的工具类则生成独立规则块插入当前规则之后layer: properties的产物如 opacity 的property注册会统一插入到文件头部删除原指令用 MagicString 的code.remove()移除apply语句本身并清理因此产生的空规则块见 transform.ts 末尾的正则清理逻辑。另外值得注意的是handleApply()对Raw子节点会递归调用transformDirectives意味着media、supports等嵌套容器内的apply也能被正确处理——测试用例中body { apply sm:lg:md:xs:w-[40em]; }展开为多层嵌套media正是这条递归路径的验证。screen按断点名称生成媒体查询screen允许用断点名称代替手写媒体查询断点来自 theme.breakpoints 配置.grid { --uno: grid grid-cols-2; } screen xs { .grid { --uno: grid-cols-1; } } screen sm { .grid { --uno: grid-cols-3; } } /* ... */转换结果.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } media (min-width: 320px) { .grid { grid-template-columns: repeat(1, minmax(0, 1fr)); } } media (min-width: 640px) { .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } } /* ... */如果断点名在 theme 中不存在screen.ts 会直接抛出breakpoint xxx not found错误属于快速失败设计方便在开发期暴露拼写错误。screen lt-小于断点前缀lt表示“小于该断点”生成的媒体查询使用max-width.grid { --uno: grid grid-cols-2; } screen lt-xs { .grid { --uno: grid-cols-1; } } screen lt-sm { .grid { --uno: grid-cols-3; } }.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } media (max-width: 319.9px) { .grid { grid-template-columns: repeat(1, minmax(0, 1fr)); } } media (max-width: 639.9px) { .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }319.9px这样的上界由unocss/rule-utils的calcMaxWidthBySize()计算断点值减 0.1与lt-变体生成的媒体查询保持一致。screen at-精确落在断点区间前缀at表示“恰好处于该断点区间”生成min-width与max-width组合查询上界取下一个断点减 0.1.grid { --uno: grid grid-cols-2; } screen at-xs { .grid { --uno: grid-cols-1; } } screen at-xl { .grid { --uno: grid-cols-3; } } screen at-xxl { .grid { --uno: grid-cols-4; } }.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } media (min-width: 320px) and (max-width: 639.9px) { .grid { grid-template-columns: repeat(1, minmax(0, 1fr)); } } media (min-width: 1280px) and (max-width: 1535.9px) { .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } } media (min-width: 1536px) { .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }注意最后一段at-xxl没有上界——当该断点是 theme 中最后一个时源码中variantEntries[idx 1]为空就只输出min-width行为上与裸screen xxl等价。从源码结构看screen.ts 中还有一个版本适配细节它会检测 presets 中是否包含unocss/preset-wind4是则从theme.breakpoint单数读取断点否则从theme.breakpoints复数读取。也就是说如果你切换了 preset 大版本需要确认断点配置键名匹配否则screen会因找不到断点而报错。theme()用点语法读取主题配置theme()函数让你以点语法dot notation访问theme配置中的任意值.btn-blue { background-color: theme(colors.blue.500); }编译结果以当前仓库 preset 的调色板为准.btn-blue { background-color: #3b82f6; }带默认值的 theme()文档正文没有展开但源码 functions.ts 与测试用例明确支持第二个参数作为回退值当主题键不存在时使用逗号之后的默认值而不是抛错.btn { color: theme(not.exists.color, #fff); font-family: theme(not.exists.font, ui-sans-serif, system-ui); }会分别输出color: #fff;与font-family: ui-sans-serif, system-ui;。相对地缺键且无默认值theme(not.exists)、空默认值theme(not.exists, )、或两个参数间缺少逗号都会抛出错误——这些行为都有对应的测试断言见 test/transformer-directives.test.ts 的theme() with defaults用例。throwOnMissing默认true控制“找不到值时”是抛错还是静默保留若设置了默认值则即使throwOnMissing为true也不会抛错而是采用默认值。icon()把图标工具类编译为 SVG data URIicon()把图标名称转成具体的 SVG 图标用于background-image等场景.icon { background-image: icon(i-carbon-sun); }编译后是一个内联的 data URI.icon { background-image: url(data:image/svgxml;utf8,%3Csvg viewBox0 0 32 32 width1em height1em xmlnshttp://www.w3.org/2000/svg %3E%3Cpath fillcurrentColor dM16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6M5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z/%3E%3C/svg%3E); }前提依赖icon()依赖unocss/preset-icons会读取该 preset 的配置前缀、集合、缩放等请确保已添加该 preset。从 icon.ts 的实现看它会从当前 Uno 配置的 presets 中查找unocss/preset-icons复用其api与IconsOptions包括scale、prefix、collections、customizations等若找不到 preset则打印警告 “unocss/preset-icons not found, icon() directive will be keep as-is” 并保留原样不会报错——这与throwOnMissing的严格模式不同属于“优雅降级”行为。自定义图标颜色图标默认使用currentColor作为填充色。第二个参数可以指定自定义颜色颜色值本身还支持theme()引用.icon { background-image: icon(i-carbon-moon, #fff); background-image: icon(i-carbon-moon, theme(colors.red.500)); /* 使用主题色 */ }编译后currentColor会被替换为对应颜色URL 编码后.icon { background-image: url(data:image/svgxml;utf8,...%3Cpath fill%23fff dM13.503 5.414.../%3E%3C/svg%3E); background-image: url(data:image/svgxml;utf8,...%3Cpath fill%23ef4444 dM13.503 5.414.../%3E%3C/svg%3E); }实现上functions.ts 的icon分支会先对第二个参数执行transformThemeFn()因此theme(colors.red.500)这种嵌套字符串也能解析成真实色值再encodeURIComponent后传给transformIconString()后者在编码出的 SVG 中全局替换currentColor。底层机制小结综合 transform.ts 的实现整个转换流程是快速判定先用字符串includes判断是否含apply/screen/theme(/icon(/自定义属性别名不含则直接返回零解析开销AST 解析用 css-tree 的parse()解析开启parseCustomProperty这正是--at-apply能被识别为声明的前提同时关闭parseAtrulePrelude以避免screen等 at-rule 的参数被误解析遍历处理walk()遍历 ASTAtrule.screen交给handleScreen()Function节点theme/icon交给handleFunction()Rule交给handleApply()原地改写全程通过 MagicString 的overwrite/remove/appendLeft等做增量改写保留其余源码与映射信息因此 sourcemap 可以正确回溯递归与清理handleApply对Raw节点嵌套 at-rule 内部递归调用自身最后一轮再清除因指令移除而产生的空规则块。这种“AST 定位 MagicString 增量改写”的组合使得apply展开、screen替换、theme()值替换可以混合出现在同一份 CSS 中并正确共存同时输出仍是可被任意 CSS 消费方理解的普通 CSS。参考路径官方文档docs/transformers/directives.md包入口与过滤器packages-presets/transformer-directives/src/index.ts选项类型定义packages-presets/transformer-directives/src/types.ts主转换流程packages-presets/transformer-directives/src/transform.tsapply展开packages-presets/transformer-directives/src/apply.tsscreen处理packages-presets/transformer-directives/src/screen.tstheme()/icon()处理packages-presets/transformer-directives/src/functions.ts、packages-presets/transformer-directives/src/icon.ts行为测试含断点、theme 默认值、codeFiltertest/transformer-directives.test.ts主题/断点配置docs/config/theme.md【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考