更新记录

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-btncard 等 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-500dark: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/images
    • defaultExtension — 默认文件后缀,默认 .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 钩子:构建启动时扫描源码、提取类名、生成 CSS
    • transform 钩子:模板中含特殊字符的类名同步转义,确保 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 构建时预编译,零运行时开销,全端兼容。

version license uni_modules


目录


核心特性

特性 说明
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 动态类名支持

详细方案:docs/js-dynamic-class-support.md

解决的痛点

传统的纯模板正则扫描无法提取 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'])

无限维度主题

详细方案:docs/unlimited-dimensions.md

设计目标

  1. 无限维度 — 配置 N 个维度,前缀自动可用,无需改代码
  2. 顺序无关vip:male:dark:bg-gold-500dark:male:vip:bg-gold-500 生成完全相同的 CSS
  3. 构建时去重 — 同一语义的类名只生成一条 CSS 规则
  4. 运行时无感 — 开发者写任何顺序都能正确工作
  5. 向后兼容 — 现有 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 统一分配优先级,无冲突
零维度 / 单维度 完全向后兼容,无任何改动

背景图简写

详细方案:docs/background-image-shorthand.md

解决的痛点

<!-- 旧写法:路径冗长,重复书写 -->
<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')(绝对路径不拼接)

判断逻辑:

  1. /static 开头 → 视为绝对路径,原样返回
  2. 末尾匹配 .[a-zA-Z]{2,4} → 已有后缀,仅拼 basePath
  3. 否则 → 拼 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.jsCSSRegeneratorBuildRuleMatcher
  • 解析函数:BuildRuleMatcher 中匹配 bg-[url(...)] 后调用 resolveBgUrl()
  • 兼容性:不配置 backgroundImage 时行为与原生 bg-[url(...)] 完全一致

平台条件编译

一、核心功能与实现原理

uni-app 是一个跨端框架,但不同端对 CSS 的支持存在差异:

  • 小程序 不支持组合选择器(> * + *~+
  • App-Nvue 不支持视口单位(vw/vh/%/em/rem)、grid-*background-imagemin/max-widthbox-shadowvar()
  • 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 已知限制

  1. 属性穷举有限NVUE_UNSUPPORTED_PROPS 不追求完全覆盖 nvue 不支持的所有 CSS 属性,仅列举本插件可能产出的属性。如果你新增了产出 transform-styleperspective 等冷门属性的规则,需要扩展该列表。
  2. 百分比单位仅检测声明部分 — 选择器中的 %(如属性选择器 [class*="50%"])不会被误判,但极少数将百分比写在选择器自定义属性里的场景需要手动测试。
  3. 不修改用户代码 — 守卫只作用于 generated.css,不会自动改写 <style> 标签内的手写样式。
  4. 多行规则applyPlatformGuardLines() 按行包裹,复杂的 @media 嵌套规则需保证一行一条。
  5. 检测是模式匹配,非语法解析 — 极端构造(如字符串内包含 min-width:)可能误判,目前未发现实际案例。

4.5 与 uni-app 条件编译的关系

jz-uoscss 输出的注释完全遵循 uni-app 条件编译规范,由 uni-app 编译器在打包阶段处理。这意味着:

  • 不依赖任何额外的 PostCSS 插件
  • 与 uni-app 原生 <style> 内的 /* #ifdef H5 */ 注释完全等价
  • 升级 uni-app 框架时无需调整

内置规则

基础类(279+ 条)

类别 示例
间距 p- m- p-10padding: 10rpxpx-30padding-left/right: 30rpx-mt-10 → 负边距
尺寸 w- h- w-100width: 100rpxw-fullwidth: 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() 会同步改写:

  1. class="..." 静态字符串
  2. :class="..." 动态字符串字面量
  3. <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 → 行为与之前一致
  • 不配置 backgroundImagebg-[url(...)] 行为与之前一致
  • scanJS: false → 关闭 JS 扫描

常见问题

Q1:动态拼接的类名(bg-${color}-500)能识别吗?

部分能JSScriptExtractor 会扫描模板字符串的静态部分,但 ${...} 内的变量值无法静态分析。建议:

  • 使用 safelistFnsafelistPatterns 枚举所有可能值
  • 或使用 // @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.tsimport '@/uni_modules/jz-uoscss/generated.css'(非虚拟模块)
  • Vite 插件已正确注册(应在 uni() 之后)

Q5:能与 Tailwind / UnoCSS 共存吗?

可以并行使用,但不推荐。两者类名空间会冲突,且 jz-uoscss 的转义规则(/-s111-)会改写所有含特殊字符的 class,可能与其它框架的输出不兼容。


许可证

MIT License


相关文档

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。