Skip to content

feat: unocss-plugin 支持 wx:class 对象写法中的类名扫描 - #2519

Open
mackwang112 wants to merge 17 commits into
masterfrom
feat-unocss-dynamic-class
Open

feat: unocss-plugin 支持 wx:class 对象写法中的类名扫描#2519
mackwang112 wants to merge 17 commits into
masterfrom
feat-unocss-dynamic-class

Conversation

@mackwang112

Copy link
Copy Markdown
Collaborator

新支持的写法包括:

  • wx:class="{{ { 'ml-17rpx': flag } }}" — 普通类名
  • wx:class="{{ { 'h-100%': flag } }}" — 含特殊字符的类名
  • wx:class="{{ { 'ml-17rpx h-100%': flag } }}" — 对象 key 中空格分隔的多个类名
  • wx:class="{{ { '*card': flag } }}" — alias 展开

mackwang112 and others added 3 commits June 10, 2026 14:20
修复 unocss-plugin 无法处理 wx:class="{{ { 'ml-17rpx': flag } }}" 对象写法的问题。

模板编译阶段 transDynamicClassExpr 会将 wx:class 对象 key 中的特殊字符
编码为合法 identifier(如 ml-17rpx → ml_da_17rpxMpxEscape),导致 unocss-plugin
扫描编译产物时 parseStrings 无法识别,对应 CSS 不会被生成。

新增 parseMpxEscapeKeys,通过正则扫描 MpxEscape 结尾的 identifier key,
用 unescapeKey 还原原始类名后交给 transformClasses 处理,再重新编码写回 wxml。

支持的写法:
- wx:class="{{ { 'ml-17rpx': flag } }}" — 普通类名
- wx:class="{{ { 'h-100%': true } }}" — 含特殊字符的类名
- wx:class="{{ { 'ml-17rpx h-100%': flag } }}" — key 中空格分隔的多个类名
- wx:class="{{ { '*card': flag } }}" — alias 展开

