Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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)))

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.

对于objectkey可以只记录不替换吧

})
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')

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.

这个unescapeKey维护在当前包内吧,webpack-plugin中本身不需要这个


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) {

@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.

这个文件不需要改动

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) {
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