UnoCSS Vite 集成实战指南:从多模式渲染到框架适配与 Monorepo 落地方案
本篇技术指南围绕 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 聚合了
presetWind3、presetAttributify、presetTypography、presetIcons、presetScrollbar与自研色板预设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 兼容的类名体系(如 flex、p-4、bg-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 是单应用完整示例:它同时启用了 presetWind3、presetAttributify、presetTypography、presetWebFonts、presetIcons 与 presetChromatic,并挂载 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.ts 与 apps/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:foo 与 class: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 | 本文所依据的官方参考 |
十、常见问题速查
- 类名不生效,但类确实写了:优先检查 (a) 入口是否导入了
virtual:uno.css;(b) 文件是否在 content pipeline 的 include 范围内(.ts/.js 默认不提取);(c) 浏览器 DevTools 面板地址/__unocss中该类的规则是否存在。 - Vue scoped 样式被全局工具类污染:改用
mode: 'vue-scoped',将工具类样式收进组件作用域。 - Web Components 内部类无效:改用
mode: 'shadow-dom',并在组件<style>中放置@unocss-placeholder占位符。 - 构建报 JSX 属性类型错误(React + attributify):从 build 脚本移除
tsc,或为该属性声明全局 JSX 类型扩展。 - CI 中 Web 字体预设超时:参考 airi 的实践调大 Node
autoSelectFamilyAttemptTimeout,并为presetWebFonts配置warning/failure超时时间。 - 动态拼接的类名丢失:静态可枚举类入
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 配置逐一阅读,即可将文档知识转化为可上线的生产级配置。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0634
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java01
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java00
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00