diff --git a/docs-vitepress/api/compile.md b/docs-vitepress/api/compile.md
index 809e723046..e2f456285b 100644
--- a/docs-vitepress/api/compile.md
+++ b/docs-vitepress/api/compile.md
@@ -1723,7 +1723,7 @@ module.exports = defineConfig({
## MpxUnocssBase
-Mpx 内置的 unocss preset,继承自 `@unocss/preset-uno`,并额外提供小程序原子类的预设样式,安装示例如下:
+Mpx 内置的 unocss preset,继承自 `@unocss/preset-wind3`,并额外提供小程序原子类的预设样式,安装示例如下:
```bash
npm install -D @mpxjs/unocss-base
diff --git a/docs-vitepress/guide/platform/platform.md b/docs-vitepress/guide/platform/platform.md
new file mode 100644
index 0000000000..290c3407c1
--- /dev/null
+++ b/docs-vitepress/guide/platform/platform.md
@@ -0,0 +1,345 @@
+# 跨端输出基础
+
+Mpx以微信增强DSL为基础,支持跨端输出至多端小程序、web和客户端,包括支付宝、百度、抖音、京东、QQ等多端小程序平台,基于Vue的web平台,和基于react-native的ios、android及鸿蒙平台。
+
+
+## 跨端输出配置
+
+配置mpx进行跨端输出十分简单,找到项目构建的webpack配置,在@mpxjs/webpack-plugin的配置参数中设置mode和srcMode参数即可。
+
+```javascript
+new MpxwebpackPlugin({
+ // mode为mpx编译的目标平台,可选值有(wx|ali|swan|qq|tt|jd|web|ios|android|harmony)
+ mode: 'ali',
+ // srcMode为mpx编译的源码平台,目前仅支持wx
+ srcMode: 'wx'
+})
+```
+
+对于使用 @mpxjs/cli 创建的项目,可以通过在 `npm script` 当中定义 `targets` 来设置编译的目标平台,多个平台标识以`,`分隔。
+
+```javascript
+// 项目 package.json
+{
+ "script": {
+ "build:cross": "mpx-cli-service build --targets=wx,ali,ios,android"
+ }
+}
+```
+
+## 跨端条件编译
+
+Mpx跨端输出时在框架内针对不同平台的差异进行了大量的转换抹平工作,但框架能做的工作始终是有限的,对于框架无法抹平的部分我们会在编译和运行时进行报错提示,同时提供了完善的跨平台条件编译机制,便于用户自行进行差异化处理,该能力也能够用于实现区分平台进行业务逻辑实现。
+
+Mpx中我们支持了三种维度的条件编译,分别是文件维度,区块维度和代码维度,其中,文件维度和区块维度主要用于处理一些大块的平台差异性逻辑,而代码维度主要用于处理一些局部简单的平台差异。
+
+### 文件维度条件编译
+
+文件维度条件编译简单的来说就是文件为维度进行跨平台差异代码的编写,例如在微信->支付宝的项目中存在一个业务地图组件map.mpx,由于微信和支付宝中的原生地图组件标准差异非常大,无法通过框架转译方式直接进行跨平台输出,这时你可以在相同的位置新建一个map.ali.mpx,在其中使用支付宝的技术标准进行开发,编译系统会根据当前编译的mode来加载对应模块,当mode为ali时,会优先加载map.ali.mpx,反之则会加载map.mpx。
+
+文件维度条件编译能够与webpack alias结合使用,对于npm包的文件我们并不方便在原本的文件位置创建.ali的条件编译文件,但我们可以通过webpack alias在相同位置创建一个`虚拟的`.ali文件,并将其指向项目中的其他文件位置。
+
+```js
+ // 对于npm包中的文件依赖
+ import npmModule from 'somePackage/lib/index'
+
+ // 配置以下alias后,当mode为ali时,会优先加载项目目录中定义的projectRoot/somePackage/lib/index文件
+ // vue.config.js
+ module.exports = defineConfig({
+ configureWebpack() {
+ return {
+ resolve: {
+ alias: {
+ 'somePackage/lib/index.ali': 'projectRoot/somePackage/lib/index'
+ }
+ }
+ }
+ }
+ })
+```
+
+### 区块维度条件编译
+
+在.mpx单文件中一般存在template、js、stlye、json四个区块,mpx的编译系统支持以区块为维度进行条件编译,只需在区块标签中添加`mode`属性定义该区块的目标平台即可,示例如下:
+
+```html
+
+
+
+ 支付宝环境
+
+
+
+
+ 其他环境
+
+```
+
+### 代码维度条件编译
+
+如果只有局部的代码存在跨平台差异,mpx同样支持在代码内使用if/else进行局部条件编译,用户可以在js代码和template插值中访问`__mpx_mode__`获取当前编译mode,进行平台差异逻辑编写,js代码中使用示例如下。
+
+除了 `__mpx_mode__` 这个默认插值以外,有别的环境变量需要的话可以在mpx.plugin.conf.js里通过defs进行配置。
+
+```js
+if(__mpx_mode__ === 'ali') {
+ // 执行支付宝环境相关逻辑
+} else {
+ // 执行其他环境相关逻辑
+}
+```
+template代码中使用示例如下
+
+```html
+
+支付宝环境
+其他环境
+```
+
+JSON中的条件编译(注意,这个依赖JSON的动态方案,得通过name="json"这种方式来编写,其实写的是js代码,最终module.exports导出一个可json化的对象即可):
+```html
+
+```
+
+样式的条件编译:
+```css
+/*
+ @mpx-if (__mpx_env__ === 'someEvn')
+*/
+ /* @mpx-if (__mpx_mode__ === 'wx') */
+ .backColor {
+ background: green;
+ }
+ /*
+ @mpx-elif (__mpx_mode__ === 'qq')
+ */
+ .backColor {
+ background: black;
+ }
+ /* @mpx-endif */
+
+ /* @mpx-if (__mpx_mode__ === 'swan') */
+ .backColor {
+ background: cyan;
+ }
+ /* @mpx-endif */
+ .textSize {
+ font-size: 18px;
+ }
+/*
+ @mpx-else
+*/
+.backColor {
+ /* @mpx-if (__mpx_mode__ === 'swan') */
+ background: blue;
+ /* @mpx-else */
+ background: red;
+ /* @mpx-endif */
+}
+/*
+ @mpx-endif
+*/
+```
+
+### 属性维度条件编译
+
+属性维度条件编译允许用户在组件上使用 `@` 和 `|` 符号来指定某个节点或属性只在某些平台下有效。
+
+对于同一个 button 组件,微信小程序支持 `open-type="getUserInfo"`,但是支付宝小程序支持 `open-type="getAuthorize" `。如果不使用任何维度的条件编译,则在编译的时候会有警告和报错信息。
+
+比如业务中需要通过 button 按钮获取用户信息,虽然可以使用代码维度条件编译来解决,但是增加了很多代码量:
+
+```html
+
+
+
+```
+
+而用属性维度的编译则方便很多:
+
+```html
+
+```
+
+属性维度的编译也可以对整个节点进行条件编译,例如只想在支付宝小程序中输出某个节点:
+
+```html
+this is view
+```
+需要注意使用上述用法时,节点自身在构建时框架不会对节点属性进行平台语法转换,但对于其子节点,框架并不会继承父级节点 mode,会进行正常跨平台语法转换。
+```html
+
+
+ tap click
+
+// srcMode 为 wx 跨端输出 ali 结果为
+
+ tap click
+
+```
+上述示例为错误写法,假如srcMode为微信小程序,用上述写法构建输出支付宝小程序时,父节点 bindtap 不会被转为 onTap,在支付宝平台执行时事件会无响应。
+
+正确写法如下:
+```html
+
+
+ tap click
+
+// 输出 ali 产物
+
+ tap click
+
+```
+有时开发者期望使用 @ali 这种方式仅控制节点的展示,保留节点属性的平台转换能力,为此 Mpx 实现了一个隐式属性条件编译能力
+```html
+
+test
+```
+在对应的平台前加一个_,例如@_ali、@_swan、@_tt等,使用该隐式规则仅有条件编译能力,节点属性语法转换能力依旧。
+
+有时候我们不仅需要对节点属性进行条件编译,可能还需要对节点标签进行条件编译。
+
+为此,我们支持了一个特殊属性 `mpxTagName`,如果节点存在这个属性,我们会在最终输出时将节点标签修改为该属性的值,配合属性维度条件编译,即可实现对节点标签进行条件编译,例如在百度环境下希望将某个 view 标签替换为 cover-view,我们可以这样写:
+
+```html
+will be cover-view in swan
+```
+
+### 通过 env 实现自定义目标环境的条件编译 {#use-env}
+
+Mpx 支持在以上四种条件编译的基础上,通过自定义 env 的形式实现在不同环境下编译产出不同的代码。
+
+实例化 MpxWebpackPlugin 的时候,传入配置 env。
+
+```javascript
+// vue.config.js
+module.exports = defineConfig({
+ pluginOptions: {
+ mpx: {
+ srcMode: 'wx' // srcMode为mpx编译的源码平台,目前仅支持wx
+ plugin: {
+ env: "didi" // env为mpx编译的目标环境,需自定义
+ }
+ }
+ }
+})
+```
+
+#### 文件维度条件编译
+
+微信转支付宝的项目中存在一个业务地图组件map.mpx,由于微信和支付宝中的原生地图组件标准差异非常大,无法通过框架转译方式直接进行跨平台输出,而且这个地图组件在不同的目标环境中也有很大的差异,这时你可以在相同的位置新建一个 map.ali.didi.mpx 或 map.ali.qingju.mpx,在其中使用支付宝的技术标准进行开发,编译系统会根据当前编译的 mode 和 env 来加载对应模块,当 mode 为 ali,env 为 didi 时,会优先加载 map.ali.didi.mpx、map.ali.mpx,如果没有定义 env,则会优先加载 map.ali.mpx,反之则会加载 map.mpx。
+
+#### 区块维度条件编译
+
+在.mpx单文件中一般存在template、js、stlye、json四个区块,mpx的编译系统支持以区块为维度进行条件编译,只需在区块标签中添加`mode`或`env`属性定义该区块的目标平台即可,示例如下:
+
+```html
+
+
+ 该区块中的所有代码需采用支付宝的技术标准进行编写
+
+
+
+
+ 该区块中的所有代码需采用支付宝的技术标准进行编写
+
+
+
+
+ 该区块中的所有代码需采用支付宝的技术标准进行编写
+
+
+
+
+ 该区块中的所有代码需采用支付宝的技术标准进行编写
+
+```
+
+注意,如果多个相同的区块写相同的 mode 和 env,默认会用最后一个,如:
+
+```html
+
+ 该区块会被忽略
+
+
+
+ 默认会用这个区块
+
+```
+
+#### 代码维度条件编译
+
+如果在 MpxWebpackPlugin 插件初始化时自定义了 env,你可以访问`__mpx_env__`获取当前编译env,进行环境差异逻辑编写。使用方法与`__mpx_mode__`相同。
+
+#### 属性维度条件编译
+
+env 属性维度条件编译与 mode 的用法大致相同,使用 `:` 符号与 mode 和其他 env 进行串联,与 mode 组合使用格式形如 `attr@mode:env:env|mode:env`,为了不与 mode 混淆,当条件编译中仅存在 env 条件时,也需要添加 `:` 前缀,形如 `attr@:env`。
+
+对于同一个 button 组件,微信小程序支持 `open-type="getUserInfo"`,但是支付宝小程序支持 `open-type="getAuthorize" `。如果不使用任何维度的条件编译,则在编译的时候会有警告和报错信息。
+
+如果当前编译的目标平台是 wx,以下写法 open-type 属性将被忽略
+
+```html
+
+```
+
+如果当前 env 不是 didi,以下写法 open-type 属性也会被忽略
+```html
+
+```
+
+如果只想在 mode 为 wx 且 env 为 didi 或 qingju 的环境下使用 open-type 属性,则可以这样写:
+```html
+
+```
+
+env 属性维度的编译同样支持对整个节点或者节点标签名进行条件编译:
+
+```html
+this is a view component
+this is a view component
+```
+如果只声明了 env,没有声明 mode,跨平台输出时框架对于节点属性默认会进行转换:
+```html
+
+this is a view component
+this is a view component
+```
+
+
+## 环境API跨端抹平
+
+## Webview环境跨端抹平
+
+
+
diff --git a/docs-vitepress/guide/platform/rn-unocss.md b/docs-vitepress/guide/platform/rn-unocss.md
new file mode 100644
index 0000000000..0213182c20
--- /dev/null
+++ b/docs-vitepress/guide/platform/rn-unocss.md
@@ -0,0 +1,407 @@
+## 使用原子类
+
+Mpx 使用 unocss 作为原子类引擎,使得在使用 mpx 开发跨端项目也可以使用原子类。如果要在你的项目当中集成原子类的能力,具体操作请参照接入文档。
+
+受限于 RN 平台的样式规则能力限制,绝大部分的 unocss 提供的原子类并不能在跨 RN 项目当中使用,以下是目前所支持的原子类:
+
+> 对于不支持的原子类,在项目编译构建阶段会将不支持的原子类以 error 形式提示,且最终的编译产物当中不会产出对应的原子类结果
+
+
+#### [Typography](https://windicss.org/utilities/general/typography.html#typography)
+
+| 规则 | 是否支持 | 备注 |
+| :------------------------ | :------: | :---------------------------------------------------: |
+| Font family | 是 | |
+| Font size | 是 | |
+| Font style | 是 | |
+| Font weight | 是 | |
+| Font Variant Numberic | 否 | |
+| Hyphens | 否 | |
+| Letter spacing | 是 | |
+| Line height | 是 | |
+| Tab size | 否 | |
+| Text alignment | 是 | |
+| Text color | 是 | |
+| Text decoration | 是 | |
+| Text decoration style | 是 | ios 支持,安卓不支持 |
+| Text decoration thickness | 否 | |
+| Text underline offset | 否 | |
+| Text decoration opacity | 是 | |
+| Text indent | 否 | |
+| Text opacity | 是 | |
+| Text shadow | 是 | |
+| Text stroke | 否 | |
+| Text stroke color | 否 | |
+| Text transform | 是 | |
+| Vertical alignment | 是 | 安卓支持,ios 不支持,且只支持 auto/top/bottom/center |
+| White space | 否 | |
+| Word break | 否 | |
+| Write mode | 否 | |
+| Writing orientation | 否 | |
+
+#### Svg
+
+不支持
+
+#### [Variants](https://windicss.org/utilities/general/variants.html)
+
+* Screen Variants
+
+Mobile First
+
+| Varaint | 规则 | 备注 |
+| :------------- | :------: | :---: |
+| sm | @media (min-width: 640px) | |
+| md | @media (min-width: 768px) | |
+| lg | @media (min-width: 1024px) | |
+| xl | @media (min-width: 1280px) | |
+| 2xl | @media (min-width: 1536px) | |
+
+Desktop First
+
+| Varaint | 规则 | 备注 |
+| :------------- | :------: | :---: |
+| {
- return str.replace(escapeReg, function (match) {
- if (escapeMap[match]) return escapeMap[match]
- // unknown escaped
- return '_u_'
- })
-})
-
function concat (a = '', b = '') {
return a ? b ? (a + ' ' + b) : a : b
}
@@ -219,6 +190,42 @@ function transformStyleObj (styleObj) {
return transformed
}
+
+const createLayer = (isNativeStyle) => {
+ const layerMap = {
+ preflight: isNativeStyle ? [] : {},
+ uno: isNativeStyle ? [] : {},
+ app: isNativeStyle ? [] : {},
+ normal: isNativeStyle ? [] : {},
+ important: isNativeStyle ? [] : {}
+ }
+
+ const genResult = () => {
+ if (isNativeStyle) {
+ return [
+ ...layerMap.preflight,
+ ...layerMap.uno,
+ ...layerMap.app,
+ ...layerMap.normal,
+ ...layerMap.important
+ ]
+ } else {
+ return Object.assign({}, layerMap.preflight, layerMap.uno, layerMap.app, layerMap.normal, layerMap.important)
+ }
+ }
+
+ const mergeToLayer = (name, ...classObjs) => {
+ const layer = layerMap[name] || layerMap.normal
+ return isNativeStyle ? layer.push(...classObjs) : Object.assign(layer, ...classObjs)
+ }
+
+ return {
+ mergeToLayer,
+ layerMap,
+ genResult
+ }
+}
+
function isNativeStyle (style) {
return Array.isArray(style) || (
typeof style === 'object' &&
@@ -254,55 +261,70 @@ export default function styleHelperMixin () {
return concat(staticClass, stringifyDynamicClass(dynamicClass))
},
__getStyle (staticClass, dynamicClass, staticStyle, dynamicStyle, hide) {
+
const isNativeStaticStyle = staticStyle && isNativeStyle(staticStyle)
- let result = isNativeStaticStyle ? [] : {}
- const mergeResult = isNativeStaticStyle ? (...args) => result.push(...args) : (...args) => Object.assign(result, ...args)
- // 使用一下 __getSizeCount 触发其 get
- this.__getSizeCount()
+
+ const { mergeToLayer, genResult } = createLayer(isNativeStaticStyle)
+
if (staticClass || dynamicClass) {
- // todo 当前为了复用小程序unocss产物,暂时进行mpEscape,等后续正式支持unocss后可不进行mpEscape
- const classString = mpEscape(concat(staticClass, stringifyDynamicClass(dynamicClass)))
+ const classString = concat(staticClass, stringifyDynamicClass(dynamicClass))
+
+ let needAddUnoPreflight = false
+ const { unoClassMap = {}, unoVarClassMap = {}, unoPreflightsClassMap = {} } = global.__getUnoClass?.() || {}
classString.split(/\s+/).forEach((className) => {
let localStyle, appStyle
const getAppClassStyle = global.__getAppClassStyle || noop
- if (localStyle = this.__getClassStyle(className)) {
+ if (localStyle = this.__getClassStyle?.(className)) {
if (localStyle._media?.length) {
- mergeResult(localStyle._default, getMediaStyle(localStyle._media))
+ mergeToLayer('normal', localStyle._default, getMediaStyle(localStyle._media))
} else {
- mergeResult(localStyle._default)
+ mergeToLayer('normal', localStyle._default)
}
+ } else if (unoClassMap[className]) {
+ const unoClass = unoClassMap[className]._default
+ const importantClass = className.endsWith('!')
+ mergeToLayer(importantClass ? 'important' : 'uno', unoClass)
+ if (unoClass.transform || unoClass.filter) needAddUnoPreflight = true
+ } else if (unoVarClassMap[className]) {
+ mergeToLayer('important', unoVarClassMap[className]._default)
} else if (appStyle = getAppClassStyle(className)) {
if (appStyle._media?.length) {
- mergeResult(appStyle._default, getMediaStyle(appStyle._media))
+ mergeToLayer('app', appStyle._default, getMediaStyle(appStyle._media))
} else {
- mergeResult(appStyle._default)
+ mergeToLayer('app', appStyle._default)
}
} else if (isObject(this.__props[className])) {
// externalClasses必定以对象形式传递下来
- mergeResult(this.__props[className])
+ mergeToLayer('normal', this.__props[className])
}
})
+
+ if (needAddUnoPreflight) {
+ mergeToLayer('preflight', unoPreflightsClassMap)
+ }
}
+
+
if (staticStyle || dynamicStyle) {
const styleObj = {}
if (isNativeStaticStyle) {
if (Array.isArray(staticStyle)) {
- result = result.concat(staticStyle)
+ mergeToLayer('normal', ...staticStyle)
} else {
- mergeResult(staticStyle)
+ mergeToLayer('normal', staticStyle)
}
} else {
Object.assign(styleObj, parseStyleText(staticStyle))
}
Object.assign(styleObj, normalizeDynamicStyle(dynamicStyle))
- mergeResult(transformStyleObj(styleObj))
+ mergeToLayer('normal', transformStyleObj(styleObj))
}
if (hide) {
- mergeResult({
+ mergeToLayer('important', {
// display: 'none'
// RN下display:'none'容易引发未知异常问题,使用布局样式模拟
flex: 0,
@@ -319,6 +341,9 @@ export default function styleHelperMixin () {
overflow: 'hidden'
})
}
+
+ const result = genResult()
+
const isEmpty = isNativeStaticStyle ? !result.length : isEmptyObject(result)
return isEmpty ? empty : result
}
diff --git a/packages/unocss-base/lib/index.js b/packages/unocss-base/lib/index.js
index 670dbd383d..46d20f34ea 100644
--- a/packages/unocss-base/lib/index.js
+++ b/packages/unocss-base/lib/index.js
@@ -1,11 +1,28 @@
-const { presetUno } = require('@unocss/preset-uno')
+import { presetWind3 } from '@unocss/preset-wind3'
+import { presetLegacyCompat } from '@unocss/preset-legacy-compat'
+import presetRn from '../preset-rn/index'
// eslint-disable-next-line
const remRE = /(-?[\.\d]+)rem/g
-module.exports = function presetMpx (options = {}) {
- const uno = presetUno(options)
+export default function presetMpx (options = {}) {
+ const uno = presetWind3(options)
+ const mpxCurrentTargetMode = process.env.MPX_CURRENT_TARGET_MODE
+ const isReact = mpxCurrentTargetMode === 'ios' || mpxCurrentTargetMode === 'android'
+ const extraPresets = []
+
+ if (isReact) {
+ extraPresets.push(presetRn())
+ options.dark = 'media'
+ }
+
+ extraPresets.push(presetLegacyCompat({
+ commaStyleColorFunction: true,
+ legacyColorSpace: true
+ }))
+
const { baseFontSize = 37.5 } = options
+
return {
...uno,
name: '@mpxjs/unocss-base',
@@ -17,11 +34,12 @@ module.exports = function presetMpx (options = {}) {
util.entries.forEach((i) => {
const value = i[1]
if (typeof value === 'string' && remRE.test(value)) {
- i[1] = value.replace(remRE, (_, p1) => process.env.MPX_CURRENT_TARGET_MODE === 'web'
+ i[1] = value.replace(remRE, (_, p1) => mpxCurrentTargetMode === 'web'
? `${p1 * baseFontSize * (100 / 750).toFixed(8)}vw`
: `${p1 * baseFontSize}rpx`)
}
})
- }
+ },
+ presets: extraPresets
}
}
diff --git a/packages/unocss-base/package.json b/packages/unocss-base/package.json
index 68b8ec19dd..79de3efa16 100644
--- a/packages/unocss-base/package.json
+++ b/packages/unocss-base/package.json
@@ -6,6 +6,7 @@
"directories": {
"lib": "lib"
},
+ "type": "module",
"scripts": {},
"author": "",
"license": "Apache-2.0",
@@ -14,6 +15,7 @@
"access": "public"
},
"dependencies": {
- "@unocss/preset-uno": "^0.52.7"
+ "@unocss/preset-wind3": "^66.0.0",
+ "@unocss/preset-legacy-compat": "^66.0.0"
}
}
diff --git a/packages/unocss-base/preset-rn/index.js b/packages/unocss-base/preset-rn/index.js
new file mode 100644
index 0000000000..ad339b7637
--- /dev/null
+++ b/packages/unocss-base/preset-rn/index.js
@@ -0,0 +1,49 @@
+import rules, { blocklistRules } from './rules/index'
+import { normalizeTransformVar } from './rules/transforms'
+import theme from './theme'
+import blocklistVariants from './variants/index'
+import { transformBase } from '@unocss/preset-mini/rules'
+import { filterBase } from '@unocss/preset-wind3/rules'
+
+function normalizePreflightBase (preflightBase) {
+ normalizeTransformVar(preflightBase)
+ return preflightBase
+}
+
+function preflights () {
+ return [
+ {
+ layer: 'preflights',
+ getCSS ({ theme, generator }) {
+ generator._mpx2rnUnoPreflightBase = {
+ ...normalizePreflightBase(transformBase),
+ ...filterBase
+ }
+ }
+ }
+ ]
+}
+
+function postprocess (utilsObject) {
+ const everyIsVar = utilsObject.entries.every(v => {
+ return v[0].startsWith('--un')
+ })
+ if (everyIsVar) {
+ utilsObject.layer = 'varUtilities'
+ }
+ return utilsObject
+}
+
+export default function presetRnMpx () {
+ return {
+ name: '@mpxjs/unocss-preset-rn',
+ rules,
+ theme,
+ preflights: preflights(),
+ postprocess: [postprocess],
+ blocklist: [
+ ...blocklistRules,
+ ...blocklistVariants
+ ]
+ }
+}
diff --git a/packages/unocss-base/preset-rn/rules/align.js b/packages/unocss-base/preset-rn/rules/align.js
new file mode 100644
index 0000000000..b98c0d5e25
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/align.js
@@ -0,0 +1,31 @@
+import { verticalAligns } from '@unocss/preset-mini/rules'
+
+const support = [
+ 'mid',
+ 'start',
+ 'btm',
+ 'end',
+ 'auto',
+ 'top',
+ 'bottom',
+ 'middle'
+]
+
+const blockVerticalAligns = verticalAligns.map(([rule]) => (raw) => {
+ const result = raw.match(rule)
+ if (result && !support.includes(result[1])) {
+ return true
+ }
+})
+
+const blockTextAligns = [
+ 'text-start',
+ 'text-end',
+ 'text-align-start',
+ 'text-align-end'
+]
+
+export {
+ blockVerticalAligns,
+ blockTextAligns
+}
diff --git a/packages/unocss-base/preset-rn/rules/animation.js b/packages/unocss-base/preset-rn/rules/animation.js
new file mode 100644
index 0000000000..82e6a1e1ce
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/animation.js
@@ -0,0 +1 @@
+export { animations as blockAnimations } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/rules/background.js b/packages/unocss-base/preset-rn/rules/background.js
new file mode 100644
index 0000000000..ef43a4d3d3
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/background.js
@@ -0,0 +1,23 @@
+const blockBackgroundStyles = [
+ // attachments
+ 'bg-fixed',
+ 'bg-locale',
+ 'bg-scroll',
+ // repeat
+ 'bg-repeat',
+ 'bg-repeat-x',
+ 'bg-repeat-y',
+ 'bg-repeat-round',
+ 'bg-repeat-space',
+ // origins
+ 'bg-origin-border',
+ 'bg-origin-padding',
+ 'bg-origin-content',
+ // clips
+ 'bg-clip-border',
+ 'bg-clip-content',
+ 'bg-clip-padding',
+ 'bg-clip-text'
+]
+
+export { blockBackgroundStyles }
diff --git a/packages/unocss-base/preset-rn/rules/behaviors.js b/packages/unocss-base/preset-rn/rules/behaviors.js
new file mode 100644
index 0000000000..240ef53532
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/behaviors.js
@@ -0,0 +1,13 @@
+import { imageRenderings, overscrolls, listStyle, accents, carets } from '@unocss/preset-wind3/rules'
+import { appearance, outline, willChange } from '@unocss/preset-mini/rules'
+
+export const blockBehaviors = [
+ ...overscrolls,
+ ...imageRenderings,
+ ...listStyle,
+ ...outline,
+ ...willChange,
+ ...appearance,
+ ...accents,
+ ...carets
+]
diff --git a/packages/unocss-base/preset-rn/rules/border.js b/packages/unocss-base/preset-rn/rules/border.js
new file mode 100644
index 0000000000..59f6218bda
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/border.js
@@ -0,0 +1,14 @@
+import { borders } from '@unocss/preset-mini/rules'
+
+const unSupport = ['s', 'e', 'bs', 'be', 'is', 'ie', 'block', 'inline']
+
+const blockBorders = borders.map(([rule]) => raw => {
+ const result = raw.match(rule)
+ if (raw === 'border-double') {
+ return true
+ } else if (result && unSupport.includes(result[1])) {
+ return true
+ }
+})
+
+export { blockBorders }
diff --git a/packages/unocss-base/preset-rn/rules/color.js b/packages/unocss-base/preset-rn/rules/color.js
new file mode 100644
index 0000000000..009c653ede
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/color.js
@@ -0,0 +1 @@
+export { colorScheme as blockColorScheme } from '@unocss/preset-mini/rules'
diff --git a/packages/unocss-base/preset-rn/rules/columns.js b/packages/unocss-base/preset-rn/rules/columns.js
new file mode 100644
index 0000000000..d5a087aace
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/columns.js
@@ -0,0 +1 @@
+export { columns as blockColumns } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/rules/container.js b/packages/unocss-base/preset-rn/rules/container.js
new file mode 100644
index 0000000000..22b5949aa6
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/container.js
@@ -0,0 +1,7 @@
+import { containerParent } from '@unocss/preset-mini/rules'
+import { container } from '@unocss/preset-wind3/rules'
+
+export {
+ containerParent as blockContainerParent,
+ container as blockContainer
+}
diff --git a/packages/unocss-base/preset-rn/rules/decoration.js b/packages/unocss-base/preset-rn/rules/decoration.js
new file mode 100644
index 0000000000..7e74196060
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/decoration.js
@@ -0,0 +1,24 @@
+import { textDecorations } from '@unocss/preset-mini/rules'
+import { isReg } from '../../utils/index'
+
+const unSupport = ['auto', 'from-font', 'overline']
+
+const blockTextDecorations = [
+ ...textDecorations.map(([rule]) => raw => {
+ if (isReg(rule)) {
+ const result = raw.match(rule)
+ if (result && unSupport.includes(result[1])) {
+ return true
+ }
+ }
+ }),
+ // size
+ /^(?:underline|decoration)-(auto|from-font)$/,
+ // offset
+ /^(?:underline|decoration)-offset-(.+)$/,
+ // wavy style
+ 'underline-wavy',
+ 'decoration-wavy'
+]
+
+export { blockTextDecorations }
diff --git a/packages/unocss-base/preset-rn/rules/divide.js b/packages/unocss-base/preset-rn/rules/divide.js
new file mode 100644
index 0000000000..cbcb3f1233
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/divide.js
@@ -0,0 +1 @@
+export { divides as blockDivides } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/rules/filters.js b/packages/unocss-base/preset-rn/rules/filters.js
new file mode 100644
index 0000000000..113e85cd3e
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/filters.js
@@ -0,0 +1,21 @@
+import { filters } from '@unocss/preset-wind3/rules'
+import { isReg, isString } from '../../utils/index'
+
+const unSupport = [
+ 'backdrop-filter',
+ 'backdrop-filter-none'
+]
+
+export const blockFilters = filters.map(([rule]) => (raw) => {
+ const reg = /^backdrop-/
+ let result = ''
+ if (isString(rule) && unSupport.includes(raw)) {
+ return true
+ } else if (isReg(rule)) {
+ const matcher = raw.match(rule)
+ if (matcher) {
+ result = matcher[0]
+ return reg.test(result)
+ }
+ }
+})
diff --git a/packages/unocss-base/preset-rn/rules/flex.js b/packages/unocss-base/preset-rn/rules/flex.js
new file mode 100644
index 0000000000..7da81108e7
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/flex.js
@@ -0,0 +1,9 @@
+
+const blockFlex = [
+ 'flex-inline',
+ 'inline-flex'
+]
+
+export {
+ blockFlex
+}
diff --git a/packages/unocss-base/preset-rn/rules/gap.js b/packages/unocss-base/preset-rn/rules/gap.js
new file mode 100644
index 0000000000..f406fa335d
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/gap.js
@@ -0,0 +1,12 @@
+import { gaps } from '@unocss/preset-mini/rules'
+
+const blockGaps = gaps.map(([rule]) => (raw) => {
+ const result = raw.match(rule)
+ if (result && result[0].startsWith('grid')) {
+ return true
+ }
+})
+
+export {
+ blockGaps
+}
diff --git a/packages/unocss-base/preset-rn/rules/global.js b/packages/unocss-base/preset-rn/rules/global.js
new file mode 100644
index 0000000000..969a7e7bb2
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/global.js
@@ -0,0 +1,3 @@
+import { globalKeywords } from '../../utils/index'
+
+export const blockGlobalRules = globalKeywords.map((v) => [new RegExp(`.*-${v}$`)])
diff --git a/packages/unocss-base/preset-rn/rules/grid.js b/packages/unocss-base/preset-rn/rules/grid.js
new file mode 100644
index 0000000000..e77d3595f7
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/grid.js
@@ -0,0 +1,3 @@
+import { grids } from '@unocss/preset-mini/rules'
+
+export { grids as blockGrids }
diff --git a/packages/unocss-base/preset-rn/rules/index.js b/packages/unocss-base/preset-rn/rules/index.js
new file mode 100644
index 0000000000..9d22ee0645
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/index.js
@@ -0,0 +1,86 @@
+import shadow from './shadow'
+import { transforms } from './transforms'
+// block rules
+import { blockColorScheme } from './color'
+import { blockBackgroundStyles } from './background'
+import { blockPaddingAndMargins, blockSpaces } from './spacing'
+import { blockTypography } from './typography'
+import { blockBehaviors } from './behaviors'
+import { blockOverflows } from './layout'
+import { blockFilters } from './filters'
+import { blockStatics } from './static'
+import {
+ blockFlexGridJustifiesAlignments,
+ blockJustifies,
+ blockOrders,
+ blockPlacements,
+ blockPositions,
+ blockFloats,
+ blockBoxSizing
+} from './positions'
+import { blockFlex } from './flex'
+import { blockGlobalRules } from './global'
+import { blockTextAligns, blockVerticalAligns } from './align'
+import { blockContainer, blockContainerParent } from './container'
+import { blockAnimations } from './animation'
+import { blockColumns } from './columns'
+import { blockDivides } from './divide'
+import { blockPlaceholders } from './placeholder'
+import { blockScrolls } from './scrolls'
+import { blockTables } from './table'
+import { blockTouchActions } from './touch-actions'
+import { blockViewTransition } from './view-transition'
+import { blockGaps } from './gap'
+import { blockTransitions } from './transition'
+import { blockSvgUtilities } from './svg'
+import { blockRings } from './ring'
+import { blockBorders } from './border'
+import { blockLineClamps } from './line-clamp'
+import { blockGrids } from './grid'
+import { blockTextDecorations } from './decoration'
+
+export const blocklistRules = [
+ ...blockTextDecorations,
+ ...blockBackgroundStyles,
+ ...blockBorders,
+ ...blockOverflows,
+ // space
+ ...blockPaddingAndMargins,
+ ...blockSpaces,
+ // position
+ ...blockPositions,
+ ...blockFlexGridJustifiesAlignments,
+ ...blockJustifies,
+ ...blockOrders,
+ ...blockPlacements,
+ ...blockFloats,
+ ...blockBoxSizing,
+ // static
+ ...blockStatics,
+ ...blockVerticalAligns,
+ ...blockTextAligns,
+ ...blockFlex,
+ ...blockGaps,
+ ...blockTypography,
+ ...blockBehaviors,
+ ...blockContainerParent,
+ ...blockContainer,
+ ...blockColorScheme,
+ ...blockColumns,
+ ...blockGrids,
+ ...blockPlaceholders,
+ ...blockTables,
+ ...blockScrolls,
+ ...blockDivides,
+ ...blockTouchActions,
+ ...blockRings,
+ ...blockLineClamps,
+ ...blockSvgUtilities,
+ ...blockViewTransition,
+ ...blockTransitions,
+ ...blockAnimations,
+ ...blockFilters,
+ ...blockGlobalRules
+]
+
+export default [...shadow, ...transforms]
diff --git a/packages/unocss-base/preset-rn/rules/layout.js b/packages/unocss-base/preset-rn/rules/layout.js
new file mode 100644
index 0000000000..9c08c6b351
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/layout.js
@@ -0,0 +1,12 @@
+const blockOverflows = [
+ (raw) => {
+ const reg = /^(?:overflow|of)-(.+)$/
+ const match = raw.match(reg)
+ if (match && !['hidden', 'visible', 'scroll'].includes(match[1])) {
+ return true
+ }
+ },
+ /^(?:overflow|of)-([xy])-(.+)$/
+]
+
+export { blockOverflows }
diff --git a/packages/unocss-base/preset-rn/rules/line-clamp.js b/packages/unocss-base/preset-rn/rules/line-clamp.js
new file mode 100644
index 0000000000..ea6188a0ae
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/line-clamp.js
@@ -0,0 +1 @@
+export { lineClamps as blockLineClamps } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/rules/placeholder.js b/packages/unocss-base/preset-rn/rules/placeholder.js
new file mode 100644
index 0000000000..2c548c1b8a
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/placeholder.js
@@ -0,0 +1 @@
+export { placeholders as blockPlaceholders } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/rules/positions.js b/packages/unocss-base/preset-rn/rules/positions.js
new file mode 100644
index 0000000000..2b5b8cbcec
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/positions.js
@@ -0,0 +1,54 @@
+import {
+ positions,
+ justifies,
+ orders,
+ alignments,
+ placements,
+ floats
+} from '@unocss/preset-mini/rules'
+
+const unSupport = ['sticky']
+
+const blockPositions = positions.map(([rule]) => (raw) => {
+ const result = raw.match(rule)
+ if (result && unSupport.includes(result[1])) {
+ return true
+ }
+})
+
+// 不支持的规则过滤覆盖
+const blockJustifies = [
+ 'justify-left',
+ 'justify-right',
+ 'justify-stretch',
+ 'justify-items-center',
+ 'justify-items-end',
+ 'justify-items-start',
+ 'justify-items-stretch',
+ 'justify-self-auto',
+ 'justify-self-center',
+ 'justify-self-end',
+ 'justify-self-stretch',
+ 'justify-self-start'
+]
+
+const blockFlexGridJustifiesAlignments = [
+ // flex部分不支持
+ ...[...blockJustifies, ...placements].map((k) => `flex-${k}`),
+ // grid全部不支持
+ ...[...justifies, ...alignments, ...placements].map((k) => `grid-${k}`)
+]
+
+const blockBoxSizing = [
+ 'box-content'
+]
+
+export {
+ blockPositions,
+ blockJustifies,
+ orders as blockOrders,
+ placements as blockPlacements,
+ blockFlexGridJustifiesAlignments,
+ floats as blockFloats,
+ blockBoxSizing
+}
diff --git a/packages/unocss-base/preset-rn/rules/ring.js b/packages/unocss-base/preset-rn/rules/ring.js
new file mode 100644
index 0000000000..9998943eeb
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/ring.js
@@ -0,0 +1 @@
+export { rings as blockRings } from '@unocss/preset-mini/rules'
diff --git a/packages/unocss-base/preset-rn/rules/scrolls.js b/packages/unocss-base/preset-rn/rules/scrolls.js
new file mode 100644
index 0000000000..347726f740
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/scrolls.js
@@ -0,0 +1 @@
+export { scrolls as blockScrolls } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/rules/shadow.js b/packages/unocss-base/preset-rn/rules/shadow.js
new file mode 100644
index 0000000000..533bce0a85
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/shadow.js
@@ -0,0 +1,19 @@
+import { colorableShadows, colorResolver, h, hasParseableColor } from '@unocss/preset-mini/utils'
+
+const boxShadows = [
+ [/^shadow(?:-(.+))?$/, (match, context) => {
+ const [, d] = match
+ const { theme } = context
+ const v = theme.boxShadow?.[d || 'DEFAULT']
+ const c = d ? h.bracket.cssvar(d) : undefined
+
+ if ((v != null || c != null) && !hasParseableColor(c, theme, 'shadowColor')) {
+ return {
+ 'box-shadow': colorableShadows(v || c, '--un-shadow-color').join(',')
+ }
+ }
+ return colorResolver('--un-shadow-color', 'shadow', 'shadowColor')(match, context)
+ }]
+]
+
+export default boxShadows
diff --git a/packages/unocss-base/preset-rn/rules/spacing.js b/packages/unocss-base/preset-rn/rules/spacing.js
new file mode 100644
index 0000000000..47f1e8d06a
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/spacing.js
@@ -0,0 +1,25 @@
+import { margins, paddings } from '@unocss/preset-mini/rules'
+import { spaces } from '@unocss/preset-wind3/rules'
+
+const unSupport = [
+ 's',
+ 'e',
+ 'bs',
+ 'be',
+ 'is',
+ 'ie',
+ 'block',
+ 'inline'
+]
+
+const blockPaddingAndMargins = [...paddings, ...margins].map(([rule]) => (raw) => {
+ const result = raw.match(rule)
+ if (result && unSupport.includes(result[1])) {
+ return true
+ }
+})
+
+export {
+ blockPaddingAndMargins,
+ spaces as blockSpaces
+}
diff --git a/packages/unocss-base/preset-rn/rules/static.js b/packages/unocss-base/preset-rn/rules/static.js
new file mode 100644
index 0000000000..fab79aa1fa
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/static.js
@@ -0,0 +1,50 @@
+import {
+ breaks,
+ contains,
+ contents,
+ contentVisibility,
+ cursors,
+ fontSmoothings,
+ resizes,
+ textWraps,
+ whitespaces
+} from '@unocss/preset-mini/rules'
+import { backgroundBlendModes, mixBlendModes, hyphens, isolations, objectPositions } from '@unocss/preset-wind3/rules'
+
+const blockDisplays = [
+ 'inline',
+ 'block',
+ 'inline-block',
+ 'contents',
+ 'flow-root',
+ 'list-item'
+]
+
+const blockAppearances = ['visible', 'invisible']
+
+const blockTextOverflows = ['text-ellipsis', 'text-clip']
+
+const blockFontStyles = ['oblique', 'font-oblique']
+
+const blockStatics = [
+ ...blockAppearances,
+ ...breaks,
+ ...contains,
+ ...contents,
+ ...contentVisibility,
+ ...cursors,
+ ...blockDisplays,
+ ...fontSmoothings,
+ ...blockFontStyles,
+ ...resizes,
+ ...blockTextOverflows,
+ ...textWraps,
+ ...whitespaces,
+ ...backgroundBlendModes,
+ ...mixBlendModes,
+ ...hyphens,
+ ...isolations,
+ ...objectPositions
+]
+
+export { blockStatics }
diff --git a/packages/unocss-base/preset-rn/rules/svg.js b/packages/unocss-base/preset-rn/rules/svg.js
new file mode 100644
index 0000000000..5d25e716db
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/svg.js
@@ -0,0 +1 @@
+export { svgUtilities as blockSvgUtilities } from '@unocss/preset-mini/rules'
diff --git a/packages/unocss-base/preset-rn/rules/table.js b/packages/unocss-base/preset-rn/rules/table.js
new file mode 100644
index 0000000000..09d936c843
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/table.js
@@ -0,0 +1 @@
+export { tables as blockTables } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/rules/touch-actions.js b/packages/unocss-base/preset-rn/rules/touch-actions.js
new file mode 100644
index 0000000000..325fc5ff34
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/touch-actions.js
@@ -0,0 +1 @@
+export { touchActions as blockTouchActions } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/rules/transforms.js b/packages/unocss-base/preset-rn/rules/transforms.js
new file mode 100644
index 0000000000..851345c7d2
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/transforms.js
@@ -0,0 +1,85 @@
+import { transforms } from '@unocss/preset-mini/rules'
+
+const transformBase = {
+ '--un-rotate': '0deg',
+ '--un-rotate-x': '0deg',
+ '--un-rotate-y': '0deg',
+ '--un-rotate-z': '0deg',
+ '--un-scale-x': 1,
+ '--un-scale-y': 1,
+ '--un-scale-z': 1,
+ '--un-skew-x': '0deg',
+ '--un-skew-y': '0deg',
+ '--un-translate-x': 0,
+ '--un-translate-y': 0,
+ '--un-translate-z': 0
+}
+
+const checkVars = [
+ '--un-rotate',
+ '--un-rotate-x',
+ '--un-rotate-y',
+ '--un-rotate-z',
+ '--un-skew-x',
+ '--un-skew-y'
+]
+
+function normalizeTransformVar (res) {
+ checkVars.forEach(key => {
+ if (res[key] !== undefined && res[key] === 0) {
+ res[key] = '0deg'
+ }
+ })
+ return res
+}
+
+function getPreflight (preflightKeys) {
+ return Object.fromEntries(preflightKeys.map(key => [key, transformBase[key]]))
+}
+
+const removedKeys = [
+ 'scaleZ(var(--un-scale-z))',
+ 'translateZ(var(--un-translate-z))'
+]
+
+function normalizeTransform (transform) {
+ if (!transform) return transform
+ return transform
+ .split(' ')
+ .filter(v => !removedKeys.includes(v))
+ .join(' ')
+}
+
+const transformRules = transforms.map(v => {
+ const [regex, matcher, ...another] = v
+ const options = another[0]
+ if (options && options.custom && options.custom.preflightKeys) {
+ if (typeof matcher === 'function') {
+ return [
+ regex,
+ (...args) => {
+ let res = matcher(...args)
+ if (res) {
+ if (Array.isArray(res)) {
+ res = Object.fromEntries(res)
+ }
+ normalizeTransformVar(res)
+ if (res.transform) {
+ res.transform = normalizeTransform(res.transform)
+ }
+ }
+ return res
+ },
+ ...another
+ ]
+ }
+ if (typeof matcher === 'object') {
+ matcher.transform = normalizeTransform(matcher.transform)
+ const preflight = getPreflight(options.custom.preflightKeys)
+ Object.assign(matcher, preflight)
+ }
+ }
+ return v
+})
+
+export { transformRules as transforms, normalizeTransformVar }
diff --git a/packages/unocss-base/preset-rn/rules/transition.js b/packages/unocss-base/preset-rn/rules/transition.js
new file mode 100644
index 0000000000..23eb11181e
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/transition.js
@@ -0,0 +1 @@
+export { transitions as blockTransitions } from '@unocss/preset-mini/rules'
diff --git a/packages/unocss-base/preset-rn/rules/typography.js b/packages/unocss-base/preset-rn/rules/typography.js
new file mode 100644
index 0000000000..24fedd3bed
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/typography.js
@@ -0,0 +1,33 @@
+import {
+ writingModes,
+ writingOrientations,
+ hyphens,
+ fontVariantNumeric
+} from '@unocss/preset-wind3/rules'
+import {
+ textStrokes,
+ textIndents,
+ tabSizes,
+ whitespaces,
+ breaks,
+ textOverflows,
+ textWraps,
+ fontSmoothings
+} from '@unocss/preset-mini/rules'
+
+const blockTypography = [
+ ...textIndents,
+ ...textStrokes,
+ ...tabSizes,
+ ...whitespaces,
+ ...breaks,
+ ...textOverflows,
+ ...hyphens,
+ ...writingModes,
+ ...writingOrientations,
+ ...textWraps,
+ ...fontSmoothings,
+ ...fontVariantNumeric
+]
+
+export { blockTypography }
diff --git a/packages/unocss-base/preset-rn/rules/view-transition.js b/packages/unocss-base/preset-rn/rules/view-transition.js
new file mode 100644
index 0000000000..24844b338f
--- /dev/null
+++ b/packages/unocss-base/preset-rn/rules/view-transition.js
@@ -0,0 +1 @@
+export { viewTransition as blockViewTransition } from '@unocss/preset-wind3/rules'
diff --git a/packages/unocss-base/preset-rn/theme.js b/packages/unocss-base/preset-rn/theme.js
new file mode 100644
index 0000000000..f56509741f
--- /dev/null
+++ b/packages/unocss-base/preset-rn/theme.js
@@ -0,0 +1,28 @@
+import { platformSelect } from '../utils/index'
+
+export default {
+ preflightRoot: [],
+ letterSpacing: {
+ tighter: '-0.5px',
+ tight: '-0.25px',
+ normal: '0px',
+ wide: '0.25px',
+ wider: '0.5px',
+ widest: '1px'
+ },
+ boxShadow: {
+ DEFAULT: '0 1px 3px rgba(0 0 0 / 0.1)',
+ none: '0 0 rgba(0 0 0 / 0)',
+ sm: '0 1px 2px rgba(0 0 0 / 0.05)',
+ md: '0 4px 6px rgba(0 0 0 / 0.1)',
+ lg: '0 10px 15px rgba(0 0 0 / 0.1)',
+ xl: '0 20px 25px rgba(0 0 0 / 0.1)',
+ '2xl': '0 25px 50px rgba(0 0 0 / 0.25)',
+ inner: 'inset 0 2px 4px 0 rgba(0 0 0 / 0.05)'
+ },
+ fontFamily: {
+ sans: platformSelect({ android: 'san-serif', ios: "'system font'" }),
+ serif: platformSelect({ android: 'serif', ios: 'Georgia' }),
+ mono: platformSelect({ android: 'mono', ios: "'Courier New'" })
+ }
+}
diff --git a/packages/unocss-base/preset-rn/variants/index.js b/packages/unocss-base/preset-rn/variants/index.js
new file mode 100644
index 0000000000..9448c851a3
--- /dev/null
+++ b/packages/unocss-base/preset-rn/variants/index.js
@@ -0,0 +1,80 @@
+import {
+ variantAria,
+ variantTaggedAriaAttributes,
+ variantChildren,
+ variantCombinators,
+ variantContainerQuery,
+ variantTaggedDataAttributes,
+ variantLanguageDirections,
+ variantStartingStyle,
+ variantSupports,
+ variantSelector,
+ variantCssLayer,
+ variantScope
+} from '@unocss/preset-mini/variants'
+import {
+ variantCombinators as windVariantCombinator,
+ variantContrasts,
+ variantMotions,
+ variantSpaceAndDivide,
+ variantStickyHover,
+ placeholderModifier
+} from '@unocss/preset-wind3/variants'
+
+const wrapVariant = function (variant = {}) {
+ return function (raw) {
+ const ctx = {
+ rawSelector: raw,
+ theme: this.config,
+ generator: this
+ }
+ let match
+ if (typeof variant === 'function') {
+ match = variant
+ } else if (variant.match) {
+ match = variant.match
+ } else {
+ return
+ }
+ if (match(raw, ctx)) {
+ return true
+ }
+ }
+}
+
+const transformVariants = function (variantsArr) {
+ return variantsArr.map(variants => {
+ if (Array.isArray(variants)) {
+ return variants.map(variant => wrapVariant(variant))
+ } else {
+ return wrapVariant(variants)
+ }
+ }).reduce((preV, curV) => {
+ if (Array.isArray(curV)) {
+ return preV.concat(...curV)
+ } else {
+ return preV.concat(curV)
+ }
+ }, [])
+}
+
+export default transformVariants([
+ variantAria,
+ variantTaggedAriaAttributes,
+ variantChildren,
+ variantCombinators,
+ variantContainerQuery,
+ variantTaggedDataAttributes,
+ variantLanguageDirections,
+ variantStartingStyle,
+ variantSupports,
+ variantSelector,
+ variantCssLayer,
+ variantScope,
+ windVariantCombinator,
+ variantContrasts,
+ variantMotions,
+ variantSpaceAndDivide,
+ variantStickyHover,
+ placeholderModifier
+])
diff --git a/packages/unocss-base/utils/index.js b/packages/unocss-base/utils/index.js
new file mode 100644
index 0000000000..73b9cd7ccc
--- /dev/null
+++ b/packages/unocss-base/utils/index.js
@@ -0,0 +1,91 @@
+const EMPTY = ' '
+
+const isReg = (reg) => {
+ return reg instanceof RegExp
+}
+
+const isString = (str) => {
+ return typeof str === 'string'
+}
+
+const isFunction = (fn) => {
+ return typeof fn === 'function'
+}
+
+const ruleCallback = ([match], { generator }) => {
+ return ruleFallback(match, generator)
+}
+
+const ruleFallback = (match, generator) => {
+ generator.blocked.add(match)
+ return EMPTY
+}
+
+const genEmptyRule = (...rules) => {
+ return rules.map(rule => [rule, ruleCallback])
+}
+
+const transformEmptyRule = (...rulesArr) => {
+ return rulesArr.map(rules => rules.map(rule => {
+ if (isString(rule[0])) { // staticRule
+ return [
+ [rule[0], null],
+ [new RegExp(`^${rule[0]}$`), ruleCallback]
+ ]
+ }
+ return [[rule[0], ruleCallback]]
+ })).reduce((preV, curV) => preV.concat(...curV), [])
+}
+
+const findRawRules = (matcher, rawRules, byReg = false) => {
+ if (!Array.isArray(matcher)) {
+ matcher = [matcher]
+ }
+
+ return matcher.map(m => {
+ const result = []
+ rawRules.forEach(r => {
+ const tester = r[0]
+ if (isString(m)) {
+ if (byReg) {
+ if (isReg(tester) && tester.test(m)) {
+ result.push(r)
+ }
+ } else {
+ if (isString(tester) && m === tester) {
+ result.push(r)
+ }
+ }
+ } else if (isReg(m)) {
+ if (isString(tester) && m.test(tester)) {
+ result.push(r)
+ }
+ }
+ })
+ return result
+ })
+ .filter(item => !!item.length)
+ .reduce((preV, curV) => preV.concat(curV), [])
+}
+
+export const globalKeywords = ['inherit', 'initial', 'revert', 'revert-layer', 'unset']
+
+export const makeGlobalStaticRules = prefix => {
+ return globalKeywords.map(v => `${prefix}-${v}`)
+}
+
+export const platformSelect = (options = {}) => {
+ const platform = process.env.MPX_CURRENT_TARGET_MODE
+ return options[platform] || ''
+}
+
+export {
+ genEmptyRule,
+ transformEmptyRule,
+ findRawRules,
+ ruleFallback,
+ isFunction,
+ isReg,
+ isString,
+ ruleCallback
+}
diff --git a/packages/unocss-plugin/__tests__/__snapshots__/plugin.test.js.snap b/packages/unocss-plugin/__tests__/__snapshots__/plugin.test.js.snap
new file mode 100644
index 0000000000..6bed877c5b
--- /dev/null
+++ b/packages/unocss-plugin/__tests__/__snapshots__/plugin.test.js.snap
@@ -0,0 +1,37 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`test plugin > test-template 1`] = `""`;
+
+exports[`test plugin > test-template 2`] = `
+[
+ "translate-[-50%,-50%]",
+ "text-12px",
+ "bg-#fff/10",
+]
+`;
+
+exports[`test plugin > test-template 3`] = `
+"/* layer: default */
+.translate-_bl_-50_p__2c_-50_p__br_{--un-translate-x:-50%;--un-translate-y:-50%;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
+.bg-_h_fff_s_10{background-color:rgba(255, 255, 255, 0.1);}
+.text-12px{font-size:12px;}"
+`;
+
+exports[`test plugin > test-template 4`] = `""`;
+
+exports[`test plugin > test-template 5`] = `
+[
+ "translate-[-50%]",
+ "text-12px",
+ "text-16px",
+ "bg-#fff/10",
+]
+`;
+
+exports[`test plugin > test-template 6`] = `
+"/* layer: default */
+.translate-_bl_-50_p__br_{--un-translate-x:-50%;--un-translate-y:-50%;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
+.bg-_h_fff_s_10{background-color:rgba(255, 255, 255, 0.1);}
+.text-12px{font-size:12px;}
+.text-16px{font-size:16px;}"
+`;
diff --git a/packages/unocss-plugin/__tests__/plugin.test.js b/packages/unocss-plugin/__tests__/plugin.test.js
new file mode 100644
index 0000000000..0569c72f96
--- /dev/null
+++ b/packages/unocss-plugin/__tests__/plugin.test.js
@@ -0,0 +1,50 @@
+import MpxUnocssPlugin from '../lib/index'
+import { describe, expect, test } from 'vitest'
+import { getRawSource } from '../lib/source.js'
+import { e as cssEscape } from '@unocss/core'
+import { mpEscape } from '../lib/transform.js'
+import presetMpx from '@mpxjs/unocss-base/lib/index'
+// const { presetLegacyCompat } = require('@unocss/preset-legacy-compat')
+
+// import testpage from './123.mpx?resolve'
+describe('test plugin', async () => {
+ const mode = 'wx'
+ const plugin = new MpxUnocssPlugin({
+ config: {
+ presets: [
+ presetMpx()
+ ]
+ }
+ })
+ const mockCompilation = {
+ fileDependencies: new Set()
+ }
+ const uno = await plugin.createContext(mockCompilation, mode)
+ const parseTemplate = plugin.getTemplateParser(uno)
+ async function testTemplate (content, generateOptions = {
+ preflights: false,
+ safelist: false
+ }) {
+ const source = getRawSource(content)
+ const classmap = {}
+ const { newsource } = parseTemplate(source, (className) => {
+ if (!className) {
+ return className
+ }
+ classmap[className] = true
+ return mpEscape(cssEscape(className), plugin.options.escapeMap)
+ })
+ // 测试模板是否转义
+ expect(newsource.source()).toMatchSnapshot()
+ const classList = Object.keys(classmap)
+ // 测试类名是否正确识别
+ expect(classList).toMatchSnapshot()
+ const unoFileContent = await plugin.generateStyle(uno, classList, generateOptions)
+ // 测试css输出是否正确
+ expect(unoFileContent).toMatchSnapshot()
+ }
+ test('test-template', async () => {
+ await testTemplate('')
+ await testTemplate('')
+ })
+})
diff --git a/packages/unocss-plugin/lib/index.js b/packages/unocss-plugin/lib/index.js
index 74054bbad2..54e943c1c5 100644
--- a/packages/unocss-plugin/lib/index.js
+++ b/packages/unocss-plugin/lib/index.js
@@ -1,33 +1,43 @@
-const path = require('path')
-const { minimatch } = require('minimatch')
-const unoConfig = require('@unocss/config')
-const core = require('@unocss/core')
-const mpxConfig = require('@mpxjs/webpack-plugin/lib/config')
-const toPosix = require('@mpxjs/webpack-plugin/lib/utils/to-posix')
-const fixRelative = require('@mpxjs/webpack-plugin/lib/utils/fix-relative')
-const parseRequest = require('@mpxjs/webpack-plugin/lib/utils/parse-request')
-const { has } = require('@mpxjs/webpack-plugin/lib/utils/set')
-const MpxWebpackPlugin = require('@mpxjs/webpack-plugin')
-const UnoCSSWebpackPlugin = require('./web-plugin')
-const transformerDirectives = require('@unocss/transformer-directives').default
-const transformerVariantGroup = require('@unocss/transformer-variant-group')
-const {
+import MpxWebpackPlugin from '@mpxjs/webpack-plugin'
+import mpxConfig from '@mpxjs/webpack-plugin/lib/config.js'
+import env from '@mpxjs/webpack-plugin/lib/utils/env.js'
+import fixRelative from '@mpxjs/webpack-plugin/lib/utils/fix-relative.js'
+import parseRequest from '@mpxjs/webpack-plugin/lib/utils/parse-request.js'
+import set from '@mpxjs/webpack-plugin/lib/utils/set.js'
+import toPosix from '@mpxjs/webpack-plugin/lib/utils/to-posix.js'
+import { loadConfig } from '@unocss/config'
+import { createGenerator, e as cssEscape } from '@unocss/core'
+import transformerDirectives from '@unocss/transformer-directives'
+import transformerVariantGroup from '@unocss/transformer-variant-group'
+import { minimatch } from 'minimatch'
+import * as path from 'path'
+import {
parseClasses,
- parseStrings,
- parseMustache,
- stringifyAttr,
+ parseCommentConfig,
parseComments,
- parseCommentConfig
-} = require('./parser')
-const { getReplaceSource, getConcatSource, getRawSource } = require('./source')
-const {
- transformStyle,
+ parseMustache,
+ parseStrings,
+ stringifyAttr
+} from './parser.js'
+import platformPreflightsMap from './platform.js'
+import { UnoCSSRNWebpackPlugin } from './rn-plugin/index.js'
+import {
+ getConcatSource,
+ getRawSource,
+ getReplaceSource
+} from './source.js'
+import {
buildAliasTransformer,
- transformGroups,
+ cssRequiresTransform,
mpEscape,
- cssRequiresTransform
-} = require('./transform')
-const platformPreflightsMap = require('./platform')
+ transformGroups,
+ transformStyle
+} from './transform.js'
+import { UnoCSSWebpackPlugin } from './web-plugin/index.js'
+
+const { isWeb, isReact } = env
+const { has } = set
+
const PLUGIN_NAME = 'MpxUnocssPlugin'
function filterFile (file, scan) {
@@ -85,12 +95,13 @@ function normalizeOptions (options) {
root = process.cwd(),
config,
configFiles,
- transformCSS,
+ transformCSS, // false | true | { applyVariable: ['--at-apply'] }
transformGroups, // false | true | { separators: [':','-'] }
webOptions = {}
} = options
// 是否兼容为true的写法
if (transformGroups) transformGroups = transformGroups instanceof Object ? transformGroups : {}
+ if (transformCSS) transformCSS = transformCSS instanceof Object ? transformGroups : {}
// web配置
// todo config读取逻辑通过UnoCSSWebpackPlugin内置逻辑进行,待改进
webOptions = {
@@ -98,7 +109,7 @@ function normalizeOptions (options) {
exclude: scan.exclude || [],
transformers: [
...transformGroups ? [transformerVariantGroup(transformGroups)] : [],
- ...transformCSS ? [transformerDirectives()] : []
+ ...transformCSS ? [transformerDirectives(transformCSS)] : []
],
...webOptions
}
@@ -206,16 +217,14 @@ class MpxUnocssPlugin {
async createContext (compilation, mode) {
const { root, config, configFiles } = this.options
- const { config: resolved, sources } = await unoConfig.loadConfig(root, config, configFiles)
+ const { config: resolved, sources } = await loadConfig(root, config, configFiles)
sources.forEach((item) => {
compilation.fileDependencies.add(item)
- // fix jiti require cache for watch
- delete require.cache[item]
})
const platformPreflights = platformPreflightsMap[mode] || []
- return core.createGenerator({
+ return await createGenerator({
...resolved,
preflights: [
...(resolved.preflights || []),
@@ -224,6 +233,54 @@ class MpxUnocssPlugin {
})
}
+ getTemplateParser (uno) {
+ // process classes
+ const transformAlias = buildAliasTransformer(uno.config.alias)
+ const transformClasses = (source, classNameHandler = c => c) => {
+ // pre process
+ source = transformAlias(source)
+ if (this.options.transformGroups) {
+ source = transformGroups(source, this.options.transformGroups)
+ }
+ const content = source.source()
+ // escape & fill classesMap
+ return content.split(/\s+/).map(classNameHandler).join(' ')
+ }
+ return (source, classNameHandler) => {
+ source = getReplaceSource(source)
+ const content = source.original().source()
+ parseClasses(content).forEach(({ result, start, end }) => {
+ let { replaced, val } = parseMustache(result, (exp) => {
+ const expSource = getReplaceSource(exp)
+ parseStrings(exp).forEach(({ result, start, end }) => {
+ result = transformClasses(result, classNameHandler)
+ expSource.replace(start, end, result)
+ })
+ return expSource.source()
+ }, str => transformClasses(str, classNameHandler))
+ if (replaced) {
+ val = stringifyAttr(val)
+ source.replace(start - 1, end + 1, val)
+ }
+ })
+ // process comments
+ const commentConfig = {}
+ parseComments(content).forEach(({ result, start, end }) => {
+ Object.assign(commentConfig, parseCommentConfig(result))
+ source.replace(start, end, '')
+ })
+ if (commentConfig.safelist) {
+ this.getSafeListClasses(commentConfig.safelist).forEach((className) => {
+ classNameHandler(className)
+ })
+ }
+ return {
+ newsource: source,
+ commentConfig
+ }
+ }
+ }
+
apply (compiler) {
this.minify = isProductionLikeMode(compiler.options)
// 处理web
@@ -234,11 +291,12 @@ class MpxUnocssPlugin {
return
}
const mode = this.mode = mpxPluginInstance.options.mode
- if (mode === 'web') {
+ if (isWeb(mode) || isReact(mode)) {
const { webOptions } = this.options
- if (!getPlugin(compiler, UnoCSSWebpackPlugin)) {
+ const WebpackPlugin = isReact(mode) ? UnoCSSRNWebpackPlugin : UnoCSSWebpackPlugin
+ if (!getPlugin(compiler, WebpackPlugin)) {
// todo 考虑使用options.config/configFiles读取配置对象后再与webOptions合并后传递给UnoCSSWebpackPlugin,保障读取的config对象与mp保持一致
- compiler.options.plugins.push(new UnoCSSWebpackPlugin(webOptions))
+ compiler.options.plugins.push(new WebpackPlugin(webOptions))
}
compiler.hooks.done.tap(PLUGIN_NAME, ({ compilation }) => {
for (const dep of compilation.fileDependencies) {
@@ -256,7 +314,7 @@ class MpxUnocssPlugin {
}, (compilation) => {
const { __mpx__: mpx } = compilation
mpx.hasUnoCSS = true
- if (mode === 'web') return
+ if (isWeb(mode) || isReact(mode)) return
compilation.hooks.processAssets.tapPromise({
name: PLUGIN_NAME,
stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONS
@@ -291,12 +349,17 @@ class MpxUnocssPlugin {
}
return 'main'
}
-
+ let checkApplyReg
+ if (this.options.transformCSS) {
+ const checkApplyList = this.options.transformCSS.applyVariable || ['--at-apply', '--uno-apply', '--uno']
+ checkApplyReg = new RegExp(`(${checkApplyList.join('|')})(\s)?\:`)
+ }
// 处理wxss
const processStyle = async (file, source) => {
const content = source.source()
- if (!content || !cssRequiresTransform(content)) return
- const output = await transformStyle(content, file, uno)
+ const hasApplyVariable = checkApplyReg && checkApplyReg.test(content)
+ if (!content || !(cssRequiresTransform(content) || hasApplyVariable)) return
+ const output = await transformStyle(content, file, uno, this.options.transformCSS)
if (!output || output.length <= 0) {
error(`${file} 解析style错误,检查样式文件输入!`)
return
@@ -311,29 +374,15 @@ class MpxUnocssPlugin {
const commentConfigMap = {}
const mainClassesMap = packageClassesMaps.main
- const cssEscape = core.e
// config中的safelist视为主包classes
const safeListClasses = this.getSafeListClasses(config.safelist)
safeListClasses.forEach((className) => {
mainClassesMap[className] = true
})
- const transformAlias = buildAliasTransformer(config.alias)
- const transformClasses = (source, classNameHandler = c => c) => {
- // pre process
- source = transformAlias(source)
- if (this.options.transformGroups) {
- source = transformGroups(source, this.options.transformGroups)
- }
- const content = source.source()
- // escape & fill classesMap
- return content.split(/\s+/).map(classNameHandler).join(' ')
- }
+ const parseTemplate = this.getTemplateParser(uno)
const processTemplate = async (file, source) => {
- source = getReplaceSource(source)
- const content = source.original().source()
-
const packageName = getPackageName(file)
const filename = file.slice(0, -templateExt.length)
const currentClassesMap = packageClassesMaps[packageName] = packageClassesMaps[packageName] || {}
@@ -351,35 +400,9 @@ class MpxUnocssPlugin {
}
return mpEscape(cssEscape(className), this.options.escapeMap)
}
- parseClasses(content).forEach(({ result, start, end }) => {
- let { replaced, val } = parseMustache(result, (exp) => {
- const expSource = getReplaceSource(exp)
- parseStrings(exp).forEach(({ result, start, end }) => {
- result = transformClasses(result, classNameHandler)
- expSource.replace(start, end, result)
- })
- return expSource.source()
- }, str => transformClasses(str, classNameHandler))
- if (replaced) {
- val = stringifyAttr(val)
- source.replace(start - 1, end + 1, val)
- }
- })
- // process comments
- const commentConfig = {}
- parseComments(content).forEach(({ result, start, end }) => {
- Object.assign(commentConfig, parseCommentConfig(result))
- source.replace(start, end, '')
- })
- if (commentConfig.safelist) {
- this.getSafeListClasses(commentConfig.safelist).forEach((className) => {
- classNameHandler(className)
- })
- }
-
+ const { newsource, commentConfig } = parseTemplate(source, classNameHandler)
commentConfigMap[filename] = commentConfig
-
- assets[file] = source
+ assets[file] = newsource
}
await Promise.all(Object.entries(assets).map(([file, source]) => {
@@ -538,4 +561,4 @@ class MpxUnocssPlugin {
}
}
-module.exports = MpxUnocssPlugin
+export default MpxUnocssPlugin
diff --git a/packages/unocss-plugin/lib/parser.js b/packages/unocss-plugin/lib/parser.js
index 1ce02abc72..3966a9ce99 100644
--- a/packages/unocss-plugin/lib/parser.js
+++ b/packages/unocss-plugin/lib/parser.js
@@ -1,4 +1,4 @@
-const { parseMustache, stringifyAttr } = require('@mpxjs/webpack-plugin/lib/template-compiler/compiler')
+import { parseMustache, stringifyAttr } from '@mpxjs/webpack-plugin/lib/template-compiler/compiler.js'
function parseClasses (content) {
const output = []
@@ -78,7 +78,7 @@ function parseStrings (content) {
return output
}
-module.exports = {
+export {
parseClasses,
parseStrings,
parseComments,
diff --git a/packages/unocss-plugin/lib/platform.js b/packages/unocss-plugin/lib/platform.js
index 08ba96519b..793a88a20a 100644
--- a/packages/unocss-plugin/lib/platform.js
+++ b/packages/unocss-plugin/lib/platform.js
@@ -1,6 +1,6 @@
// todo 此处进行跨端preflights定义,保障跨端样式表现一致,待补充完善
// todo 目前不支持声明web preflight,待完善
-module.exports = {
+export default {
ali: [
{
getCSS: () => `
diff --git a/packages/unocss-plugin/lib/rn-plugin/index.js b/packages/unocss-plugin/lib/rn-plugin/index.js
new file mode 100644
index 0000000000..651e6dabc0
--- /dev/null
+++ b/packages/unocss-plugin/lib/rn-plugin/index.js
@@ -0,0 +1,118 @@
+import WebpackSources from 'webpack-sources'
+import nodePath from 'path'
+import { createContext, normalizeAbsolutePath } from '../web-plugin/utils.js'
+import { RESOLVED_ID_RE } from '../web-plugin/consts.js'
+import { getClassMap } from '@mpxjs/webpack-plugin/lib/react/style-helper.js'
+import shallowStringify from '@mpxjs/webpack-plugin/lib/utils/shallow-stringify.js'
+import { fileURLToPath } from 'url'
+
+const __filename = fileURLToPath(import.meta.url) // 当前文件的绝对路径
+const __dirname = nodePath.dirname(__filename) // 当前文件的目录路径
+
+const PLUGIN_NAME = 'unocss:webpack'
+
+function WebpackPlugin (configOrPath, defaults) {
+ return {
+ apply (compiler) {
+ // transform 提取tokens
+ compiler.options.module.rules.unshift({
+ enforce: 'pre',
+ use: (data) => {
+ if (data.resource == null) { return [] }
+
+ const id = normalizeAbsolutePath(data.resource + (data.resourceQuery || ''))
+ if (compiler.__unoCtx.filter('', id) && !id.match(/\.html$/) && !RESOLVED_ID_RE.test(id)) {
+ return [{
+ loader: nodePath.resolve(__dirname, '../web-plugin/transform-loader')
+ }]
+ }
+
+ return []
+ }
+ })
+
+ compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+ compilation.hooks.optimizeAssets.tapPromise(PLUGIN_NAME, async () => {
+ const mpx = compilation.__mpx__
+ const { mode, srcMode } = mpx
+ const ctx = compiler.__unoCtx
+ const uno = ctx.uno
+ // 清空transformCache避免watch修改不生效
+ ctx.transformCache.clear()
+ const tokens = new Set()
+ for (const module of compilation.modules) {
+ const assetsInfo = module.buildInfo.assetsInfo || new Map()
+ for (const [, { unocssTokens } = {}] of assetsInfo) {
+ if (unocssTokens) {
+ for (const token of unocssTokens) {
+ tokens.add(token)
+ }
+ }
+ }
+ }
+ const result = await uno.generate(tokens, { minify: true })
+ if (uno.blocked.size) {
+ compilation.errors.push(`[Mpx Unocss]: all those '${[...uno.blocked].join(', ')}' class utilities is not supported in react native mode`)
+ }
+ const getLayersClassMap = (layers) => {
+ return getClassMap({
+ content: result.getLayers(layers),
+ filename: 'mpx2rn-unocss',
+ mode,
+ srcMode,
+ warn: msg => {
+ compilation.warnings.push(msg)
+ },
+ error: msg => {
+ compilation.errors.push(msg)
+ }
+ })
+ }
+ const classMap = getLayersClassMap(result.layers.filter(v => v !== 'varUtilities'))
+ const utilitiesClassMap = getLayersClassMap(['varUtilities'])
+ const preflightsClassMap = uno._mpx2rnUnoPreflightBase
+
+ const files = Object.keys(compilation.assets)
+ for (const file of files) {
+ if (file === '*') { return }
+ let code = compilation.assets[file].source().toString()
+ let replaced = false
+ code = code
+ .replace('__unoCssMapPlaceholder__', () => {
+ replaced = true
+ return shallowStringify(classMap)
+ })
+ .replace('__unoVarUtilitiesCssMap__', () => {
+ return shallowStringify(utilitiesClassMap)
+ })
+ .replace('__unoCssMapPreflights__', () => {
+ return JSON.stringify(preflightsClassMap)
+ })
+ if (replaced) { compilation.assets[file] = new WebpackSources.RawSource(code) }
+ }
+ })
+ })
+
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
+ const mpx = compilation.__mpx__
+ mpx.unoCtx = compiler.__unoCtx.uno
+ })
+
+ compiler.hooks.beforeCompile.tapPromise(PLUGIN_NAME, async (compilation) => {
+ const ctx = await createContext(configOrPath, defaults)
+ ctx.uno.config.blocklist = ctx.uno.config.blocklist.map((item) => {
+ if (typeof item === 'function') {
+ return item.bind(ctx.uno)
+ }
+ return item
+ })
+ compiler.__unoCtx = ctx
+ return ctx
+ })
+ }
+ }
+}
+
+export {
+ WebpackPlugin as UnoCSSRNWebpackPlugin
+}
diff --git a/packages/unocss-plugin/lib/source.js b/packages/unocss-plugin/lib/source.js
index 2c29592bf9..4ea1d006b8 100644
--- a/packages/unocss-plugin/lib/source.js
+++ b/packages/unocss-plugin/lib/source.js
@@ -1,5 +1,5 @@
-const { ReplaceSource, RawSource, ConcatSource, Source } = require('webpack').sources
-
+import webpack from 'webpack'
+const { ReplaceSource, RawSource, ConcatSource, Source } = webpack.sources
function getRawSource (s) {
if (s instanceof RawSource) { return s }
if (s instanceof Source) { return new RawSource(s.source()) }
@@ -17,7 +17,7 @@ function getConcatSource (s) {
return new ConcatSource(s)
}
-module.exports = {
+export {
getRawSource,
getReplaceSource,
getConcatSource
diff --git a/packages/unocss-plugin/lib/transform.js b/packages/unocss-plugin/lib/transform.js
index bf0daffc95..a269f20a5b 100644
--- a/packages/unocss-plugin/lib/transform.js
+++ b/packages/unocss-plugin/lib/transform.js
@@ -1,6 +1,6 @@
-const MagicString = require('magic-string')
-const transformerDirectives = require('@unocss/transformer-directives').default
-const { getReplaceSource } = require('./source')
+import MagicString from 'magic-string'
+import transformerDirectives from '@unocss/transformer-directives' // default
+import { getReplaceSource } from './source.js'
const escapedReg = /\\(.)/g
function mpEscape (str, escapeMap = {}) {
@@ -71,17 +71,18 @@ function cssRequiresTransform (source) {
async function transformStyle (
code,
id,
- uno
+ uno,
+ options
) {
const s = new MagicString(code)
- await transformerDirectives().transform(s, id, { uno })
+ await transformerDirectives(options).transform(s, id, { uno })
if (s.hasChanged()) {
code = s.toString()
}
return code
}
-module.exports = {
+export {
cssRequiresTransform,
transformGroups,
mpEscape,
diff --git a/packages/unocss-plugin/lib/web-plugin/consts.js b/packages/unocss-plugin/lib/web-plugin/consts.js
index e56289c26c..6d91c54604 100644
--- a/packages/unocss-plugin/lib/web-plugin/consts.js
+++ b/packages/unocss-plugin/lib/web-plugin/consts.js
@@ -29,7 +29,7 @@ function getLayerPlaceholder (layer) {
return `#--unocss--{layer:${layer}}`
}
-module.exports = {
+export {
LAYER_MARK_ALL,
LAYER_PLACEHOLDER_RE,
RESOLVED_ID_RE,
diff --git a/packages/unocss-plugin/lib/web-plugin/index.js b/packages/unocss-plugin/lib/web-plugin/index.js
index 3b8df3ef7e..9d6288c14e 100644
--- a/packages/unocss-plugin/lib/web-plugin/index.js
+++ b/packages/unocss-plugin/lib/web-plugin/index.js
@@ -1,19 +1,31 @@
-const WebpackSources = require('webpack-sources')
-const VirtualModulesPlugin = require('webpack-virtual-modules')
-const node_path = require('node:path')
-const process = require('process')
-const fs = require('fs')
-const { createContext, getPath, normalizeAbsolutePath } = require('./utils')
-const { LAYER_MARK_ALL, LAYER_PLACEHOLDER_RE, RESOLVED_ID_RE, getLayerPlaceholder, resolveId, resolveLayer } = require('./consts')
+import fs from 'fs'
+import nodePath, { dirname } from 'path'
+import process from 'process'
+import { fileURLToPath } from 'url'
+import WebpackSources from 'webpack-sources'
+import VirtualModulesPlugin from 'webpack-virtual-modules'
+import {
+ LAYER_MARK_ALL,
+ LAYER_PLACEHOLDER_RE,
+ RESOLVED_ID_RE,
+ getLayerPlaceholder,
+ resolveId,
+ resolveLayer
+} from './consts.js'
+import {
+ createContext,
+ getPath,
+ normalizeAbsolutePath
+} from './utils.js'
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
const PLUGIN_NAME = 'unocss:webpack'
-const VIRTUAL_MODULE_PREFIX = node_path.resolve(process.cwd(), '_virtual_')
+const VIRTUAL_MODULE_PREFIX = nodePath.resolve(process.cwd(), '_virtual_')
function WebpackPlugin (configOrPath, defaults) {
return {
apply (compiler) {
- const ctx = createContext(configOrPath, defaults)
- const { uno, filter, transformCache } = ctx
const entries = new Set()
const __vfsModules = new Set()
let __vfs = null
@@ -27,7 +39,7 @@ function WebpackPlugin (configOrPath, defaults) {
__vfs = new VirtualModulesPlugin()
compiler.options.plugins.push(__vfs)
}
- compiler.__unoCtx = ctx
+
// 添加解析虚拟模块插件 import 'uno.css' 并且注入layer代码
const resolverPlugin = {
apply (resolver) {
@@ -79,9 +91,9 @@ function WebpackPlugin (configOrPath, defaults) {
if (data.resource == null) { return [] }
const id = normalizeAbsolutePath(data.resource + (data.resourceQuery || ''))
- if (filter('', id) && !id.match(/\.html$/) && !RESOLVED_ID_RE.test(id)) {
+ if (compiler.__unoCtx.filter('', id) && !id.match(/\.html$/) && !RESOLVED_ID_RE.test(id)) {
return [{
- loader: node_path.resolve(__dirname, './transform-loader')
+ loader: nodePath.resolve(__dirname, './transform-loader')
}]
}
@@ -91,8 +103,10 @@ function WebpackPlugin (configOrPath, defaults) {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.optimizeAssets.tapPromise(PLUGIN_NAME, async () => {
+ const ctx = compiler.__unoCtx
+ const uno = ctx.uno
// 清空transformCache避免watch修改不生效
- transformCache.clear()
+ ctx.transformCache.clear()
const tokens = new Set()
for (const module of compilation.modules) {
const assetsInfo = module.buildInfo.assetsInfo || new Map()
@@ -122,9 +136,21 @@ function WebpackPlugin (configOrPath, defaults) {
}
})
})
+
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
+ const mpx = compilation.__mpx__
+ mpx.unoCtx = compiler.__unoCtx.uno
+ })
+
+ compiler.hooks.beforeCompile.tapPromise(PLUGIN_NAME, async (compilation) => {
+ const ctx = await createContext(configOrPath, defaults)
+ compiler.__unoCtx = ctx
+ return ctx
+ })
}
}
}
+
function getLayer (id) {
let layer = resolveLayer(getPath(id))
if (!layer) {
@@ -133,4 +159,7 @@ function getLayer (id) {
}
return layer
}
-module.exports = WebpackPlugin
+
+export {
+ WebpackPlugin as UnoCSSWebpackPlugin
+}
diff --git a/packages/unocss-plugin/lib/web-plugin/transform-loader.js b/packages/unocss-plugin/lib/web-plugin/transform-loader.js
index fd151e6bf5..9c4dbf71c9 100644
--- a/packages/unocss-plugin/lib/web-plugin/transform-loader.js
+++ b/packages/unocss-plugin/lib/web-plugin/transform-loader.js
@@ -1,14 +1,13 @@
-const { applyTransformers, isCssId } = require('./utils')
-const parseComponent = require('@mpxjs/webpack-plugin/lib/parser')
-const genComponentTag = require('@mpxjs/webpack-plugin/lib/utils/gen-component-tag')
-const path = require('path')
+import { applyTransformers, isCssId } from './utils.js'
+import parseComponent from '@mpxjs/webpack-plugin/lib/parser.js'
+import genComponentTag from '@mpxjs/webpack-plugin/lib/utils/gen-component-tag.js'
+import path from 'path'
async function transform (code, map) {
const callback = this.async()
const ctx = this._compiler.__unoCtx
const mpx = this.getMpx()
if (!ctx || !mpx) return callback(null, code, map)
- await ctx.ready
// 使用resourcePath而不是resource作为id,规避query的影响
const id = this.resourcePath
const { extract, transformCache } = ctx
@@ -78,4 +77,4 @@ async function transform (code, map) {
}
}
-module.exports = transform
+export default transform
diff --git a/packages/unocss-plugin/lib/web-plugin/utils.js b/packages/unocss-plugin/lib/web-plugin/utils.js
index dfdabcc510..c5a84264d7 100644
--- a/packages/unocss-plugin/lib/web-plugin/utils.js
+++ b/packages/unocss-plugin/lib/web-plugin/utils.js
@@ -1,66 +1,62 @@
-const pluginutils = require('@rollup/pluginutils')
-const config = require('@unocss/config')
-const core = require('@unocss/core')
-const node_path = require('node:path')
-const MagicString = require('magic-string')
-const remapping = require('@ampproject/remapping')
+import pluginutils from '@rollup/pluginutils'
+import { cssIdRE as defCssIdRE, createGenerator } from '@unocss/core'
+import { loadConfig } from '@unocss/config'
+import MagicString from 'magic-string'
+import remapping from '@ampproject/remapping'
+import nodePath from 'path'
const INCLUDE_COMMENT = '@unocss-include'
const IGNORE_COMMENT = '@unocss-ignore'
const CSS_PLACEHOLDER = '@unocss-placeholder'
-const defaultExclude = [core.cssIdRE]
+const defaultExclude = [defCssIdRE]
const defaultInclude = [/\.(vue|mpx|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/]
const sfcIdRE = /\.(vue|mpx)($|\?)/
const templateIdRE = /\.(wxml|axml|swan|qml|ttml|qxml|jxml|ddml|html)($|\?)/
const cssIdRE = /\.(wxss|acss|css|qss|ttss|jxss|ddss)($|\?)/
-function createContext (configOrPath, defaults = {}, extraConfigSources = []) {
+async function createContext (configOrPath, defaults = {}, extraConfigSources = []) {
const root = process.cwd()
let rawConfig = {}
- const uno = core.createGenerator(rawConfig, defaults)
- let rollupFilter = pluginutils.createFilter(defaultInclude, defaultExclude)
- const idFilter = pluginutils.createFilter([sfcIdRE, templateIdRE, cssIdRE, core.cssIdRE])
- const ready = reloadConfig()
- async function reloadConfig () {
- const result = await config.loadConfig(root, configOrPath, extraConfigSources, defaults)
- rawConfig = result.config
- uno.setConfig(rawConfig)
- rollupFilter = pluginutils.createFilter(
- rawConfig.include || defaultInclude,
- rawConfig.exclude || defaultExclude
- )
- const presets = /* @__PURE__ */ new Set()
- uno.config.presets.forEach((i) => {
- if (!i.name) {
- return
- }
- if (presets.has(i.name)) {
- console.warn(`[unocss] duplication of preset ${i.name} found, there might be something wrong with your config.`)
- } else {
- presets.add(i.name)
- }
- })
+ const uno = await createGenerator(rawConfig, defaults)
+ let rollupFilter = pluginutils.createFilter(defaultInclude, defaultExclude)
+ const idFilter = pluginutils.createFilter([sfcIdRE, templateIdRE, cssIdRE, defCssIdRE])
- const transformers = uno.config.transformers
- if (transformers) {
- const pre = []
- const normal = []
- const post = []
- transformers.forEach(i => {
- if (i.enforce === 'pre') pre.push(i)
- else if (i.enforce === 'post') post.push(i)
- else normal.push(i)
- })
- uno.config.transformers = [
- ...pre,
- ...normal,
- ...post
- ]
+ const result = await loadConfig(root, configOrPath, extraConfigSources, defaults)
+ rawConfig = result.config
+ await uno.setConfig(rawConfig)
+ rollupFilter = pluginutils.createFilter(
+ rawConfig.include || defaultInclude,
+ rawConfig.exclude || defaultExclude
+ )
+ const presets = /* @__PURE__ */ new Set()
+ uno.config.presets.forEach((i) => {
+ if (!i.name) {
+ return
}
+ if (presets.has(i.name)) {
+ console.warn(`[unocss] duplication of preset ${i.name} found, there might be something wrong with your config.`)
+ } else {
+ presets.add(i.name)
+ }
+ })
- return result
+ const transformers = uno.config.transformers
+ if (transformers) {
+ const pre = []
+ const normal = []
+ const post = []
+ transformers.forEach(i => {
+ if (i.enforce === 'pre') pre.push(i)
+ else if (i.enforce === 'post') post.push(i)
+ else normal.push(i)
+ })
+ uno.config.transformers = [
+ ...pre,
+ ...normal,
+ ...post
+ ]
}
async function extract (code, id) {
@@ -85,9 +81,6 @@ function createContext (configOrPath, defaults = {}, extraConfigSources = []) {
}
return {
- get ready () {
- return ready
- },
filter,
uno,
extract,
@@ -128,8 +121,8 @@ async function applyTransformers (ctx, original, id) {
}
function normalizeAbsolutePath (path) {
- if (node_path.isAbsolute(path)) {
- return node_path.normalize(path)
+ if (nodePath.isAbsolute(path)) {
+ return nodePath.normalize(path)
} else {
return path
}
@@ -140,10 +133,10 @@ function getPath (id) {
}
function isCssId (id) {
- return core.cssIdRE.test(id) || cssIdRE.test(id)
+ return defCssIdRE.test(id) || cssIdRE.test(id)
}
-module.exports = {
+export {
createContext,
applyTransformers,
getPath,
diff --git a/packages/unocss-plugin/package.json b/packages/unocss-plugin/package.json
index 1c5b3e99db..02760bbb7f 100644
--- a/packages/unocss-plugin/package.json
+++ b/packages/unocss-plugin/package.json
@@ -4,8 +4,9 @@
"description": "mpx unocss webpack plugin",
"main": "lib/index.js",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "vitest"
},
+ "type": "module",
"author": "",
"license": "Apache-2.0",
"peerDependencies": {
@@ -19,14 +20,18 @@
"dependencies": {
"@ampproject/remapping": "^2.2.1",
"@rollup/pluginutils": "^5.0.2",
- "@unocss/config": "0.52.7",
- "@unocss/core": "0.52.7",
- "@unocss/transformer-directives": "^0.52.1",
- "@unocss/transformer-variant-group": "^0.52.1",
+ "@unocss/config": "^66.0.0",
+ "@unocss/core": "^66.0.0",
+ "@unocss/transformer-directives": "^66.0.0",
+ "@unocss/transformer-variant-group": "^66.0.0",
"magic-string": "^0.30.3",
"minimatch": "^9.0.1",
- "unocss": "^0.52.7",
+ "unocss": "^66.0.0",
"webpack-sources": "^3.2.3",
"webpack-virtual-modules": "^0.6.0"
+ },
+ "devDependencies": {
+ "@mpxjs/unocss-base": "^2.9.48",
+ "vitest": "^3.2.3"
}
}
diff --git a/packages/webpack-plugin/lib/platform/style/wx/index.js b/packages/webpack-plugin/lib/platform/style/wx/index.js
index 4ef0eb7f25..698277f381 100644
--- a/packages/webpack-plugin/lib/platform/style/wx/index.js
+++ b/packages/webpack-plugin/lib/platform/style/wx/index.js
@@ -144,6 +144,7 @@ module.exports = function getSpec ({ warn, error }) {
}
return true
}
+
// prop & value 校验:过滤的不合法的属性和属性值
const verification = ({ prop, value, selector }, { mode }) => {
return verifyProps({ prop, value, selector }, { mode }) && verifyValues({ prop, value, selector }) && ({ prop, value })
@@ -374,6 +375,7 @@ module.exports = function getSpec ({ warn, error }) {
const values = parseValues(value)
const transform = []
values.forEach(item => {
+ // const match = item.match(/(\w+)\(([^()]+|\s*var\([^)]+\))\)/)
const match = item.match(/([/\w]+)\((.+)\)/)
if (match && match.length >= 3) {
let key = match[1]
diff --git a/packages/webpack-plugin/lib/react/processStyles.js b/packages/webpack-plugin/lib/react/processStyles.js
index 457080232d..a2311ecfab 100644
--- a/packages/webpack-plugin/lib/react/processStyles.js
+++ b/packages/webpack-plugin/lib/react/processStyles.js
@@ -24,7 +24,7 @@ module.exports = function (styles, {
new Error('[Mpx style error][' + loaderContext.resource + ']: ' + msg)
)
}
- const { mode, srcMode } = loaderContext.getMpx()
+ const { mode, srcMode, hasUnoCSS } = loaderContext.getMpx()
async.eachOfSeries(styles, (style, i, callback) => {
const scoped = style.scoped || autoScope
const extraOptions = {
@@ -63,6 +63,21 @@ module.exports = function (styles, {
return result
}, '')
if (ctorType === 'app') {
+ if (hasUnoCSS) {
+ output += `
+ let __unoClass
+ global.__getUnoClass = function () {
+ if (!__unoClass) {
+ __unoClass = {
+ unoClassMap: __unoCssMapPlaceholder__,
+ unoVarClassMap: __unoVarUtilitiesCssMap__,
+ unoPreflightsClassMap: __unoCssMapPreflights__
+ }
+ }
+ return __unoClass
+ };\n
+ `
+ }
output += `
global.__classCaches = global.__classCaches || []
const __classCache = new Map()
diff --git a/packages/webpack-plugin/lib/react/processTemplate.js b/packages/webpack-plugin/lib/react/processTemplate.js
index fdef9073d4..9a2a433c08 100644
--- a/packages/webpack-plugin/lib/react/processTemplate.js
+++ b/packages/webpack-plugin/lib/react/processTemplate.js
@@ -32,7 +32,9 @@ module.exports = function (template, {
checkUsingComponents,
autoVirtualHostRules,
forceProxyEventRules,
- customTextRules
+ customTextRules,
+ hasUnoCSS,
+ unoCtx
} = mpx
const { resourcePath, rawResourcePath } = parseRequest(loaderContext.resource)
const builtInComponentsMap = {}
@@ -91,7 +93,9 @@ module.exports = function (template, {
componentGenerics,
hasVirtualHost: matchCondition(resourcePath, autoVirtualHostRules),
forceProxyEvent: matchCondition(resourcePath, forceProxyEventRules),
- isCustomText: matchCondition(resourcePath, customTextRules)
+ isCustomText: matchCondition(resourcePath, customTextRules),
+ hasUnoCSS,
+ unoCtx
})
if (meta.wxsContentMap) {
diff --git a/packages/webpack-plugin/lib/react/style-helper.js b/packages/webpack-plugin/lib/react/style-helper.js
index 9eee4784e5..3be4c72c71 100644
--- a/packages/webpack-plugin/lib/react/style-helper.js
+++ b/packages/webpack-plugin/lib/react/style-helper.js
@@ -90,6 +90,12 @@ function getClassMap ({ content, filename, mode, srcMode, ctorType, warn, error
root.walkRules(rule => {
const classMapValue = {}
+ const prev = rule.prev()
+ let layer
+ if (prev && prev.type === 'comment' && prev.text.includes('rn-layer:')) {
+ layer = JSON.stringify(prev.text.split(':')[1].trim())
+ }
+
rule.walkDecls(({ prop, value }) => {
if (cssPrefixExp.test(prop) || cssPrefixExp.test(value)) return
let newData = rulesRunner({ prop, value, selector: rule.selector })
@@ -121,7 +127,6 @@ function getClassMap ({ content, filename, mode, srcMode, ctorType, warn, error
classMapValue[prop] = value
})
})
-
const classMapKeys = []
const options = getMediaOptions(rule.parent.params || '')
const isMedia = options.maxWidth || options.minWidth
@@ -129,15 +134,17 @@ function getClassMap ({ content, filename, mode, srcMode, ctorType, warn, error
selectors.each(selector => {
if (selector.nodes.length === 1 && selector.nodes[0].type === 'class') {
classMapKeys.push(selector.nodes[0].value)
+ } else if (selector.nodes.length === 2 && selector.nodes[0].type === 'class' && selector.nodes[1].type === 'pseudo') {
+ classMapKeys.push(selector.nodes[0].value + selector.nodes[1].value)
} else {
error('Only single class selector is supported in react native mode temporarily.')
}
})
}).processSync(rule.selector)
-
if (classMapKeys.length) {
classMapKeys.forEach((key) => {
if (Object.keys(classMapValue).length) {
+ const layerObj = layer ? { _layer: layer } : {}
const _default = classMap[key]?._default || {}
const _media = classMap[key]?._media || []
if (isMedia) {
@@ -150,7 +157,8 @@ function getClassMap ({ content, filename, mode, srcMode, ctorType, warn, error
}
classMap[key] = {
_media,
- _default
+ _default,
+ ...layerObj
}
}
})
diff --git a/packages/webpack-plugin/lib/template-compiler/compiler.js b/packages/webpack-plugin/lib/template-compiler/compiler.js
index 2315e5d352..21374576e0 100644
--- a/packages/webpack-plugin/lib/template-compiler/compiler.js
+++ b/packages/webpack-plugin/lib/template-compiler/compiler.js
@@ -1109,6 +1109,27 @@ function stringifyWithResolveComputed (modelValue) {
return result.join('+')
}
+function processUnoPseudo (staticClass, unoCtx) {
+ const { config } = unoCtx
+ const separators = config.separators.join('|')
+ const pseudoClassReg = new RegExp(`(hover)(?:${separators})`) // 目前仅处理了 hover 状态
+ const pseudoClass = {}
+ const newStaticClass = staticClass.split(/\s+/).map(rawClass => {
+ const pseudoMatch = rawClass.match(pseudoClassReg)
+ if (pseudoMatch) {
+ const pseudo = pseudoMatch[1]
+ pseudoClass[pseudo] = pseudoClass[pseudo] || []
+ pseudoClass[pseudo].push(rawClass + ':' + pseudo)
+ return ''
+ }
+ return rawClass
+ }).filter(Boolean).join(' ')
+ return {
+ newStaticClass,
+ pseudoClass
+ }
+}
+
function processStyleReact (el, options) {
// process class/wx:class/style/wx:style/wx:show for react native
const dynamicClass = getAndRemoveAttr(el, config[mode].directive.dynamicClass).val
@@ -1124,6 +1145,15 @@ function processStyleReact (el, options) {
error$1(`Attrs ${config[mode].directive.show} should have a value `)
}
+ let unoStaticHoverClass = ''
+ if (options.hasUnoCSS) {
+ const result = processUnoPseudo(staticClass, options.unoCtx)
+ staticClass = result.newStaticClass
+ if (result.pseudoClass.hover) {
+ unoStaticHoverClass = result.pseudoClass.hover.join(' ')
+ }
+ }
+
if (dynamicClass || staticClass || dynamicStyle || staticStyle || show) {
const staticClassExp = parseMustacheWithContext(staticClass).result
const dynamicClassExp = parseMustacheWithContext(dynamicClass).result
@@ -1144,6 +1174,9 @@ function processStyleReact (el, options) {
let staticClass = el.attrsMap[className + '-class'] || ''
let staticStyle = getAndRemoveAttr(el, className + '-style').val || ''
staticClass = staticClass.replace(/\s+/g, ' ')
+ if (unoStaticHoverClass && className === 'hover') {
+ staticClass += unoStaticHoverClass
+ }
staticStyle = staticStyle.replace(/\s+/g, ' ')
if ((staticClass && staticClass !== 'none') || staticStyle) {
const staticClassExp = parseMustacheWithContext(staticClass).result