首页
/ UnoCSS Vite 集成实战指南:从多模式渲染到框架适配与 Monorepo 落地方案

UnoCSS Vite 集成实战指南:从多模式渲染到框架适配与 Monorepo 落地方案

2026-09-08 16:09:42作者:幸俭卉

本篇技术指南围绕 airi 开源仓库内置的 UnoCSS 技能文档展开,系统讲解在 Vite 工程中接入 UnoCSS 的完整链路——从插件安装、uno.config.ts 配置、virtual:uno.css 入口注入,到 global / vue-scoped / shadow-dom / per-module / dist-chunk 五种生成模式的选择,再到 React、Vue、Svelte、Solid、Preact、Elm、Lit Web Components 等框架的适配要点。文中结合 airi 仓库真实的多应用代码,展示单仓库(Monorepo)中共享 UnoCSS 配置、内容提取管线(content pipeline)与 Electron 渲染进程集成的工程化实践,读者读完后可以直接在自己的 Vite + Vue / React / Svelte / Electron 项目中复现整套配置方案。

本文论述基于 .agents/skills/unocss/references/integrations-vite.md 这一官方技能参考文档,并结合仓库内各应用(apps)、包(packages)的实际配置代码进行源码级印证。

一、为什么 Vite 插件是使用 UnoCSS 最常见的方式

UnoCSS 是原子化 CSS 引擎(Atomic CSS),其核心思路是"按需生成":扫描源码中的工具类(utility class),只产出真正被使用到的 CSS,从而避免传统 UI 框架引入整份样式文件的体积开销。

Vite 插件路径(unocss/vite)之所以是最主流的使用方式,是因为它直接嵌入了 Vite 的模块图与构建管线,具备以下天然优势:

  • 通过虚拟模块(virtual module)按需注入样式,无需手动维护 CSS 文件;
  • 开发模式毫秒级热更新,类名增删即时生效;
  • 构建阶段直接产出打包后的原子 CSS,无需额外 PostCSS 步骤;
  • 深度集成 Vite 的 dev server,可访问内置 Inspector 调试面板。

仓库佐证:airi 仓库根目录 uno.config.ts 聚合了 presetWind3presetAttributifypresetTypographypresetIconspresetScrollbar 与自研色板预设 presetChromatic,而各应用则通过 unocss/vite 插件消费这份配置(详见下文)。

二、最小接入三步走:安装、配置、注入

1. 安装依赖

pnpm add -D unocss

在 pnpm workspace(Monorepo)中,通常把 unocss 作为公共 devDependency 提升到根 package.json,各子应用共享同一版本。airi 仓库中所有 Vue 应用(component-calling、stage-tamagotchi、stage-web 等)均从根配置继承能力,正体现了这种共享模式。

2. 在 vite.config.ts 注册插件

// vite.config.ts
import UnoCSS from 'unocss/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    UnoCSS(),
  ],
})

真实工程对照:airi 的 apps/component-calling/vite.config.ts 中,插件顺序为 VueRouter → Vue → Unocss(),配置注释明确指向根目录的 uno.config("see uno.config.ts for config"),说明该插件会自动发现项目根目录下的 uno.config.ts,无需在此处重复传递配置对象。

3. 创建 uno.config.ts

// uno.config.ts
import { defineConfig, presetWind3 } from 'unocss'

export default defineConfig({
  presets: [
    presetWind3(),
  ],
})

presetWind3 是 Wind 3 预设,提供与 Tailwind CSS 兼容的类名体系(如 flexp-4bg-red-500),同时保持 UnoCSS 按需生成的性能特性。此外常用预设还包括:

预设 作用
presetWind3 Wind 3 风格工具类(Tailwind 兼容)
presetAttributify 支持属性模式,如 <div bg-red-500>
presetIcons 按需使用 Iconify 图标
presetTypography 提供 prose 排版工具类
presetWebFonts 自动拉取/托管 Web 字体
presetScrollbar 滚动条样式工具类

