首页
/ airi 项目 UnoCSS 实战:用 transformerDirectives 在 CSS 中原生启用 @apply、@screen、theme() 与 icon()

airi 项目 UnoCSS 实战:用 transformerDirectives 在 CSS 中原生启用 @apply、@screen、theme() 与 icon()

2026-09-08 20:02:14作者:尤辰城Agatha

transformerDirectives 是 UnoCSS 提供的一个核心 transformer,它让开发者能够直接在 CSS 中书写 @apply@screentheme()icon() 四种指令,把工具类(utilities)与主题(theme)能力"搬进"普通 CSS 文件中。在 airi 仓库中,这条能力既被根配置集中启用(uno.config.ts),也被应用到桌面端渲染层(如 spotlight.vue)的复杂渐变与响应式样式编写上。读完本文,你将掌握四种指令的完整语法、别名配置方法,以及如何像 airi 一样在 Vue SFC 的 scoped style 里优雅地混用工具类与原生 CSS。

本文的原始知识卡位于仓库 .agents/skills/unocss/references/transformer-directives.md,下面结合 airi 的真实配置与源码逐层展开。

transformerDirectives 解决的问题

在纯 UnoCSS 工作流中,工具类通常直接写在模板的 class 属性中。但当你需要复用一组工具类、或想在 ::before / ::after 等伪元素、@keyframes 内部、媒体查询里组合样式时,模板 class 就无能为力了。transformerDirectives 正是为此而生——它扫描 CSS 源码,把指令转换为展开后的工具类声明,使"样式逻辑收敛到 CSS"与"工具类按需生成"两种范式可以共存。

安装与最小配置非常简单,在 UnoCSS 配置文件(通常是 uno.config.ts)中加入 transformer 即可:

import { defineConfig, transformerDirectives } from 'unocss'

export default defineConfig({
  transformers: [
    transformerDirectives(),
  ],
})

airi 中的启用方式:从根配置到各应用共享

airi 的根级 uno.config.ts 把共享配置封装为 sharedUnoConfig(),其中 transformers 同时启用了 directives 与 variant group 两个转换器,并对别名做了显式限定:

// 根 uno.config.ts(节选)
transformers: [
  transformerDirectives({
    applyVariable: ['--at-apply'],
  }),
  transformerVariantGroup(),
],

这里的关键点:显式传入 applyVariable: ['--at-apply'] 后,自定义属性别名只保留 --at-apply 一个(不再包含默认同义别名,详见下文别名一节)。随后,不同子应用通过 mergeConfigs 复用这份共享配置——例如 packages/stage-ui/uno.config.ts 直接合并了根配置:

import { defineConfig, mergeConfigs } from 'unocss'
import { histoireUnoConfig, sharedUnoConfig } from '../../uno.config'

export default mergeConfigs([
  sharedUnoConfig(),
  histoireUnoConfig(),
  defineConfig({}),
])

也有的应用独立声明完整配置,例如 apps/component-calling/uno.config.ts 中与 transformerVariantGroup() 并排启用了默认形态的 transformerDirectives()(不传参数)。仓库通过 pnpm workspace catalog 统一锁定了 unocss 版本(见 pnpm-workspace.yamlunocss: 66.7.5 的 catalog 条目),因此各应用获得的 transformer 行为是一致的。

@apply:在 CSS 中内联工具类

基础用法与带变体的写法

@apply 可以把一组工具类合并进任意 CSS 规则中,最直接的应用是封装"类组件化"的样式:

.custom-btn {
  @apply py-2 px-4 font-semibold rounded-lg;
}

当需要混入 hover:focus: 等**变体(variant)**时,由于变体值中带冒号与空格,必须使用引号包裹整个字符串,否则指令解析会失败:

.custom-btn {
  @apply 'hover:bg-blue-600 focus:ring-2';
}

CSS 自定义属性替代写法(vanilla CSS 兼容)

@apply 是标准 CSS 之外的指令语法,若希望样式文件仍能通过普通 CSS 解析器(例如防止原生 CSS 校验报错,或希望渐进增强),可改用 CSS 自定义属性别名:

.custom-div {
  --at-apply: text-center my-0 font-medium;
}

transformer 支持三个同义别名,配置如下:

transformerDirectives({
  applyVariable: ['--at-apply', '--uno-apply', '--uno'],
  // 或完全关闭别名能力:applyVariable: false
})
  • 默认别名是 --at-apply
  • 当传入数组时,数组内容即生效别名集合;
  • 设为 false 可关闭该特性(若你的样式表中恰好有用到这些自定义属性的场景,可避免误展开)。

airi 的真实用法:伪元素 + 复杂渐变 + 深色模式

自定义属性写法在 airi 桌面端被大量使用,因为它能出现在 ::before / ::after 这类伪元素规则中,与手写 position、mask-image 等原生属性无缝共存。下面摘自已搜索可见的 apps/stage-tamagotchi/src/renderer/pages/spotlight.vue(舞台 spotlight 输入框的光晕层):

.spotlight-card::before {
  pointer-events: none;
  --at-apply: 'bg-gradient-to-r from-primary-500/25 via-primary-500/12 to-transparent dark:from-primary-400/25 dark:via-primary-400/12 dark:to-transparent';
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 85%;
  height: 100%;
  mask-image: linear-gradient(120deg, white 100%);
}

.spotlight-card::after {
  pointer-events: none;
  --at-apply: 'bg-dotted-[primary-300/35] dark:bg-dotted-[primary-200/16]';
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  background-size: 10px 10px;
  content: '';
  mask-image: linear-gradient(165deg, white 30%, transparent 55%);
}

这个例子至少说明了三点实战要点:

  1. 值必须带引号:多段工具类(含空格与 dark: 变体)必须写成引号字符串,--at-apply 后的值才会被当作完整指令解析;
  2. 可以消费自定义规则与主题色bg-dotted-[primary-300/35] 对应根配置 uno.config.ts 中自定义的 bg-dotted-[...] 规则,而 primary-300 等色板来自 presetChromatic 生成的主题——证明 --at-apply 展开的是与模板 class 完全一致的完整工具类体系;
  3. 原生 CSS 与指令可并存:同一规则内 mask-imagebackground-sizeposition 等手写声明与 --at-apply 互不干扰,这让"UI 引擎层样式"保持为可读的 CSS。

另一个更简洁的用法见 apps/stage-tamagotchi/src/renderer/pages/index.vue(加载动效墙):

.wall {
  --at-apply: text-primary-300;

  --wall-width: 8px;
  animation: wall-move 1s linear infinite;
  background-image: repeating-linear-gradient(
    45deg,
    currentColor,
    currentColor var(--wall-width),
    #ff00 var(--wall-width),
    #ff00 calc(var(--wall-width) * 2)
  );
}

这里 --at-apply: text-primary-300 仅为元素注入文字颜色,随后手写的 background-image 通过 currentColor 引用它——指令与原生 CSS 通过颜色关键字协同工作,是"工具类用于取主题 token、原生 CSS 负责复杂绘制"的理想分工。

提示:从源码检索看,airi 的 Vue SFC 中统一采用 --at-apply 别名而非 @apply 关键字。若团队规约更看重可迁移性,可采用与根配置一致的 applyVariable 显式列表,避免样式被其他解析器误读。

@screen:把断点写成语义化的媒体查询

@screen <断点名> 会被转换为对应的媒体查询,断点名称来自主题 breakpoints(presetWind3 / preset-mini 默认提供 sm/md/lg/xl/2xl 等)。经典响应式栅格示例:

.grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
}

