Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/unocss-plugin/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ const transformerVariantGroup = require('@unocss/transformer-variant-group')
const {
parseClasses,
parseStrings,
parseMpxEscapeKeys,
parseMustache,
stringifyAttr,
parseComments,
parseCommentConfig
} = require('./parser')
const { escapeKey, escapeClassName } = require('@mpxjs/webpack-plugin/lib/template-compiler/trans-dynamic-class-expr')
const { getReplaceSource, getConcatSource, getRawSource } = require('./source')
const {
transformStyle,
Expand Down Expand Up @@ -358,6 +360,10 @@ class MpxUnocssPlugin {
result = transformClasses(result, classNameHandler)
expSource.replace(start, end, result)
})
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过了

})
return expSource.source()
}, str => transformClasses(str, classNameHandler))
if (replaced) {
Expand Down
24 changes: 24 additions & 0 deletions packages/unocss-plugin/lib/parser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { parseMustache, stringifyAttr } = require('@mpxjs/webpack-plugin/lib/template-compiler/compiler')
const { unescapeKey } = require('@mpxjs/webpack-plugin/lib/template-compiler/trans-dynamic-class-expr')

function parseClasses (content) {
const output = []
Expand Down Expand Up @@ -78,9 +79,32 @@ function parseStrings (content) {
return output
}

// 匹配对象字面量中标识符形式的 key,如 { ml_da_17rpxMpxEscape: flag, a: true }
// key 前面必须是 { 或 ,(加可选空格),后面是 :
const objKeyReg = /(?:[{,]\s*)([\w-]+?)(?=\s*:)/gm

function parseMpxEscapeKeys (content) {
const output = []
if (!content) { return output }
let match
objKeyReg.lastIndex = 0
while (match = objKeyReg.exec(content)) {
const raw = match[1]
const end = match.index + match[0].length - 1
const start = end - raw.length + 1
output.push({
result: unescapeKey(raw),
start,
end
})
}
return output
}

module.exports = {
parseClasses,
parseStrings,
parseMpxEscapeKeys,
parseComments,
parseCommentConfig,
parseMustache,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ const t = require('@babel/types')
const traverse = require('@babel/traverse').default
const generate = require('@babel/generator').default
const isValidIdentifierStr = require('../utils/is-valid-identifier-str')
const escapeReg = /[()[\]{}#!.:,%'"+$]/g
const escapeMap = {
function escapeRegExp (str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
const classNameEscapeMap = {
'(': '_pl_',
')': '_pr_',
'[': '_bl_',
Expand All @@ -23,22 +25,66 @@ const escapeMap = {
'+': '_a_',
$: '_si_'
}
const classNameEscapeReg = new RegExp('[' + Object.keys(classNameEscapeMap).map(escapeRegExp).join('') + ']', 'g')

// classNameEscapeMap 的反向映射,用于还原 escapeClassName 编码
const classNameDecodeMap = Object.keys(classNameEscapeMap).reduce((acc, key) => {
acc[classNameEscapeMap[key]] = key
return acc
}, {})
const classNameDecodeReg = new RegExp(Object.keys(classNameDecodeMap).map(escapeRegExp).join('|'), 'g')

function mpEscape (str) {
return str.replace(escapeReg, function (match) {
if (escapeMap[match]) return escapeMap[match]
function escapeClassName (str) {
return str.replace(classNameEscapeReg, function (match) {
if (classNameEscapeMap[match]) return classNameEscapeMap[match]
// unknown escaped
return '_u_'
})
}

function keyEscape (str) {
let result = str.replace(/-/g, '_da_').replace(/\s+/g, '_sp_')
if (result !== str) result += 'MpxEscape'
return result
function unescapeClassName (str) {
return str.replace(classNameDecodeReg, m => classNameDecodeMap[m] || m)
}

const KEY_ESCAPE_SUFFIX = 'MpxEscape'
const KEY_ESCAPE_DASH = '_da_'
const KEY_ESCAPE_SPACE = '_sp_'

const keyEscapeMap = {
'-': KEY_ESCAPE_DASH,
' ': KEY_ESCAPE_SPACE,
'*': '_st_'
}
const keyDecodeMap = Object.keys(keyEscapeMap).reduce((acc, key) => {
acc[keyEscapeMap[key]] = key
return acc
}, {})
const keyDecodeReg = new RegExp(Object.keys(keyDecodeMap).map(escapeRegExp).join('|'), 'g')

function escapeKey (str) {
const result = str.replace(/-/g, KEY_ESCAPE_DASH).replace(/\s+/g, KEY_ESCAPE_SPACE).replace(/\*/g, '_st_')
if (result !== str) return result + KEY_ESCAPE_SUFFIX
return str
}

function unescapeKey (str) {
Comment thread
mackwang112 marked this conversation as resolved.
Outdated
if (str.endsWith(KEY_ESCAPE_SUFFIX)) {
return unescapeClassName(
str.slice(0, -KEY_ESCAPE_SUFFIX.length).replace(keyDecodeReg, m => keyDecodeMap[m])
)
}
return str
}

module.exports = transDynamicClassExpr
module.exports.KEY_ESCAPE_SUFFIX = KEY_ESCAPE_SUFFIX
module.exports.KEY_ESCAPE_DASH = KEY_ESCAPE_DASH
module.exports.KEY_ESCAPE_SPACE = KEY_ESCAPE_SPACE
module.exports.unescapeKey = unescapeKey
module.exports.escapeKey = escapeKey
module.exports.escapeClassName = escapeClassName

module.exports = function transDynamicClassExpr (expr, { error } = {}) {
function transDynamicClassExpr (expr, { error } = {}) {
try {
const ast = babylon.parse(expr, {
plugins: [
Expand All @@ -50,7 +96,7 @@ module.exports = function transDynamicClassExpr (expr, { error } = {}) {
path.node.properties.forEach((property) => {
if (t.isObjectProperty(property) && !property.computed) {
const rawPropertyName = property.key.name || property.key.value
const propertyName = keyEscape(mpEscape(rawPropertyName))
const propertyName = escapeKey(escapeClassName(rawPropertyName))
if (!isValidIdentifierStr(propertyName)) {
error && error(`Dynamic classname [${rawPropertyName}] can not be escaped as a valid identifier, which is not supported.`)
} else {
Expand Down
Loading