airi 的 apps/component-calling/uno.config.ts 是单应用完整示例:它同时启用了 presetWind3presetAttributifypresetTypographypresetWebFontspresetIconspresetChromatic,并挂载 transformerDirectives(支持 @apply 指令)与 transformerVariantGroup(支持 hover:(bg-red-500 text-white) 变体分组)两个 transformer。

4. 在入口文件注入样式

// main.ts
import 'virtual:uno.css'

virtual:uno.css 是 UnoCSS 提供的虚拟模块,开发模式下由插件实时生成全部被扫描到的原子 CSS,构建时则输出为产物中的实际样式文件。

真实工程对照:airi 的 apps/component-calling/src/main.ts 在入口处先导入 @unocss/reset/tailwind.css 做样式重置,再导入 uno.css(即 virtual:uno.css 的别名)。注意两点工程细节:

  • 正式工程常配合 @unocss/reset 提供与 Tailwind 一致的 reset 基础样式;
  • 类名中如包含 CSS 变量时,虚拟模块名称必须保持精确(virtual: 前缀不可省略)。

三、五种生成模式(Modes):场景与取舍

插件选项 mode 控制生成 CSS 的注入方式。技能文档共列出五种模式,前三种为稳定模式,后两种标记为实验性:

global(默认)

标准模式。所有扫描到的工具类统一生成一份全局 CSS,通过入口处的 virtual:uno.css 导入注入。适合绝大多数 SPA 应用:

import 'virtual:uno.css'

这是最简单也最推荐的默认选择,airi 各应用的入口(如 component-calling 的 main.ts)均采用该模式。

vue-scoped

将生成的 CSS 注入到 Vue SFC 的 <style scoped> 内,实现样式按组件作用域隔离,适合需要避免类名泄漏到第三方组件 DOM 的场景:

UnoCSS({
  mode: 'vue-scoped',
})

该模式牺牲少量编译期性能换取组件隔离性,通常仅在样式冲突敏感的业务场景中启用。

shadow-dom

面向使用 Shadow DOM 的 Web Components。由于 Shadow DOM 内的样式天然与外部文档隔离,全局注入的 CSS 无法命中影子树内部节点,因此需要把占位符 @unocss-placeholder 写入组件自身的 <style> 内,让插件在该位置注入按需样式:

const template = document.createElement('template')
template.innerHTML = `
<style>
  :host { ... }
  @unocss-placeholder
</style>
<div class="m-1em">...</div>
`

使用要点:

  • @unocss-placeholder 是必须的占位注释,缺了它样式无法注入到 Shadow Root 内;
  • :host 等 Shadow DOM 专属选择器可正常书写在占位符附近;
  • Lit 用户可配合 css 标签模板使用(见下文 Lit 一节)。

per-module(实验性)

按模块粒度生成 CSS 并支持可选的作用域限定。适用于依赖加载模块即拥有独立样式的架构,但会带来更多小的 CSS 片段,需在体积与隔离间权衡,仅建议在理解其机制后用于特定项目。

dist-chunk(实验性)

面向 MPA(多页面应用),在构建阶段按 chunk 生成对应 CSS,使得每个页面产物只携带自身用到的工具类,避免 MPA 场景下全局样式重复加载。

四、开发期两大利器:DevTools 与 Inspector

浏览器 DevTools 直接改类

开发期可通过额外导入 virtual:unocss-devtools 开启 DevTools 面板能力:

import 'virtual:uno.css'
import 'virtual:unocss-devtools'

重要警告:该功能基于 MutationObserver 监听 DOM 变化来检测类名。这意味着脚本动态添加的类也会被纳入生成范围,页面元素较多或频繁增删类名时会产生额外的扫描开销,生产构建中务必不要导入该虚拟模块。

Inspector 调试面板

开发模式下访问 http://localhost:5173/__unocss 即可打开 UnoCSS 内置 Inspector,用于:

  • 查看当前已生成的全部 CSS 规则及其源码来源;
  • 按文件查看实际被使用的类;
  • 在 REPL 中输入任意类名即时测试其展开结果。

