用 Vite + UnoCSS 搭一个本地共享样式库
在多个项目并行开发时,样式总是最先开始漂移的那个。同一套毛玻璃效果,这个项目写一遍,那个项目再写一遍,改参数时漏掉一处,视觉就开始不一致。与其每次靠记忆对齐,不如把这些东西收进一个地方统一维护。
这篇文章记录的是我搭建这个”样式库”的过程——不是发布到 npm 的那种库,而是一个本地可以随时预览、可以被其他本地项目引用的小工具。
为什么要这样做
问题很具体:我在几个项目里重复用同一套色彩 token 和毛玻璃样式。起初是直接复制 CSS 文件,改一个地方得同步到其他地方,迟早会出现遗漏。
除了共享的需求,还有一个原因——我需要一个地方能快速预览这些样式。新加一个渐变色,立刻能看到效果;毛玻璃参数调了,对比前后的视觉差异。一个展示页比在真实项目里反复刷新效率高得多。
技术选型
候选了三个方向:
- 纯 HTML + UnoCSS CDN:零配置,但没有热更新,样式多了文件难以组织
- Vite + UnoCSS 插件:现代构建工具,热更新快,配置简单,适合长期维护
- Astro + UnoCSS:适合文档站,对这个场景偏重
最终选 Vite。项目结构极简:
style-lab/ src/ style/index.css # CSS 变量(色彩 token、毛玻璃变量) main.js # 入口,渲染色板展示 index.html # 展示页 uno.config.js # UnoCSS 配置 package.jsonCSS 变量系统
所有颜色都用 CSS 自定义属性,而不是直接写死 hex 值:
:root { --primaryColor: #4879fe; --infoColor: #11b0ee; --successColor: #10c690; --warningColor: #ff8800; --errorColor: #ef5552;
/* 带透明度的版本,供渐变背景使用 */ --infoColor-10: rgba(17, 176, 238, 0.2); --infoColor-5: rgba(17, 176, 238, 0.1);
/* 毛玻璃 */ --glass-bg: rgba(255, 255, 255, 0.65); --glass-border: rgba(255, 255, 255, 0.4); --glass-shadow: rgba(31, 38, 135, 0.12);}暗色模式用 html.dark 类而非 @media (prefers-color-scheme: dark),原因是需要手动切换——媒体查询只跟随系统,无法独立控制。JS 初始化时读 localStorage,没有偏好则 fallback 到系统设置:
const saved = localStorage.getItem('theme')const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matchesif (saved === 'dark' || (!saved && prefersDark)) { document.documentElement.classList.add('dark')}暗色模式的变量覆盖:
html.dark { --glass-bg: rgba(27, 27, 27, 0.65); --glass-border: rgba(255, 255, 255, 0.15); --glass-shadow: rgba(0, 0, 0, 0.4); --bodyColor: #000000; --cardColor: #1b1b1b; /* ... */}这套结构的好处是:所有用了 CSS 变量的地方,切换主题时不需要任何额外处理,浏览器自动重算。
UnoCSS 配置的具名导出
关键设计:把 shortcuts、rules、theme 抽成具名导出,default export 继续给 style-lab 自己用。
export const shortcuts = [ ['frosted-glass', 'glass-medium rounded-xl'], ['card-glass-1', 'glass-panel-light rounded-xl'], // ...]
export const rules = [ ['glass-medium', { 'backdrop-filter': 'blur(10px)', '-webkit-backdrop-filter': 'blur(10px)', 'border': '1px solid var(--borderColor)', }], ['glass-panel-light', { 'background-color': 'var(--glass-bg)', 'border': '1px solid var(--glass-border)', 'box-shadow': '0 4px 16px 0 var(--glass-shadow)', 'backdrop-filter': 'blur(8px) saturate(120%)', }], // ...]
export const theme = { colors: { info: 'var(--infoColor)', success: 'var(--successColor)', // ... },}
// 自身 dev server 用export default defineConfig({ shortcuts, rules, theme, ... })消费方在自己的 uno.config.js 里:
import { shortcuts, rules, theme } from 'style-lab/uno'
export default defineConfig({ shortcuts: [...shortcuts, /* 项目自己的 */], rules: [...rules], theme: { colors: { ...theme.colors } },})本地包共享:npm file: 协议
在目标项目里安装:
npm install ../style-labnpm v7+ 会创建符号链接,node_modules/style-lab 指向源目录,不是拷贝。验证:
ls -la node_modules/style-lab# -> ../../../style-lab这意味着:
| 改了什么 | 目标项目需要做什么 |
|---|---|
src/style/index.css | 无需操作,Vite 热更新自动同步 |
uno.config.js | 重启 dev server |
消费方入口引入 CSS:
import 'style-lab/css' // package.json exports 字段映射到 src/style/index.csspackage.json 里的 exports 配置:
{ "exports": { "./css": "./src/style/index.css", "./uno": "./uno.config.js" }}几个踩过的细节
毛玻璃背景图的 filter 隔离
展示页的毛玻璃区域用了一张图片做背景,暗色模式下想压暗背景图,但不能影响上面的玻璃卡片。如果把 filter: brightness(0.5) 加在父容器上,子元素会一起变暗,玻璃效果失真。
解决方式是用 ::before 伪元素承载背景图,filter 只作用于伪元素:
.glass-bg::before { content: ''; position: absolute; inset: 0; border-radius: inherit; background: url('...') center/cover no-repeat; transition: filter 0.25s; z-index: 0;}html.dark .glass-bg::before { filter: brightness(0.45) saturate(0.7);}.glass-bg > * { position: relative; z-index: 1; /* 内容浮在伪元素之上 */}渐变背景不能用 UnoCSS 的透明度修饰符
UnoCSS 里写 from-info/10 意思是”info 色的 10% 透明度”,但当 info 被定义为 var(--infoColor) 时,UnoCSS 无法静态解析这个 CSS 变量的色值,透明度修饰符会静默失效。
这种情况直接在 CSS 变量层解决——预先定义好 --infoColor-10、--infoColor-5,在 shortcut 里直接引用:
['bg-info-gradient', { background: 'linear-gradient(135deg, var(--infoColor-10), var(--infoColor-5))', 'border-radius': '0.5rem'}],