@screen sm {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@screen lg {
  .grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

相比手写媒体查询,@screen 的可读性更强,且断点值始终与主题定义保持单一事实来源。

断点变体:lt- 与 at-

除默认的"大于等于某断点"语义外,还支持两种变体:

/* 小于某断点才生效(max-width 语义) */
@screen lt-sm {
  .item { display: none; }
}

/* 仅在该断点区间生效(min/max 组合) */
@screen at-md {
  .item { width: 50%; }
}

这在"移动端隐藏某元素""仅中屏横排"等场景中比默认的向上兼容语义更精确。

theme():在任意 CSS 属性中读取主题令牌

theme('路径点分字符串') 允许在 CSS 值里直接读取 UnoCSS 主题配置,避免把 colors.blue.500 这类十六进制硬编码散落在样式文件中:

.btn-blue {
  background-color: theme('colors.blue.500');
  padding: theme('spacing.4');
  border-radius: theme('borderRadius.lg');
}

取值路径采用点分记号,映射到根配置 defineConfig({ theme: {...} }) 的对象结构。airi 根配置扩展了丰富的主题段(见 uno.config.ts),例如 theme.fontFamily 中定义的 cute / cutejp / sans-rounded 等字体族、以及 theme.animation 下一整套 keyframes / durations / timingFns 的入场退场动画配置——这些都可以通过 theme('fontFamily.cute')theme('animation.durations.fadeIn') 这类写法在 CSS 中引用,与 @apply、模板 class 读取的是同一份主题数据。

icon():把图标工具类转成 SVG 背景图

icon() 指令能把预设图标工具类(如 i-carbon-sun)转换为内联 SVG background-image。它依赖 preset-icons 提供图标解析——airi 根配置恰好启用了 presetIcons 并挂载了多个 Iconify 集合(见 uno.config.ts):

.icon-sun {
  background-image: icon('i-carbon-sun');
}

/* 第二个参数指定自定义颜色 */
.icon-moon {
  background-image: icon('i-carbon-moon', '#fff');
}

/* 颜色参数同样支持 theme() 读取主题色 */
.icon-alert {
  background-image: icon('i-carbon-warning', 'theme("colors.red.500")');
}

注意两点:颜色的使用方式是覆盖图标原色(本质是为内联 SVG 注入指定色值);而 theme("colors.red.500") 之所以用双引号,是因为它嵌套在外层单引号之内,且颜色参数本身是一个表达式而非普通字符串。该能力非常适合为那些无法通过 text-* 染色的背景型图标注入主题色。

完整组合示例:Card 组件

把四种指令放进一个组件级样式,即可看到它们如何协作(完整示例亦保留于原始知识卡):

.card {
  @apply rounded-lg shadow-md p-4;
  background-color: theme('colors.white');
}

.card-header {
  @apply 'font-bold text-lg border-b';
  padding-bottom: theme('spacing.2');
}

@screen md {
  .card {
    @apply flex gap-4;
  }
}

.card-icon {
  background-image: icon('i-carbon-document');
  @apply w-6 h-6;
}

从这份示例可以归纳协作模式:@apply 负责排版骨架与可见性,theme() 负责取值,@screen 负责响应式分段,icon() 负责图形资产。在 airi 这类多端(Web / Electron / Capacitor / tamagotchi 桌面应用)monorepo 中,把这份 CSS 放在公共 UI 包(如 packages/stage-ui)内,配合根配置的分层启用策略,即可保证各端获得一致的指令行为。

使用建议与注意事项

  • 作用域内使用:在 Vue SFC 中,<style scoped> 内的 @apply / --at-apply 展开发生在 UnoCSS 构建阶段,Vue 的 scoped 属性处理与其并不冲突,airi 的多个页面即采用这种组合。
  • 带空格与变体务必加引号@apply--at-apply 的值若包含 hover: / dark: / 空格分隔的多段类名,必须整体加引号,否则会被当作单一工具类解析失败。
  • 别名的统一管理:既然项目从根配置统一了 applyVariable,新增子应用时应优先复用 sharedUnoConfig() 或与其保持一致,避免不同应用间别名集合漂移。
  • 图标类需存在集合icon() 依赖 presetIcons 及其配置的 collections;如果图标名不在已加载集合内,构建期不会生成对应背景图。
  • 与 extractor 的配合@apply / --at-apply 等指令内出现的工具类由 transformer 收集并触发生成,airi 根配置的 content pipeline(见 uno.config.ts)同时覆盖 .vue.ts/.js 等文件,确保 CSS 中引用的类与模板中用到的类都进入统一扫描范围。

若需进一步了解相关的 preset-icons、preset-wind3、variant-group 与 shortcuts 能力,可继续查阅仓库 .agents/skills/unocss 下的 references 知识卡(如 preset-icons.mdpreset-wind3.mdtransformer-variant-group.mdcore-shortcuts.md),它们与本文共同构成 UnoCSS 在 airi 中的完整使用参考。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395