文章目录
- 前言
- [UnoCSS 集成指南 - 小程序适配原理](#UnoCSS 集成指南 - 小程序适配原理)
-
- 问题背景
- [核心问题:小程序与 Web 的架构差异](#核心问题:小程序与 Web 的架构差异)
-
- [1. 样式隔离机制不同](#1. 样式隔离机制不同)
- [2. UnoCSS 默认工作模式](#2. UnoCSS 默认工作模式)
- 解决方案原理
-
- [1. 使用 `per-module` 模式](#1. 使用
per-module模式) - [2. 插件顺序:uni() 必须在前](#2. 插件顺序:uni() 必须在前)
- [3. presetUni() 预设](#3. presetUni() 预设)
-
- [a. 单位转换](#a. 单位转换)
- [b. 样式兼容性处理](#b. 样式兼容性处理)
- [c. 平台差异抹平](#c. 平台差异抹平)
- [4. envMode: 'build' 配置](#4. envMode: 'build' 配置)
- [1. 使用 `per-module` 模式](#1. 使用
- 完整的样式生成流程
-
- [H5 模式](#H5 模式)
- 小程序模式(正确配置后)
- 实际配置
-
- [1. main.ts - 移除条件编译](#1. main.ts - 移除条件编译)
- [2. vite.config.ts](#2. vite.config.ts)
- [3. uno.config.ts](#3. uno.config.ts)
- 验证结果
- 依赖版本
- 总结
前言
UnoCSS 集成指南 - 小程序适配原理
问题背景
在 uni-app 项目中集成 UnoCSS 后,H5 端样式正常显示,但小程序端样式完全加载失败。
核心问题:小程序与 Web 的架构差异
1. 样式隔离机制不同
Web (H5):
- 全局样式表:所有样式可以写在一个 CSS 文件中
- 样式作用域:通过 CSS 选择器控制
- 动态注入:支持运行时动态插入
<style>标签
小程序:
- 组件样式隔离:每个组件必须有独立的
.wxss文件 - 页面样式隔离:每个页面必须有独立的
.wxss文件 - 静态编译:不支持运行时动态注入样式
- 样式文件必须在编译时确定
2. UnoCSS 默认工作模式
UnoCSS 默认采用 全局模式 (global mode):
- 扫描所有源码,收集所有使用的原子类
- 生成一个统一的
uno.css文件 - 在入口文件导入这个全局样式表
这种模式在 Web 环境完美运行,但在小程序中会失败,因为:
- 小程序不支持全局样式表
- 每个组件/页面需要独立的样式文件
- 样式必须在编译时静态生成
解决方案原理
1. 使用 per-module 模式
typescript
UnoCSS({
mode: 'per-module' // 关键配置
})
原理:
- 全局模式 (global) :生成一个
uno.css包含所有样式 - per-module 模式 :为每个 Vue 组件单独生成样式
ComponentA.vue→ComponentA.wxssComponentB.vue→ComponentB.wxss- 每个组件只包含自己使用的原子类
为什么这样能解决问题:
- 符合小程序的组件样式隔离机制
- uni-app 编译器会将每个组件的样式提取到对应的
.wxss文件 - 避免了全局样式表的依赖
2. 插件顺序:uni() 必须在前
typescript
plugins: [
uni(), // 必须在前
UnoCSS(), // 必须在后
]
原理:
-
uni() 插件:负责将 Vue SFC 转换为小程序组件
- 解析
.vue文件 - 提取
<template>→.wxml - 提取
<script>→.js - 提取
<style>→.wxss
- 解析
-
UnoCSS 插件:负责生成原子 CSS
- 扫描模板中的 class
- 生成对应的 CSS 规则
- 注入到组件的
<style>块
为什么顺序重要:
- uni() 先处理:识别 uni-app 特有语法和组件结构
- UnoCSS 后处理:在 uni() 转换后的基础上注入样式
- 如果顺序反了:UnoCSS 无法正确识别小程序组件结构
3. presetUni() 预设
typescript
presets: [
presetUni(), // uni-app 专用预设
]
@uni-helper/unocss-preset-uni 解决的问题:
a. 单位转换
- Web:使用
px、rem、em - 小程序:使用
rpx(响应式像素) - presetUni 自动将 UnoCSS 的单位转换为
rpx
typescript
// 你写的代码
<view class="w-100 h-50">
// presetUni 转换后
.w-100 { width: 100rpx; } // 不是 100px
.h-50 { height: 50rpx; }
b. 样式兼容性处理
- 过滤小程序不支持的 CSS 属性
- 转换小程序特有的样式写法
- 处理伪类和伪元素的兼容性
c. 平台差异抹平
typescript
// 某些 CSS 特性在小程序中不支持
backdrop-filter // 部分小程序不支持
position: sticky // 小程序支持有限
presetUni 会自动处理这些差异。
4. envMode: 'build' 配置
typescript
envMode: 'build'
原理:
- 开发模式 (dev):UnoCSS 会进行热更新检查、样式预检查
- 构建模式 (build):跳过开发时的检查,直接生成最终样式
为什么小程序需要这个:
- 小程序的开发模式与 Web 不同
- 开发时的预检查可能误判小程序环境
- 强制使用构建模式避免兼容性问题
完整的样式生成流程
H5 模式
源码 → UnoCSS 扫描 → 生成 uno.css → 全局导入 → 浏览器渲染
小程序模式(正确配置后)
源码 → uni() 解析组件
↓
UnoCSS (per-module) 为每个组件生成样式
↓
ComponentA.vue → ComponentA.wxss
ComponentB.vue → ComponentB.wxss
↓
小程序运行时加载独立样式文件
实际配置
1. main.ts - 移除条件编译
typescript
// ❌ 错误:排除小程序
// #ifndef MP-WEIXIN
import "uno.css";
// #endif
// ✅ 正确:所有平台都导入
import "uno.css";
原因 :per-module 模式下,uno.css 只是一个入口标识,实际样式已经分散到各个组件中。
2. vite.config.ts
typescript
export default defineConfig(async () => {
const UnoCSS = await import("unocss/vite").then((m) => m.default);
return {
plugins: [
uni(), // 1. 先处理 uni-app 转换
UnoCSS({
mode: 'per-module' // 2. 按组件生成样式
}),
],
};
});
3. uno.config.ts
typescript
export default defineConfig({
presets: [
presetUni(), // uni-app 适配
presetAttributify(), // 属性化模式
presetIcons(), // 图标支持
],
transformers: [
transformerDirectives(), // @apply 等指令
transformerVariantGroup(), // 变体组语法
],
envMode: 'build', // 强制构建模式
});
验证结果
配置正确后,编译输出:
dist/dev/mp-weixin/
├── components/
│ ├── AudioPlayer.wxss ← 独立样式
│ ├── ModeSelector.wxss ← 独立样式
├── pages/
│ ├── practice/
│ │ └── practice.wxss ← 独立样式
每个 .wxss 文件只包含该组件使用的原子类,实现了样式隔离。
依赖版本
json
{
"unocss": "^66.5.10",
"@uni-helper/unocss-preset-uni": "^0.2.11",
"@dcloudio/vite-plugin-uni": "3.0.0-4080420251103001"
}
总结
小程序集成 UnoCSS 的核心是理解:
- 架构差异:小程序需要组件级样式隔离
- per-module 模式:让 UnoCSS 适配小程序的样式机制
- 插件顺序:确保 uni-app 编译流程正确
- presetUni:处理单位转换和平台兼容性
- envMode:避免开发模式的兼容性问题