同步重构 trans-dynamic-class-expr.js:
- mpEscape → escapeClassName,keyEscape → escapeKey,新增对应的 unescape 函数
- 硬编码字符串提取为具名常量(KEY_ESCAPE_SUFFIX/DASH/SPACE)
- escapeReg 改为从 classNameEscapeMap 动态生成,保持 map 和 reg 同步
- classNameDecodeReg 按 token 长度降序排列,避免短 token 优先匹配

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
当前 classNameDecodeMap 的所有 key 互不重叠,无需按长度降序排列。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread packages/unocss-plugin/lib/index.js Outdated
})
parseMpxEscapeKeys(exp).forEach(({ result, start, end }) => {
const expanded = transformClasses(result, classNameHandler)
expSource.replace(start, end, escapeKey(escapeClassName(expanded)))

@hiyuki hiyuki Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escapeClassName 这里应该不用再做一次这个了,transformClasses的结果应该就已经escape过了

Comment thread packages/webpack-plugin/lib/template-compiler/trans-dynamic-class-expr.js Outdated
Comment thread packages/unocss-plugin/lib/index.js Outdated
unknown: '_u_',
...escapeMap
}
escapeMap = Object.assign({}, mpEscapeMap, { unknown: '_u_' }, escapeMap)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

去除外部传入的escapeMap逻辑,不再接受外部传入

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

拉齐这两个map吧

@hiyuki hiyuki left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本轮 review 发现两个问题:

  1. hasUnoCSS 是 compilation 级全局开关,但 UnoCSS 对模板的资产处理仍受 scan.include/exclude 限制。当前模板编译器在 hasUnoCSS 为 true 时会对所有资源跳过 transDynamicClassExpr,而 processAssets 只会重写命中 scan 规则的模板。默认 scan 仅包含 src/**/*,因此 npm 组件、src 外资源或被显式 exclude 的模板不会经过任一侧的 key 编码。

    例如被排除模板中的 wx:class="{{ { 'foo-bar': flag } }}" 最终仍会输出:

    <view class='{{_s_.c("", ({ "foo-bar": flag }))}}'/>

    未启用 UnoCSS 时则会正确输出 foo_da_barMpxEscape。建议仅对确定会被 UnoCSS 资产阶段处理的资源跳过模板编译器转换,或者让资产阶段对未扫描模板继续执行基础 key 编码,并补充 scan.exclude / src 外资源的回归测试。

  2. 移除 escapeMap 配置及 api/compile.md#escapemap 后,docs-vitepress/articles/2.9-release.mddocs-vitepress/articles/2.9-release-alter.md 仍明确说明用户可以传入该配置,并继续链接已删除的锚点;这与当前指南中“不支持自定义”的说明冲突。请同步更新这两处历史说明,或提供明确的废弃/迁移说明。

@mackwang112

Copy link
Copy Markdown
Collaborator Author

已按建议修复 hasUnoCSS 全局开启但部分模板未经过 UnoCSS 扫描的问题:

  • transDynamicClassExpr 在 hasUnoCSS=true 时仍保留 WXS 对象 key 的基础编码,生成 MpxEscape 后缀,但不处理 mpEscapeMap。
  • UnoCSS 资产处理阶段先通过 unescapeWxsObjectKey 反解 MpxEscape,再进行 alias、规则匹配和最终转义。
  • 因此被 scan.exclude 排除、src 外或 npm 组件等未进入 UnoCSS 处理的模板,仍能保留 WXS 所需的基础 key 编码;进入 UnoCSS 的模板则不会发生重复编码。
  • 已补充模板编译阶段与 UnoCSS 完整处理链路的回归测试,相关动态 class 测试 5/5 通过。

修复提交:c81fdc51e

Comment thread packages/unocss-plugin/lib/index.js Outdated
* @param {boolean} objectKey
* @returns {{start: number, end: number}|undefined}
*/
function findOriginalClassLoc (source, className, objectKey) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个源码定位逻辑太重了感觉没必要,且不是根据sourceMap来定位的,而是根据匹配?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

维护成本过高,还是以实现功能进行最小改动为目标吧,错误定位溯源不是本期目标

@hiyuki hiyuki left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复核最新 head e6a87a2 后,上轮两个问题仍未关闭,建议修复后再合并:

  1. hasUnoCSS 仍是 compilation 级全局开关,但 UnoCSS 的模板资产处理继续受 scan.include/exclude 限制。transDynamicClassExpr 在 hasUnoCSS 为 true 时会对所有资源延迟特殊字符转换,而 processAssets 只重写命中 scan 的模板。因此,被 scan.exclude 排除、位于默认 src/**/* 之外或来自 npm 的模板中,类似 wx:class 对象 key hover:bg-red-100 的冒号仍会保留在字符串 key 中,既没有转换成目标 WXS 所需的合法标识符,也不会由 UnoCSS 资产阶段补齐。当前修改只保留了短横线和空格的基础编码,没有覆盖其他特殊字符。建议将延迟转换的判断细化到资源级,或确保未扫描模板仍执行基础 key 编码,并补充 scan.exclude / src 外资源的回归测试。

  2. 本 PR 删除了 escapeMap 配置实现以及 api/compile.md#escapemap 锚点,但 docs-vitepress/articles/2.9-release.md 和 docs-vitepress/articles/2.9-release-alter.md 仍说明用户可以传入该配置并链接已删除锚点。请同步更新这两处说明,或补充明确的历史版本/迁移说明。

@mackwang112

Copy link
Copy Markdown
Collaborator Author

已根据最新 review 更新:

  • 将是否延迟动态 class key 转换细化到资源级,复用 UnoCSS scan.include/exclude 规则。命中 scan 的模板直接交给 UnoCSS 处理;未命中的模板继续执行 transDynamicClassExpr。
  • 未命中 scan 时仅做 WXS 对象 key 所需的短横线/空格编码,并通过 isValidIdentifierStr 检查最终 key,不额外禁用 mpEscapeMap 中的字符。
  • 补充 loader 层回归测试,覆盖 scan include 命中、exclude 排除和 src 外文件,并补充 UnoCSS 动态 class 测试。
  • 更新 scan 行为文档;两篇 2.9 历史文档也已移除失效的 escapeMap 锚点,并补充配置移除与升级说明。

验证:Webpack Plugin 定向测试 7/7 通过,UnoCSS Plugin 动态 class 测试 6/6 通过,ESLint 与 git diff --check 通过。

提交:c88b4fc6d、d33bd3980

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants