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
28 changes: 0 additions & 28 deletions docs-vitepress/api/compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -1678,34 +1678,6 @@ module.exports = defineConfig({
})
```

### escapeMap

`object`

针对原子类中出现的`[` `(` `,`等特殊字符,在web中会通过转义字符`\`进行转义,由于小程序环境下不支持css选择器中出现`\`转义字符,我们内置支持了一套不带`\`的转义规则对这些特殊字符进行转义,同时替换模版和css文件中的类名,内建的默认转义规则,可自定义转译规则
```js
// vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
pluginOptions: {
mpx: {
unocss: {
escapeMap: {
':': '_d_',
}
}
}
}
})
```
```css
<view class="dark:text-green-400"/>
```
将会转化为
```css
.dark .dark_d_text-green-400{--un-text-opacity:1;color:rgba(74,222,128,var(--un-text-opacity));}
```

### root

`string = process.cwd()`
Expand Down
12 changes: 7 additions & 5 deletions docs-vitepress/guide/advance/utility-first-css.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ plugins.push(new MpxUnocssPlugin())
基于`unocss`的原子类支持`value auto-infer`(值自动推导),可以在模版中根据相关规则书写灵活的自定义值原子类,如`p-5px bg-[hsl(211.7,81.9%,69.6%)]`等,针对原子类中出现的`[` `(` `,`等特殊字符,在web中会通过转义字符`\`进行转义,由于小程序环境下不支持css选择器中出现`\`转义字符,我们内置支持了一套不带`\`的转义规则对这些特殊字符进行转义,同时替换模版和css文件中的类名,内建的默认转义规则如下:

```js
const escapeMap = {
const classEscapeMap = {
'(': '_pl_',
')': '_pr_',
'[': '_bl_',
Expand All @@ -316,13 +316,15 @@ const escapeMap = {
'\'': '_q_',
'"': '_dq_',
'+': '_a_',
$: '_si_',
// unknown用于兜底不在上述范围中未知的转义字符
unknown: '_u_'
$: '_si_'
}
```

与此同时,用户也可以通过传递`@mpxjs/unocss-plugin`的[`escapeMap`配置项](../../api/compile.md#escapemap)来覆盖内建的转义规则。
该转义规则同时用于编译产物与运行时处理,为保证两端结果一致,不支持自定义。对于映射表以外的特殊字符,能够被 UnoCSS 规则正常处理的类名会使用内建兜底规则转义;未被 UnoCSS 处理的类名会输出编译错误。

使用 `@mpxjs/unocss-plugin` 时,`wx:class` 对象字面量中的 key 会和静态 `class` 使用相同的转义规则。插件会在扫描模板时将转义后的 key 转换为小程序可用的标识符,无法转换为合法标识符时会输出编译错误。

未使用 `@mpxjs/unocss-plugin` 时,`wx:class` 对象字面量的 key 仅支持合法标识符以及包含空格或 `-` 的类名,其他特殊字符会输出编译错误。

### 原子类分包输出 {#subpackage}

Expand Down
2 changes: 1 addition & 1 deletion packages/unocss-plugin/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Mpx 与 UnoCSS 的集成插件:在小程序构建中扫描 wxml/mpx 模板提
- 装配 unocss generator(基于 `@unocss/core` + `@unocss/config`)。
- 通过 `MpxWebpackPlugin` 钩子在小程序产物 emit 阶段扫描 wxml 资产,调用 [parser.js](lib/parser.js) 提取 class,生成新增的 wxss 资产。
- 集成 `transformerDirectives` / `transformerVariantGroup`,在样式 transform 阶段调用 [transform.js](lib/transform.js)。
- [lib/parser.js](lib/parser.js):`parseClasses` / `parseStrings` / `parseMustache` / `stringifyAttr` / `parseComments` / `parseCommentConfig`,从模板/字符串/注释中提取 class 与配置
- [lib/parser.js](lib/parser.js):`parseClasses` / `parseClassExpression` / `parseMustache` / `stringifyAttr` / `parseComments` / `parseCommentConfig`,从模板、表达式与注释中提取 class 和配置
- [lib/transform.js](lib/transform.js):`transformStyle` / `buildAliasTransformer` / `transformGroups` / `mpEscape` / `cssRequiresTransform`,处理 unocss → 小程序 wxss 的转义(class 名转义、伪类、组合器等)。
- [lib/source.js](lib/source.js):`getReplaceSource` / `getConcatSource` / `getRawSource`,统一封装 webpack `Source` 对象的创建。
- [lib/platform.js](lib/platform.js):各小程序平台的 preflights / 选择器映射表(被主插件按 `mpx_mode` 取用)。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ exports[`test plugin test-template 3`] = `
.text-12px{font-size:12px;}"
`;

exports[`test plugin test-template 4`] = `"<view wx:class="{{(['translate-_bl_-50_p__br_',{'text-12px text-16px': false},'bg-_h_fff_s_10'])}}" />"`;
exports[`test plugin test-template 4`] = `"<view wx:class="{{(['translate-_bl_-50_p__br_',{text_da_12px_sp_text_da_16pxMpxEscape: false},'bg-_h_fff_s_10'])}}" />"`;

exports[`test plugin test-template 5`] = `
[
"translate-[-50%]",
"bg-#fff/10",
"text-12px",
"text-16px",
"bg-#fff/10",
]
`;

Expand Down
121 changes: 121 additions & 0 deletions packages/unocss-plugin/__tests__/dynamic-class.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { jest } from '@jest/globals'
import compiler from '@mpxjs/webpack-plugin/lib/template-compiler/compiler.js'
import { createGenerator } from '@unocss/core'
import MpxUnocssPlugin from '../lib/index.js'
import { parseClassExpression } from '../lib/parser.js'
import { getRawSource } from '../lib/source.js'

describe('dynamic class object keys', () => {
const plugin = new MpxUnocssPlugin({ config: {} })

async function transformTemplate (content, errors, rules = []) {
const uno = await createGenerator({ rules })
const parseTemplate = plugin.getTemplateParser(uno)
const classes = []
const { newsource } = await parseTemplate(getRawSource(content), (className) => {
if (className) classes.push(className)
return className
}, (error, loc) => errors.push({ error, loc }))
return {
output: newsource.source(),
classes
}
}

test('parses strings and nested non-computed object keys by syntax', () => {
const result = parseClassExpression("({ \"foo'bar\": flag, [dynamic]: 'computed', nested: { 'hover:bg-red-100': flag }, active: flag ? 'text-red-500' : \"text-gray-500\" })")

expect(result.objectKeys.map(key => key.result)).toEqual(["foo'bar", 'nested', 'hover:bg-red-100', 'active'])
expect(result.strings.map(string => string.result)).toEqual(['computed', 'text-red-500', 'text-gray-500'])
})

test('uses the same escaping for static and dynamic class names', async () => {
const templateErrors = []
const pluginErrors = []
const parsed = compiler.parse('<view class="text-24rpx hover:bg-blue-100" wx:class="{{ { \'hover:bg-red-100\': flag } }}" />', {
mode: 'wx',
srcMode: 'wx',
defs: {},
usingComponentsInfo: {},
externalClasses: [],
hasUnoCSS: true,
warn: jest.fn(),
error: error => templateErrors.push(error)
})
const { output, classes } = await transformTemplate(compiler.serialize(parsed.root), pluginErrors)

expect(templateErrors).toEqual([])
expect(pluginErrors).toEqual([])
expect(plugin.options).not.toHaveProperty('escapeMap')
expect(classes).toEqual(expect.arrayContaining(['text-24rpx', 'hover:bg-blue-100', 'hover:bg-red-100']))
expect(output).toContain('"text-24rpx hover_c_bg-blue-100"')
expect(output).toMatch(/hover_c_bg_da_red_da_100MpxEscape:\s*flag/)
})

test('allows configured classes containing special characters', async () => {
const errors = []
const { output } = await transformTemplate(
'<view class="custom@blue" wx:class="{{ { \'custom@red\': flag } }}" />',
errors,
[
[/^custom@(red|blue)$/, () => ({ color: 'red' })]
]
)

expect(output).toContain('class="custom_u_blue"')
expect(output).toMatch(/custom_u_red:\s*flag/)
expect(errors).toEqual([])
})

test('reports unhandled static class names containing unsupported characters', async () => {
const errors = []
const { output } = await transformTemplate('<view class="qwe@da *asd" />', errors)

expect(output).toContain('class="qwe_u_da _u_asd"')
expect(errors).toEqual([
{
error: 'Classname [qwe@da] contains unsupported character [@].',
loc: {
className: 'qwe@da',
start: 13,
end: 23
}
},
{
error: 'Classname [*asd] contains unsupported character [*].',
loc: {
className: '*asd',
start: 13,
end: 23
}
}
])
})

test('reports class object keys that can not become valid identifiers', async () => {
const errors = []
const { output } = await transformTemplate('<view wx:class="{{ { \'custom😀red\': flag, 12: flag } }}" />', errors)

expect(output).toContain("'custom😀red': flag")
expect(errors).toEqual([
{
error: 'Dynamic classname [custom😀red] can not be escaped as a valid identifier, which is not supported.',
loc: {
className: 'custom😀red',
objectKey: true,
start: 16,
end: 54
}
},
{
error: 'Dynamic classname [12] can not be escaped as a valid identifier, which is not supported.',
loc: {
className: '12',
objectKey: true,
start: 16,
end: 54
}
}
])
})
})
7 changes: 3 additions & 4 deletions packages/unocss-plugin/__tests__/plugin.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import MpxUnocssPlugin from '../lib/index.js'
import { getRawSource } from '../lib/source.js'
import { createGenerator, e as cssEscape } from '@unocss/core'
import { mpEscape } from '../lib/transform.js'
import { createGenerator } from '@unocss/core'
import presetMpx from '@mpxjs/unocss-base/lib/index.js'

// const { presetLegacyCompat } = require('@unocss/preset-legacy-compat')
Expand Down Expand Up @@ -39,12 +38,12 @@ describe('test plugin', () => {
}) {
const source = getRawSource(content)
const classmap = {}
const { newsource } = parseTemplate(source, (className) => {
const { newsource } = await parseTemplate(source, (className) => {
if (!className) {
return className
}
classmap[className] = true
return mpEscape(cssEscape(className), plugin.options.escapeMap)
return className
})
// 测试模板是否转义
expect(newsource.source()).toMatchSnapshot()
Expand Down
Loading
Loading