用 Vite + UnoCSS 搭一个本地共享样式库

1155 字
6 分钟
用 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.json

CSS 变量系统#

所有颜色都用 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)').matches
if (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 配置的具名导出#

关键设计:把 shortcutsrulestheme 抽成具名导出,default export 继续给 style-lab 自己用。

uno.config.js
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: 协议#

在目标项目里安装:

Terminal window
npm install ../style-lab

npm v7+ 会创建符号链接,node_modules/style-lab 指向源目录,不是拷贝。验证:

Terminal window
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.css

package.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'
}],
Profile Image of the Author
xt53
Hi
分类
标签
站点统计
文章
10
分类
2
标签
19
总字数
22,614
运行时长
0
最后活动
0 天前

目录