Inspector 是排查"类写了却不生效 / 未被扫描到"类问题的第一现场:绝大多数此类问题都源于内容提取范围未覆盖对应文件(参见第七节 Vanilla JS/TS 提取配置)。

五、框架适配矩阵:一份配置打通各生态

技能文档给出的框架级配置要点可直接复制到对应工程,下面按框架逐一说明并给出插件顺序注意事项。

React

// vite.config.ts
import React from '@vitejs/plugin-react'
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    UnoCSS(), // Must be before React when using attributify
    React(),
  ],
}

关键点一(顺序):使用 @unocss/preset-attributify 时,UnoCSS() 必须放在 React() 之前,否则 JSX 中的布尔型属性写法会被 React 插件提前转换,导致 attributify 无法正确识别。

关键点二(类型检查):TS 编译器无法识别 attributify 产生的未知 JSX 属性,因此文档明确建议:使用 @unocss/preset-attributify 时,请把 tsc 从 build 脚本中移除(仅保留构建器执行,类型检查交由 IDE 或 CI 独立步骤完成),否则 pnpm build 会因 JSX 属性类型报错而失败。

Vue

Vue 与 @vitejs/plugin-vue 开箱即用,无需任何额外提取器,因为 Vue SFC 的 <template><script><style> 均在默认提取范围内。airi 仓库中全部 Vue 应用均如此接入,例如 apps/component-calling/vite.config.tsapps/stage-tamagotchi/electron.vite.config.ts(后者第 246 行 UnoCss() 位于渲染进程 renderer 插件列表内)。

Svelte

Svelte 需引入官方提取器以识别其特有语法:

import { svelte } from '@sveltejs/vite-plugin-svelte'
import extractorSvelte from '@unocss/extractor-svelte'
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    UnoCSS({
      extractors: [extractorSvelte()],
    }),
    svelte(),
  ],
}

extractorSvelte 使插件能够解析 Svelte 的 class:fooclass:foo={bar} 指令式类名绑定——这两种写法中的类名不会出现在普通的字符串字面量中,必须借助该提取器才能被扫描到。使用 Svelte 项目时还需把 @unocss/extractor-svelte 加入 devDependencies。

SvelteKit

与 Svelte 配置相同,仅将 svelte() 替换为 sveltekit()(来自 @sveltejs/kit/vite),提取器配置不变。

Solid

import UnoCSS from 'unocss/vite'
import solidPlugin from 'vite-plugin-solid'

export default {
  plugins: [
    UnoCSS(),
    solidPlugin(),
  ],
}

Solid 的 JSX 编译发生在 Vite 插件阶段之后,UnoCSS 只需位于其之前即可正常扫描模板字符串类名,无额外顺序陷阱。

Preact

import Preact from '@preact/preset-vite'
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    UnoCSS(),
    Preact(),
  ],
}

Elm

import Elm from 'vite-plugin-elm'
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    Elm(),
    UnoCSS(),
  ],
}

注意与 React 相反:Elm 场景下 Elm() 需在 UnoCSS() 之前,以先完成 Elm 源码到可扫描 JS 的转换。

Web Components(Lit)与 ::part 样式

组合使用 shadow-dom 模式与 shortcuts,并在组件样式中放置占位符:

UnoCSS({
  mode: 'shadow-dom',
  shortcuts: [
    { 'cool-blue': 'bg-blue-500 text-white' },
  ],
})
// my-element.ts
@customElement('my-element')
export class MyElement extends LitElement {
  static styles = css`
    :host { ... }
    @unocss-placeholder
  `
}

Shadow DOM 模式下可以额外使用 part-[<part-name>]:<utility> 语法为组件的 ::part 暴露区域生成样式,配合 exportparts 属性可让外部宿主针对性地美化组件内部结构——这是组件库作者实现"可定制但不穿透封装"的常用手段。

六、Monorepo 场景下如何共享 UnoCSS 配置

airi 仓库是一个典型 pnpm workspace Monorepo,其 UnoCSS 配置组织方式是本文最值得借鉴的工程实践:在仓库根目录维护一份"共享配置工厂",各应用通过 mergeConfigs 追加自身差异

根级共享配置

根目录 uno.config.ts 导出 sharedUnoConfig() 工厂函数,集中声明:

  • 完整 preset 集合(wind3、attributify、typography、icons、scrollbar、chromatic);
  • 两个 transformer(directives + variant group);
  • safelist(如 prose prose-sm m-auto text-left、全量 bg-primary 色阶及其透明度组合、设置页图标列表),用于强制保留无法静态扫描到的动态类名;
  • content.pipeline(见第七节);
  • 扩展的 rules(如自定义 mask-[...]bg-dotted-[...]drag-region 规则)与 theme.fontFamily 多语言圆体字体栈。

单应用若需要独立变体,可如 apps/component-calling/uno.config.ts 那样整体自定义,也可以使用 mergeConfigs 继承并叠加:

export default mergeConfigs([
  sharedUnoConfig(),
  defineConfig({
    presets: [
      presetWebFonts({
        fonts: { ...presetWebFontsFonts('none') },
        processors: createLocalFontProcessor(),
      }),
    ],
  }),
])

上面的真实片段来自 apps/stage-tamagotchi/uno.config.ts:桌面端应用通过 mergeConfigs 继承根配置,同时把 Web 字体供应商切换为 'none' 并用 createLocalFontProcessor() 做本地化字体处理——这保证了 Electron 打包产物无需在运行时访问网络字体 CDN。

网络受限环境下的字体预设实践

根配置文件开头的大段注释记录了一个很有价值的故障排查案例:在 Netlify 构建时 @unocss/preset-web-fonts 拉取字体元数据频繁抛出 ETIMEDOUT / ENETUNREACH。原因是 Node.js net 模块默认的 autoSelectFamilyAttemptTimeout(250ms)对 Happy Eyeballs 算法而言过短。解决方案是在配置顶部调用 setDefaultAutoSelectFamilyAttemptTimeout(1000) 将超时提升到 1 秒(见 uno.config.ts 第 37 行),GitHub Actions 与本地开发则不受影响。同时 component-calling 应用的字体预设还单独配置了超时告警参数 { warning: 5000, failure: 10000 }。这一案例提醒我们:UnoCSS 的 presetWebFonts 是构建期网络依赖,CI/CD 环境的连通性与超时需要提前验证。

七、Vanilla JS / TypeScript 的内容提取(content pipeline)

默认不提取 .js/.ts

这是 UnoCSS 与 Vite 集成时最容易踩的坑:默认配置下 .js.ts 文件不会被提取(class 必须出现在可静态分析的模板文件里)。若你在 TS 常量中集中管理类名(例如 shadcn-vue 的 cn() 拼类、设计令牌文件),就必须显式扩展 content 管线:

// uno.config.ts
export default defineConfig({
  content: {
    pipeline: {
      include: [
        /\.(vue|svelte|[jt]sx|html)($|\?)/,
        'src/**/*.{js,ts}',
      ],
    },
  },
})

airi 根配置 uno.config.ts(第 184–200 行)给出了更完整的生产级实践:include 中既保留默认正则 /\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/,又追加 (components|src)/**/*.{js,ts,vue} 与跨包目录 **/stage-ui/**/*.{vue,js,ts}**/ui/**/*.{vue,js,ts},从而让共享 UI 包(stage-ui、ui)内部以 JS/TS 形式书写的类名同样被扫描到;exclude 则排除了 node_modules(源码注释戏称 "DO NOT SCAN THE BLACK HOLE")。该文件注释同时说明:include 的这些补充配置对使用 shadcn-vue / shadcn-svelte 的项目是必需的——shadcn 组件把大量工具类写在 .ts 数据文件里,不纳入提取就会静默丢失全部样式。

魔法注释 @unocss-include

若不想全局放宽提取范围,可在单个 .ts 文件中加入魔法注释,强制该文件参与扫描:

// @unocss-include
export const classes = {
  active: 'bg-primary text-white',
}

文件顶部出现 @unocss-include 后,其中的字符串字面量类名都会被纳入提取。这是对"类名集中在常量对象中管理"这种写法的精准、低开销补充手段,尤其适合搭配前文 airi 根配置的 safelist 一起理解:静态可枚举的类(如全部色阶)进 safelist,动态拼接的类则依赖 include/魔法注释兜底。

八、Legacy 浏览器支持:与 @vitejs/plugin-legacy 配合

需要兼容旧浏览器时,在 Vite 插件数组中同时启用 legacy 选项与 @vitejs/plugin-legacy

import legacy from '@vitejs/plugin-legacy'
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    UnoCSS({
      legacy: {
        renderModernChunks: false,
      },
    }),
    legacy({
      targets: ['defaults', 'not IE 11'],
      renderModernChunks: false,
    }),
  ],
}

两点必须保持一致:

  • 两个插件的 renderModernChunks 取值必须同步(如上述代码均设为 false),否则现代 chunk 与 legacy chunk 之间的 CSS 归属会错位;
  • targets 按需调整,示例中的 ['defaults', 'not IE 11'] 表示兼容 browserslist 默认范围且不含 IE 11。

注意:modern/legacy 双产物模式会成倍增加构建产物,仅在确有旧内核浏览器访问需求时启用。airi 仓库的 netlify.toml / _headers 等部署配置未启用 legacy,印证现代桌面与 Web 应用默认无需该方案。

九、仓库内完整落地链路一览

为便于读者回到 airi 仓库逐一对照,下面把文中涉及的真实文件整理成一张对照表:

工程诉求 仓库文件(仓库根相对路径) 关键内容
共享/根级配置 uno.config.ts sharedUnoConfig()、content pipeline、WebFonts 网络超时修复
Vue 应用接入插件 apps/component-calling/vite.config.ts Unocss() 与 Vue/VueRouter 插件的排列
Vue 应用入口注入 apps/component-calling/src/main.ts @unocss/reset/tailwind.css + uno.css
单应用独立配置 apps/component-calling/uno.config.ts fontsource 字体预设、transformer、safelist
Electron 渲染进程集成 apps/stage-tamagotchi/electron.vite.config.ts renderer 插件列表中的 UnoCss()
Electron 侧配置合并 apps/stage-tamagotchi/uno.config.ts mergeConfigs + 本地化字体处理器
技能文档原始出处 .agents/skills/unocss/references/integrations-vite.md 本文所依据的官方参考

十、常见问题速查

  1. 类名不生效,但类确实写了:优先检查 (a) 入口是否导入了 virtual:uno.css;(b) 文件是否在 content pipeline 的 include 范围内(.ts/.js 默认不提取);(c) 浏览器 DevTools 面板地址 /__unocss 中该类的规则是否存在。
  2. Vue scoped 样式被全局工具类污染:改用 mode: 'vue-scoped',将工具类样式收进组件作用域。
  3. Web Components 内部类无效:改用 mode: 'shadow-dom',并在组件 <style> 中放置 @unocss-placeholder 占位符。
  4. 构建报 JSX 属性类型错误(React + attributify):从 build 脚本移除 tsc,或为该属性声明全局 JSX 类型扩展。
  5. CI 中 Web 字体预设超时:参考 airi 的实践调大 Node autoSelectFamilyAttemptTimeout,并为 presetWebFonts 配置 warning / failure 超时时间。
  6. 动态拼接的类名丢失:静态可枚举类入 safelist,散落 JS/TS 中的类用 @unocss-include 魔法注释,或扩展 content pipeline include。

结语

从最小三步接入到五种渲染模式,从七大框架的适配矩阵到 Monorepo 共享配置、Shadow DOM、Legacy 与 content pipeline,UnoCSS 的 Vite 插件在保留"按需生成、极致轻量"内核的同时,为不同架构的工程都提供了清晰的接入路径。airi 仓库既是对这份集成指南的最佳实践注解——根级共享配置 + 各端独立 merge、Electron 渲染进程接入、JS/TS 提取兜底,都是可以直接复刻的工程范式。对照本仓库的 uno.config.ts 与各应用的 Vite 配置逐一阅读,即可将文档知识转化为可上线的生产级配置。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
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
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
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
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527