更新记录
1.0.0(2026-06-13) 下载此版本
jz-uoscss 更新日志
v1.0.0 (2026-06-13)
新特性
JS 动态类名支持
- 新增
JSScriptExtractor— 专门从 JS/TS 代码中提取类名字符串- 自动识别字符串字面量、模板字符串、
@dynamic-css-include标注块中的类名 - 内置类名前缀白名单(
p-、m-、bg-、text-、btn、card等 40+ 前缀) - 内置保留关键字过滤(
true/false/if/else/return等)
- 自动识别字符串字面量、模板字符串、
BuildExtractor增强- 新增
_extractFromScriptBlock()处理 Vue SFC 中的<script>/<script setup>块 _extractDynamic()增强对三元表达式、对象 key、数组、模板字符串的解析- 支持从计算属性、JS 变量、对象简写、动态拼接中提取类名
- 新增
FileScanner扩展 — 默认扫描.js、.ts、.jsx、.tsx文件- 新增运行时 API
ensureClasses(classNames)— 显式声明运行时动态类名
无限维度主题(Unlimited Order-Independent Dimensions)
- 新增
compositeThemes.dimensions配置 — 支持任意数量的主题维度(品牌、会员、性别、明暗、地区……) - 规范化排序(Canonical Sort)
- 新增
canonicalSortThemes()/buildDimensionOrder()/registerDimensionOrder() - 类名
vip:male:dark:bg-gold-500与dark:male:vip:bg-gold-500规范化为同一选择器 - 构建时自动去重,CSS 体积减少
- 新增
parsePrefix()改造 — 解析时按维度声明顺序排序 + 同维度去重matchToCSSLine()改造 — 用规范化类名做转义和选择器生成- Vite
transform改造 — 模板与<script>中的类名同步规范化转义 - 新增运行时 API
initDimensionConfig(compositeThemes)— 注入维度配置setDimension(dimName, value)— 自动将theme-{value}class 挂载到根元素getDimension(dimName)— 获取当前维度值initDimensions()/getAllDimensions()/getDimensionClasses()
背景图简写(Background Image Shorthand)
- 新增
backgroundImage配置项basePath— 背景图根路径,默认/static/imagesdefaultExtension— 默认文件后缀,默认.png
- 简写类名
bg-[url(name)]- 无路径分隔符 + 无后缀:
bg-[url(background)]→url('/static/images/background.png') - 带子目录:
bg-[url(/group/background)]→url('/static/images/group/background.png') - 自带后缀:
bg-[url(/group/img.jpg)]→url('/static/images/group/img.jpg') - 绝对路径:
bg-[url(/static/other/img.png)]→ 保持原样不拼接
- 无路径分隔符 + 无后缀:
- 完全兼容主题前缀:
male:dark:bg-[url(avatar)]正常工作 - JS 中可用:
splitClassTokens已正确切分bg-[url(...)]token
平台条件编译(Conditional Compilation)
- 新增
PlatformGuard模块 — 自动为不兼容的 CSS 规则添加 uni-app 条件编译注释isH5Only()— 检测仅 H5 支持的选择器(> * + *、~ *等组合选择器)isNvueUnsupported()— 检测 nvue 不支持的单位(vw/vh/%/rem/em)和属性(grid-*/background-image/min-width/box-shadow/var()等)wrapH5Only()— 用/* #ifdef H5 */ ... /* #endif */包裹wrapNonNvue()— 用/* #ifndef APP-NVUE */ ... /* #endif */包裹applyPlatformGuard()— 按特征自动选择包裹策略(H5-only 优先于 NON-NVUE)
- 集成到
BuildRuleMatcher.matchToCSSLine()— 每条 CSS 规则在生成时自动加守卫
增强特性
safelist三种模式safelist: string[]— 直接列出类名safelistFn: (theme) => string[]— 函数式动态生成safelistPatterns: RegExp[]— 模式匹配规则
PaletteGenerator— 自动从colors配置生成调色板,供safelistFn/safelistPatterns枚举- HMR 模块缓存失效 —
configureServer钩子在 CSS 重新生成后调用moduleGraph.invalidateModule(),确保浏览器拿到最新 CSS - 类名转义新增
[]()等特殊字符支持
新特性
- Vite 插件集成
buildStart钩子:构建启动时扫描源码、提取类名、生成 CSStransform钩子:模板中含特殊字符的类名同步转义,确保 DOM class 与 CSS 选择器一一对应handleHotUpdate/configureServer钩子:HMR 支持safelist配置:确保动态拼接的类名被正确生成excludes配置:排除不需要扫描的目录
- 类名提取
- 支持
class="..."静态属性 - 支持
:class="..."动态绑定中的字符串字面量 - 支持小程序模板
class="{{...}}"
- 支持
- 类名转义(小程序特殊字符兼容)
/→-s111-:→-c111-#→-h111-.→-d111-%→-pc111-
- 多端兼容:H5、微信小程序、支付宝小程序、App-Vue、App-Nvue
新特性
- 初始版本发布
- 支持 uni_modules 规范
- 实现核心模块:规则引擎、CSS 生成器、类名提取器、解析器、伪类处理器、主题管理器
- 实现基础类系统(200+ 条规则):sizing / spacing / typography / color / shadow / border / borderRadius / position
- 实现实体类系统(50+ 个快捷方式):buttons / cards / inputs / badges / avatars
- 实现运行时系统:样式注入器(H5)、运行时管理器
- 实现预设系统:
presetWeapp/presetCommon - 支持伪类状态、多主题、静态 + 动态规则、shortcuts
- 内置缓存机制
平台兼容性
uni-app(4.53)
| Vue2 | Vue3 | Chrome | Chrome插件版本 | Safari | Safari插件版本 | app-vue | app-nvue | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| - | √ | √ | 1.0.0 | √ | 1.0.0 | √ | √ | √ | 1.0.0 | √ | 1.0.0 | - |
| 微信小程序 | 微信小程序插件版本 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | 1.0.0 | - | - | - | - | - | - | - | - | - | - | - |
jz-uoscss
专为 uni-app 设计的原子化 CSS 构建插件,基于 Vite 构建时预编译,零运行时开销,全端兼容。
目录
核心特性
| 特性 | 说明 |
|---|---|
| Vite 构建时预编译 | CSS 在构建阶段生成 generated.css,零运行时开销 |
| JS 动态类名提取 | 自动从 <script>、.js/.ts 文件中提取类名(计算属性、对象、模板字符串、三元表达式) |
| 无限维度主题 | 支持任意数量的主题维度(品牌、会员、性别、明暗、地区……),且前缀顺序无关 |
| 背景图简写 | bg-[url(name)] 自动补全根路径与扩展名,告别冗长的图片路径 |
| 平台条件编译 | 自动识别 H5/小程序/nvue 不兼容的 CSS,用 #ifdef / #ifndef 注释包裹 |
| 多端兼容 | H5、微信/支付宝/百度/字节/QQ/钉钉小程序、App-Vue、App-Nvue |
| HMR 热更新 | 修改源码或规则文件自动重新生成 CSS,并失效 Vite 模块缓存触发刷新 |
| 类名转义 | 自动处理小程序不允许的特殊字符(/、:、#、.、%、[、]、(、)) |
| 类 UnoCSS 语法 | 静态规则 + 动态正则规则 + shortcuts,学习成本低 |
| 分层类架构 | 基础类(279+)+ 实体类(15+ 模块)+ 主题前缀 + 断点前缀 + 伪类前缀 |
快速开始
1. 安装
将 jz-uoscss 目录置于项目的 src/uni_modules/ 下。
2. 注册 Vite 插件
// vite.config.ts
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import jzUoscssPlugin from './src/uni_modules/jz-uoscss/vite-plugin.js'
export default defineConfig(() => ({
plugins: [
uni(),
jzUoscssPlugin({
// 可选:动态拼接的类名兜底
safelist: ['flex-center', 'btn-primary'],
// 可选:排除目录(字符串包含匹配)
excludes: ['src/pages/legacy'],
// 可选:扫描 JS/TS 文件
scanJS: true,
// 可选:多维度主题
compositeThemes: {
dimensions: {
gender: ['male', 'female'],
mode: ['light', 'dark'],
},
},
// 可选:背景图简写
backgroundImage: {
basePath: '/static/images/common',
defaultExtension: '.png',
},
}),
],
}))
3. 引入生成的 CSS
// src/main.ts
import { createSSRApp } from 'vue'
import App from './App.vue'
// 引入构建时预生成的 CSS
// @ts-ignore
import '@/uni_modules/jz-uoscss/generated.css'
export function createApp() {
const app = createSSRApp(App)
return { app }
}
4. 在模板中使用
<template>
<view class="w-100 h-50 p-20 m-10 rounded-8 bg-primary">
<text class="text-16 text-white font-bold">Hello jz-uoscss</text>
</view>
</template>
Vite 插件配置
jzUoscssPlugin(options)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
safelist |
string[] |
[] |
强制包含的类名(即使源码中未出现也会生成) |
safelistFn |
(ctx) => string[] |
null |
函数式 safelist,可访问 theme / rules / shortcuts |
safelistPatterns |
RegExp[] |
[] |
模式 safelist,匹配规则名作为 safelist 候选 |
excludes |
string[] |
[] |
排除扫描的目录路径(字符串包含匹配) |
scanJS |
boolean |
true |
是否扫描 .js/.ts/.jsx/.tsx 文件提取类名 |
jsIncludes |
string[] |
[] |
仅扫描这些目录下的 JS/TS 文件 |
jsExcludes |
string[] |
[] |
排除这些目录下的 JS/TS 文件 |
themeVariants |
Object |
{} |
自定义主题前缀映射 { prefix: dataThemeValue } |
compositeThemes |
Object |
{} |
多维度/无限维度主题配置 { dimensions: { dim: [values] } } |
backgroundImage |
Object |
{} |
背景图简写 { basePath, defaultExtension } |
colors |
Object |
{} |
自定义颜色(用于 PaletteGenerator 枚举调色板) |
JS 动态类名支持
解决的痛点
传统的纯模板正则扫描无法提取 JS 代码中的类名,导致以下场景的 CSS 无法生成:
<script setup>
// ❌ 计算属性返回的类名(旧方案无法识别)
const computedClasses = computed(() => isActive ? 'bg-primary text-white' : 'bg-gray')
// ❌ JS 变量中的类名
const btnClass = 'px-30 py-16 rounded-8 bg-primary text-white'
// ❌ 动态拼接的类名
const colorClass = `bg-${theme}-500`
// ❌ 函数返回的类名
function getClass(type) {
return type === 'primary' ? 'btn-primary' : 'btn-default'
}
</script>
多层级提取策略
jz-uoscss 采用三层递进策略,覆盖 95%+ 场景:
┌──────────────────────────────────────────────────────────────────┐
│ Layer 1:JS/TS 源码正则扫描(自动,覆盖 80%) │
│ ├─ 字符串字面量 + 类名前缀白名单匹配 │
│ ├─ 模板字符串静态部分提取(去掉 ${...} 后扫描) │
│ └─ @dynamic-css-include 标注块 │
├──────────────────────────────────────────────────────────────────┤
│ Layer 2:增强型 :class 表达式解析(自动,覆盖 15%) │
│ ├─ 三元表达式:condition ? 'a' : 'b' │
│ ├─ 数组表达式:['a', 'b', cond ? 'c' : 'd'] │
│ └─ 对象表达式:{ 'class-a': condition } │
├──────────────────────────────────────────────────────────────────┤
│ Layer 3:safelist + 标注(兜底,覆盖剩余 5%) │
│ ├─ vite.config.ts 的 safelist / safelistFn / safelistPatterns │
│ ├─ // @dynamic-css-include 行内注释 │
│ └─ 运行时 ensureClasses() API │
└──────────────────────────────────────────────────────────────────┘
实现核心:JSScriptExtractor
文件:src/build/JSScriptExtractor.js
| 方法 | 说明 |
|---|---|
extract(code) |
入口,返回该段 JS/TS 代码中识别到的类名 Set<string> |
_extractFromStringLiterals |
扫描单/双引号字符串,按类名前缀白名单 + 保留字过滤 |
_extractFromTemplateLiterals |
扫描反引号模板字符串,剥离 ${...} 后取静态部分 |
_extractFromAnnotations |
识别 // @dynamic-css-include 后的字符串字面量 |
_looksLikeClassString |
启发式判断:是否包含 p-/bg-/btn 等 40+ 类名前缀 |
_isReserved |
过滤 true/false/if/else/return/this/class 等 |
BuildExtractor 同时增强了模板提取:
_extractFromScriptBlock(code)— 从 Vue SFC 的<script>/<script setup>块中提取_extractDynamic(code)— 增强:class="..."表达式:三元、对象 key、数组、模板字符串
标注模式
对于复杂场景,可在代码中加注释精准声明:
<script setup>
// @dynamic-css-include
const classes = {
primary: 'bg-primary text-white',
secondary: 'bg-secondary text-white',
}
// @dynamic-css-include
const btnClass = computed(() => type === 'primary' ? 'btn-primary' : 'btn-default')
</script>
safelist 三种模式
jzUoscssPlugin({
// 1) 直接列出
safelist: ['bg-primary', 'btn-primary'],
// 2) 函数式(可访问 theme / rules / shortcuts)
safelistFn: ({ theme }) => {
const list = []
for (const color of theme.colors || []) {
list.push(`bg-${color}-500`, `text-${color}-500`)
}
return list
},
// 3) 正则模式(按规则名匹配)
safelistPatterns: [
/^bg-(primary|secondary|success|warning|danger)$/,
/^text-(primary|secondary|success|warning|danger)$/,
],
})
运行时 API
import { ensureClasses } from '@/uni_modules/jz-uoscss'
// 显式声明可能用到的类名(构建时被收集,运行时 H5 注入)
ensureClasses(['bg-primary', 'text-white', 'px-30', 'py-16'])
无限维度主题
设计目标
- 无限维度 — 配置 N 个维度,前缀自动可用,无需改代码
- 顺序无关 —
vip:male:dark:bg-gold-500与dark:male:vip:bg-gold-500生成完全相同的 CSS - 构建时去重 — 同一语义的类名只生成一条 CSS 规则
- 运行时无感 — 开发者写任何顺序都能正确工作
- 向后兼容 — 现有
dark:bg-blue-600不受影响
配置示例(5 个维度)
// vite.config.ts
jzUoscssPlugin({
compositeThemes: {
dimensions: {
brand: ['nike', 'adidas', 'puma'], // 品牌维度
vip: ['gold', 'silver', 'normal'], // 会员维度
gender: ['male', 'female'], // 性别维度
mode: ['light', 'dark'], // 明暗维度
region: ['cn', 'us', 'eu'], // 地区维度
},
},
})
使用示例
<template>
<!-- 五维度并存 — 任意顺序,效果完全一致 -->
<text class="nike:gold:male:dark:cn:bg-red-500">品牌+会员+性别+暗色+中国</text>
<text class="cn:dark:male:gold:nike:bg-red-500">同上,顺序不同</text>
<!-- 部分维度 -->
<text class="nike:dark:bg-blue-600">品牌+暗色</text>
<text class="gold:male:text-yellow-500">会员+性别</text>
<!-- 单维度 -->
<text class="dark:text-white">仅暗色</text>
</template>
实现核心:规范化排序(Canonical Sort)
| 模块 / 函数 | 文件 | 作用 |
|---|---|---|
buildDimensionOrder(dimensions) |
src/build/shared.js |
根据维度声明顺序构建 { prefix → priority } 优先级映射 |
registerDimensionOrder(order) |
src/build/shared.js |
全局注册维度优先级 |
canonicalSortThemes(themes) |
src/build/shared.js |
按维度优先级稳定排序,同维度内按字母序 |
BuildRuleMatcher.parsePrefix() |
src/build/BuildRuleMatcher.js |
解析时调用 canonicalSortThemes + Set 去重 |
BuildRuleMatcher.matchToCSSLine() |
src/build/BuildRuleMatcher.js |
用规范化类名做转义和选择器生成 |
vite-plugin.js → transformSource |
vite-plugin.js |
模板/script 中的类名同步规范化转义 |
数据流
源码: class="dark:male:vip:bg-gold-500"
↓ extractor 提取
"dark:male:vip:bg-gold-500"
↓ parsePrefix
{ themes:['dark','male','vip'], baseClass:'bg-gold-500' }
↓ canonicalSortThemes (优先级 vip=0, gender=1, mode=2)
themes=['vip','male','dark']
↓ 重组 + 去重
canonicalClassName = "vip:male:dark:bg-gold-500"
↓ escapeClassName
"vip-c111-male-c111-dark-c111-bg-gold-500"
↓ 生成 CSS
.theme-vip.theme-male.theme-dark .vip-c111-male-c111-dark-c111-bg-gold-500 {
background-color: #eab308;
}
运行时 API
import {
initDimensionConfig,
setDimension,
getDimension,
initDimensions,
getAllDimensions,
getDimensionClasses,
} from '@/uni_modules/jz-uoscss'
// 1. 注入维度配置(一般在 main.ts / App.vue 中)
initDimensionConfig({
dimensions: {
gender: ['male', 'female'],
mode: ['light', 'dark'],
},
})
// 2. 切换维度(自动挂载 theme-male / theme-dark 到 <html>)
setDimension('gender', 'male')
setDimension('mode', 'dark')
// 3. 读取当前维度
getDimension('gender') // → 'male'
getAllDimensions() // → { gender: 'male', mode: 'dark' }
getDimensionClasses() // → 'theme-male theme-dark'
边界处理
| 场景 | 行为 |
|---|---|
同维度重复前缀 male:male:bg-blue |
parsePrefix 自动 Set 去重 |
同维度互斥值 male:female:bg-blue |
构建时不报错;运行时永远不会同时激活 |
未注册前缀 unknown:bg-blue |
当作伪类前缀处理,不参与主题系统 |
内置 light/dark 与自定义 mode 共存 |
buildDimensionOrder 统一分配优先级,无冲突 |
| 零维度 / 单维度 | 完全向后兼容,无任何改动 |
背景图简写
解决的痛点
<!-- 旧写法:路径冗长,重复书写 -->
<view class="bg-[url(/static/images/common/background.png)] bg-cover bg-center">
<!-- 新写法:自动补全根路径与扩展名 -->
<view class="bg-[url(background)] bg-cover bg-center">
配置
jzUoscssPlugin({
backgroundImage: {
basePath: '/static/images/common', // 默认 '/static/images'
defaultExtension: '.png', // 默认 '.png'
},
})
解析规则
| 写法 | 解析为 |
|---|---|
bg-[url(background)] |
url('/static/images/common/background.png') |
bg-[url(/group/background)] |
url('/static/images/common/group/background.png') |
bg-[url(/group/img.jpg)] |
url('/static/images/common/group/img.jpg')(保留原后缀) |
bg-[url(/static/other/img.png)] |
url('/static/other/img.png')(绝对路径不拼接) |
判断逻辑:
- 以
/static开头 → 视为绝对路径,原样返回 - 末尾匹配
.[a-zA-Z]{2,4}→ 已有后缀,仅拼 basePath - 否则 → 拼
basePath+defaultExtension
与主题前缀组合
<view class="dark:bg-[url(background)]"> <!-- 仅暗色 -->
<view class="ocean:bg-[url(wave)]"> <!-- 自定义主题 -->
<view class="male:light:bg-[url(avatar-male)]"> <!-- 多维度组合 -->
<view class="female:dark:bg-[url(/avatars/avatar-f.jpg)]"> <!-- 含子目录与后缀 -->
在 JS 中使用
const classes = 'bg-[url(background)] bg-cover'
const bgClass = `bg-[url(/icons/${name})]` // 动态拼接需要 safelist 兜底
const themedClass = 'male:light:bg-[url(bg)] male:dark:bg-[url(bg-dark)]'
实现位置
- 配置传递:
vite-plugin.js→CSSRegenerator→BuildRuleMatcher - 解析函数:
BuildRuleMatcher中匹配bg-[url(...)]后调用resolveBgUrl() - 兼容性:不配置
backgroundImage时行为与原生bg-[url(...)]完全一致
平台条件编译
一、核心功能与实现原理
uni-app 是一个跨端框架,但不同端对 CSS 的支持存在差异:
- 小程序 不支持组合选择器(
> * + *、~、+) - App-Nvue 不支持视口单位(
vw/vh/%/em/rem)、grid-*、background-image、min/max-width、box-shadow、var()等 - H5 是最完整的环境,支持所有标准 CSS
jz-uoscss 通过 PlatformGuard 模块在 CSS 生成阶段自动识别不兼容的规则,并用 uni-app 标准的条件编译注释包裹起来,让同一份 generated.css 可以安全地在所有平台编译运行。
实现原理
文件:src/uni_modules/jz-uoscss/src/build/PlatformGuard.js
// 1. 检测:分别匹配选择器层面与声明层面的不兼容特征
isH5Only(ruleText) // 选择器含 > * + *、~ * 等组合写法
isNvueUnsupported(ruleText) // 声明含 vw/vh/%、grid-*、background-image、var() 等
// 2. 包裹:注入 uni-app 条件编译注释
wrapH5Only(ruleText) // /* #ifdef H5 */ ...规则... /* #endif */
wrapNonNvue(ruleText) // /* #ifndef APP-NVUE */ ...规则... /* #endif */
// 3. 调度:按优先级自动选择策略
applyPlatformGuard(ruleText)
// 优先级:H5-only(最严格)> NON-NVUE > 无守卫
// 已包裹过的规则不重复处理
集成点:BuildRuleMatcher.matchToCSSLine() 在生成每条 CSS 规则后,会自动调用 applyPlatformGuard(),确保最终输出的 generated.css 中所有不兼容规则都被正确包裹。
检测特征清单
H5-only 选择器特征(小程序 + nvue 都不支持):
| 特征正则 | 示例 | 说明 |
|---|---|---|
>\s*\*\s*\+\s*\* |
.parent > * + * |
子选择器 + 相邻兄弟(space-x / space-y 实现常用) |
~\s*\* |
.foo ~ * |
通用兄弟组合 |
(?:^|[^>])\s\+\s\* |
.foo + * |
独立的相邻兄弟组合 |
Nvue 不支持特征(声明层面):
| 类别 | 示例 |
|---|---|
| 视口单位 | vw / vh / vmin / vmax |
| 相对字体 | em / rem |
| 百分比 | % |
| 尺寸 | min-width / max-width / min-height / max-height |
| 背景 | background-image / background(简写) |
| 网格 | grid-* / gap / row-gap / column-gap |
| 浮动滚动 | float / clear / overflow-x / overflow-y |
| 视觉效果 | box-shadow / filter / backdrop-filter |
| 文本 | white-space / word-break / word-wrap / text-overflow |
| 视图 | visibility / cursor / user-select / pointer-events |
| CSS 变量 | var(--xxx) |
输出示例
/* 标准规则:所有平台生效 */
.bg-primary { background-color: #007aff; }
/* H5-only 规则:仅 H5 编译时输出 */
/* #ifdef H5 */ .space-x-10 > * + * { margin-left: 10rpx; } /* #endif */
/* 非 nvue 规则:H5 + 小程序 + APP-VUE 编译时输出,nvue 跳过 */
/* #ifndef APP-NVUE */ .w-50-pc111- { width: 50%; } /* #endif */
/* #ifndef APP-NVUE */ .grid { display: grid; } /* #endif */
/* #ifndef APP-NVUE */ .shadow-md { box-shadow: 0 4px 6px rgba(0,0,0,0.1); } /* #endif */
uni-app 编译器读取这些注释后,会根据当前编译目标自动包含或剔除对应规则。
二、关键优势对比
| 对比项 | jz-uoscss PlatformGuard |
UnoCSS / Tailwind | 手写 SCSS |
|---|---|---|---|
| 自动识别 | ✅ 内置规则识别 + 单位/属性扫描 | ❌ 需要手动写 @apply 或 PostCSS 插件 |
❌ 完全手动 |
| 零配置 | ✅ 开箱即用 | ⚠️ 需配置 PostCSS 插件链 | ❌ 每条规则都要包裹 |
| uni-app 原生语法 | ✅ 直接输出 #ifdef/#ifndef 注释 |
❌ 需后处理 | ✅ 但需要手动维护 |
| 细粒度 | ✅ 每条 CSS 规则独立判断 | ❌ 通常按文件/模块切分 | ⚠️ 取决于书写习惯 |
| 维护成本 | ✅ 添加新规则自动获得守卫 | ⚠️ 新增规则要更新插件白名单 | ❌ 维护成本高 |
| 多端 CSS 体积 | ✅ 各端只输出兼容规则,体积最优 | ⚠️ 通常输出全量 | ✅ 但开发慢 |
| 构建可见性 | ✅ 注释清晰可读,便于调试 | ⚠️ 编译后难以追溯 | ✅ 注释可见 |
核心差异:jz-uoscss 的条件编译是对每条原子类规则做的,颗粒度比手写 SCSS 还要细,又完全自动化。其它原子化框架(UnoCSS / Tailwind)面向 Web,没有针对 uni-app 多端差异的内置守卫,需要额外的 PostCSS 插件链才能达到同等效果。
三、实践场景与最佳实践
场景 1:跨端兼容的间距系统
<!-- space-x-10 在 generated.css 中是组合选择器,仅 H5 输出 -->
<view class="flex space-x-10">
<view class="w-100 h-100 bg-primary"></view>
<view class="w-100 h-100 bg-success"></view>
</view>
构建产物:
/* #ifdef H5 */ .space-x-10 > * + * { margin-left: 10rpx; } /* #endif */
小程序 / nvue 编译时这条规则被剔除,开发者需为这些端补充等价的 gap-10 写法(Flex gap 在小程序与 H5 上均支持):
<view class="flex gap-10">
<view class="w-100 h-100 bg-primary"></view>
<view class="w-100 h-100 bg-success"></view>
</view>
场景 2:背景图 + nvue 兼容
<view class="bg-[url(banner)] bg-cover h-300">
<text>nvue 不支持 background-image,自动跳过</text>
</view>
构建产物:
/* #ifndef APP-NVUE */
.bg-\[url\(banner\)\] { background-image: url('/static/images/common/banner.png'); }
/* #endif */
最佳实践:在 nvue 页面中改用 <image> 组件 + 绝对定位,或在页面外层判断 process.env.UNI_PLATFORM 渲染不同结构。
场景 3:响应式百分比布局
<view class="w-50%">半宽(仅 H5 + 小程序 + App-Vue 生效)</view>
<view class="w-300">300rpx(所有端生效,包括 nvue)</view>
最佳实践:nvue 项目优先用 w-{N} 的 rpx 写法,避免百分比;混合栈项目可两种写法并存,由 PlatformGuard 自动选择性生效。
场景 4:组合选择器的替代方案
| H5-only 写法 | 全端推荐写法 |
|---|---|
space-x-{N} (> * + *) |
gap-{N} |
divide-y (> * + *) |
在子节点上加 border-t |
~ sibling 选择器 |
改用 v-if / 状态切换类名 |
场景 5:自定义规则也享受守卫
新增的自定义原子类规则只要返回的 CSS 含有上述特征,无需任何额外配置就会自动享受平台守卫:
// src/uni_modules/jz-uoscss/src/classes/base/custom.js
export const customRules = [
['my-glass', { 'backdrop-filter': 'blur(10rpx)' }], // → 自动 wrapNonNvue
['my-grid-3', { display: 'grid', 'grid-template-columns': 'repeat(3,1fr)' }], // → 自动 wrapNonNvue
]
四、技术规范与限制
4.1 注入位置
- 仅作用于
generated.css一份产物 - 在
BuildRuleMatcher.matchToCSSLine()内调用applyPlatformGuard() - 已包裹的规则(开头匹配
/^\s*\/\*\s*#if(n?def)\b/)跳过,避免链路上重复处理
4.2 检测层级
| 层级 | 范围 | 检测内容 |
|---|---|---|
| 选择器 | { 之前 |
组合选择器(H5-only) |
| 声明 | {...} 内部 |
单位、属性名、var()(NON-NVUE) |
注意:选择器层面只检测 H5-only 特征,不会因主选择器中带百分号等被误判为 NON-NVUE。
4.3 优先级
H5-only > NON-NVUE > 无守卫。一条规则同时命中两类特征时,使用更严格的 H5-only 包裹。
4.4 已知限制
- 属性穷举有限 —
NVUE_UNSUPPORTED_PROPS不追求完全覆盖 nvue 不支持的所有 CSS 属性,仅列举本插件可能产出的属性。如果你新增了产出transform-style、perspective等冷门属性的规则,需要扩展该列表。 - 百分比单位仅检测声明部分 — 选择器中的
%(如属性选择器[class*="50%"])不会被误判,但极少数将百分比写在选择器自定义属性里的场景需要手动测试。 - 不修改用户代码 — 守卫只作用于
generated.css,不会自动改写<style>标签内的手写样式。 - 多行规则 —
applyPlatformGuardLines()按行包裹,复杂的@media嵌套规则需保证一行一条。 - 检测是模式匹配,非语法解析 — 极端构造(如字符串内包含
min-width:)可能误判,目前未发现实际案例。
4.5 与 uni-app 条件编译的关系
jz-uoscss 输出的注释完全遵循 uni-app 条件编译规范,由 uni-app 编译器在打包阶段处理。这意味着:
- 不依赖任何额外的 PostCSS 插件
- 与 uni-app 原生
<style>内的/* #ifdef H5 */注释完全等价 - 升级 uni-app 框架时无需调整
内置规则
基础类(279+ 条)
| 类别 | 示例 |
|---|---|
间距 p- m- |
p-10 → padding: 10rpx;px-30 → padding-left/right: 30rpx;-mt-10 → 负边距 |
尺寸 w- h- |
w-100 → width: 100rpx;w-full → width: 100%;min-w-100 |
| Flex | flex / flex-col / items-center / justify-center / flex-1 / gap-10 |
| 颜色 | bg-primary / bg-white / text-primary / text-white / bg-red-500 |
| 边框 | border / border-2 / border-primary / rounded-8 / rounded-full |
| 阴影 | shadow / shadow-md / shadow-lg |
| 文字 | text-16 / text-center / font-bold / leading-1.5 / truncate |
| 定位 | absolute / relative / top-0 / inset-0 / z-10 |
| 动画 | transition / duration-300 / scale-105 |
| 布局 | block / flex / grid / hidden |
| 视觉 | opacity-50 / cursor-pointer / select-none |
实体类(15+ 模块)
buttons / cards / inputs / badges / avatars / dividers / skeletons /
masks / toasts / tags / flex / layouts / positions / borders / media / texts
<view class="btn btn-primary">主按钮</view>
<view class="card">卡片</view>
<view class="badge badge-success">成功</view>
<view class="flex-center">居中容器</view>
前缀系统
| 前缀类型 | 示例 | 说明 |
|---|---|---|
| 断点 | sm:/md:/lg:/xl: |
响应式 @media (min-width) |
| 内置主题 | light:/dark: |
明暗主题 |
| 自定义主题 | male:/female:/ocean: |
compositeThemes 注册 |
| 伪类 | hover:/active:/focus:/disabled: |
状态变体 |
支持任意组合:sm:dark:hover:bg-primary / male:dark:hover:bg-blue-600。
类名转义
小程序类名不允许包含特殊字符,构建时自动转义(DOM 上的 class 与 CSS 选择器同步改写):
| 原字符 | 转义后 | 示例 |
|---|---|---|
/ |
-s111- |
w-1/2 → .w-1-s111-2 |
: |
-c111- |
hover:bg-red → .hover-c111-bg-red:hover |
# |
-h111- |
bg-#F5F6FA → .bg--h111-F5F6FA |
. |
-d111- |
bg-primary-1.5 → .bg-primary-1-d111-5 |
% |
-pc111- |
w-50% → .w-50-pc111- |
vite-plugin.js → transformSource() 会同步改写:
class="..."静态字符串:class="..."动态字符串字面量<script>块中识别为类名的字符串(通过looksLikeClassString启发式判断,避免误伤import路径与 URL)
运行时 API
运行时方案仅适用于 H5。小程序与 App 必须使用 Vite 构建时方案。
主入口(@/uni_modules/jz-uoscss)
import {
// 初始化
setupDynamicCSS, // H5 运行时初始化
install, // Vue 插件安装
// 动态 CSS
generate, // 手动生成 CSS 字符串
ensureClasses, // 确保类名 CSS 已生成
addRule, // 动态添加规则
addShortcut, // 动态添加 shortcut
// 多维度主题
initDimensionConfig,
setDimension,
getDimension,
initDimensions,
getAllDimensions,
getDimensionClasses,
// 主题
presetThemes, lightTheme, darkTheme,
createCustomTheme, registerTheme, getTheme,
// 预设
presetWeapp, presetCommon,
// 规则数据
allBaseRules, allEntityClasses, baseClasses, entityClasses,
// 配置
getDefaultConfig, mergeConfig, deepMerge,
} from '@/uni_modules/jz-uoscss'
Vue 插件用法(H5)
import { createSSRApp } from 'vue'
import App from './App.vue'
import jzUoscss from '@/uni_modules/jz-uoscss'
export function createApp() {
const app = createSSRApp(App)
app.use(jzUoscss, {
rules: [[/^p-(\d+)$/, ([, d]) => ({ padding: `${d}rpx` })]],
shortcuts: [['flex-center', 'flex items-center justify-center']],
safelist: ['flex-center'],
})
return { app }
}
目录结构
src/uni_modules/jz-uoscss/
├── package.json
├── readme.md
├── changelog.md
├── index.js # 主入口(运行时 API)
├── vite-plugin.js # Vite 构建插件(装配 + 生命周期)
├── generated.css # 构建时自动生成(含条件编译注释)
├── config/
│ └── default.js # 默认配置(colors / spacing / breakpoints / theme)
└── src/
├── build/ # 构建期模块
│ ├── FileScanner.js # 文件扫描(.vue/.uvue/.nvue/.wxml/.axml/.js/.ts)
│ ├── BuildExtractor.js # 模板类名提取(含 :class 增强)
│ ├── JSScriptExtractor.js # JS/TS 类名提取
│ ├── BuildRuleMatcher.js # 规则匹配 + 主题前缀解析 + 规范化排序
│ ├── BuildCSSGenerator.js # CSS 文本生成
│ ├── RulesLoader.js # ES 模块动态规则加载
│ ├── CSSRegenerator.js # 生命周期编排
│ ├── PlatformGuard.js # 平台条件编译(#ifdef H5 / #ifndef APP-NVUE)
│ ├── PaletteGenerator.js # 调色板生成(safelist 枚举用)
│ ├── shared.js # 共享工具(escape/canonicalSort/breakpoints)
│ └── index.js
├── core/ # 运行时核心
│ ├── rule-engine.js
│ ├── generator.js
│ ├── extractor.js
│ ├── parser.js
│ ├── pseudo-handler.js
│ ├── theme-manager.js
│ └── utils.js
├── classes/
│ ├── base/ # 基础类(279+ 条)
│ │ ├── sizing.js
│ │ ├── spacing.js
│ │ ├── typography.js
│ │ ├── color.js
│ │ ├── shadow.js
│ │ ├── border.js
│ │ ├── borderRadius.js
│ │ ├── position.js
│ │ ├── animation.js
│ │ ├── layout.js
│ │ └── effects.js
│ └── entity/ # 实体类(15+ 模块)
│ ├── buttons.js / cards.js / inputs.js / badges.js / avatars.js
│ ├── dividers.js / skeletons.js / masks.js / toasts.js / tags.js
│ └── flex.js / layouts.js / positions.js / borders.js / media.js / texts.js
├── runtime/
│ ├── injector.js # H5 样式注入器
│ ├── dimension.js # 多维度主题运行时
│ └── index.js
├── themes/
│ ├── light.js / dark.js / custom.js
│ └── index.js
└── presets/
└── index.js # presetWeapp / presetCommon
迁移指南
从 v1.x 运行时方案迁移
// main.ts
- import DynamicCSS from '@/uni_modules/uni-dynamic-css'
- app.use(DynamicCSS, { ... })
+ // @ts-ignore
+ import '@/uni_modules/jz-uoscss/generated.css'
// vite.config.ts
+ import jzUoscssPlugin from './src/uni_modules/jz-uoscss/vite-plugin.js'
export default defineConfig(() => ({
plugins: [
uni(),
+ jzUoscssPlugin({ safelist: ['flex-center'] }),
],
}))
从 v2.0 升级到 v2.3
无破坏性变更,新增能力均向后兼容:
- 不配置
compositeThemes→ 行为与之前一致 - 不配置
backgroundImage→bg-[url(...)]行为与之前一致 scanJS: false→ 关闭 JS 扫描
常见问题
Q1:动态拼接的类名(bg-${color}-500)能识别吗?
部分能。JSScriptExtractor 会扫描模板字符串的静态部分,但 ${...} 内的变量值无法静态分析。建议:
- 使用
safelistFn或safelistPatterns枚举所有可能值 - 或使用
// @dynamic-css-include标注 + 在同作用域定义对象映射
Q2:小程序为什么必须使用构建时方案?
小程序运行环境不支持 document.createElement('style') 等 DOM API,无法运行时注入样式。所有样式必须在编译期就生成在 .wxss / .acss 中。
Q3:nvue 项目可以使用吗?
可以。PlatformGuard 会自动跳过 nvue 不支持的规则。但 nvue 本身仅支持 Flex 布局 + 部分 CSS 属性,建议优先使用 flex 系列与 rpx 单位。
Q4:HMR 修改后样式没生效?
jz-uoscss 在 CSS 重新生成后会调用 server.moduleGraph.invalidateModule() 失效 Vite 模块缓存,再 ws.send({ type: 'full-reload' }) 触发整页刷新。如仍未生效,请确认:
main.ts是import '@/uni_modules/jz-uoscss/generated.css'(非虚拟模块)- Vite 插件已正确注册(应在
uni()之后)
Q5:能与 Tailwind / UnoCSS 共存吗?
可以并行使用,但不推荐。两者类名空间会冲突,且 jz-uoscss 的转义规则(/ → -s111-)会改写所有含特殊字符的 class,可能与其它框架的输出不兼容。
许可证
MIT License
相关文档
docs/quick-start.md— 快速上手docs/architecture.md— 架构设计docs/rules-guide.md— 规则编写指南docs/api-reference.md— API 参考docs/composite-themes.md— 多维度主题(基础)docs/unlimited-dimensions.md— 无限维度(高级)docs/js-dynamic-class-support.md— JS 动态类名docs/background-image-shorthand.md— 背景图简写docs/theme-variant-plan.md— 主题变体方案docs/missing-classes-plan.md— 类名补全计划docs/test-plan.md— 测试方案docs/examples.md— 示例集docs/presets-guide.md— 预设系统docs/uni-dynamic-css.md— 总体设计

收藏人数:
下载插件并导入HBuilderX
下载插件ZIP
赞赏(0)
下载 2382
赞赏 0
下载 12239559
赞赏 1921
赞赏
京公网安备:11010802035340号