Skip to content

Commit b15b308

Browse files
committed
fix(mp): 修复 input 嵌套 keyboard-accessory 时编译后两者未保持嵌套层级的Bug (question/209237)
1 parent 50e2c47 commit b15b308

File tree

4 files changed

+186
-6
lines changed

4 files changed

+186
-6
lines changed

packages/uni-mp-compiler/src/parserOptions.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ export const enum DOMNamespaces {
1515

1616
export const parserOptions: ParserOptions = {
1717
isVoidTag(tag) {
18-
// 开启 input 标签判断,会导致 <input type="text"> 编译失败
19-
// 微信小程序允许 Input 嵌套其他组件 https://ask.dcloud.net.cn/question/202776
20-
// if (tag === 'input') {
21-
// return false
22-
// }
18+
// 微信小程序允许 input 嵌套其他组件 https://ask.dcloud.net.cn/question/202776
19+
if (tag === 'input') {
20+
return false
21+
}
2322
return isVoidTagRaw(tag)
2423
},
2524
isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { formatInputTag } from '../src/plugins/input'
2+
3+
describe('test input tag', () => {
4+
test('with end input tag', () => {
5+
expect(
6+
formatInputTag(`
7+
<template>
8+
<view class="content">
9+
<image class="logo" src="/static/logo.png"></image>
10+
<view class="text-area">
11+
<text class="title">{{ title }}</text>
12+
</view>
13+
</view>
14+
</template>`)
15+
).toBe(`
16+
<template>
17+
<view class="content">
18+
<image class="logo" src="/static/logo.png"></image>
19+
<view class="text-area">
20+
<text class="title">{{ title }}</text>
21+
</view>
22+
</view>
23+
</template>`)
24+
25+
expect(
26+
formatInputTag(
27+
`<template><view type="text"><input type="text" /></view></template>`
28+
)
29+
).toBe(
30+
`<template><view type="text"><input type="text" /></view></template>`
31+
)
32+
33+
expect(formatInputTag(`<template><input type="text" /></template>`)).toBe(
34+
`<template><input type="text" /></template>`
35+
)
36+
37+
expect(
38+
formatInputTag(`
39+
<template>
40+
<input class="hidden-input" hold-keyboard type="digit" >
41+
<keyboard-accessory>
42+
<cover-view class="custom__list">
43+
<cover-view class="custom__item" v-for="i in 4" :key="i" @click="change(i)">{{i * 5}}</cover-view>
44+
</cover-view>
45+
</keyboard-accessory>
46+
</input>
47+
</template>`)
48+
).toBe(`
49+
<template>
50+
<input class="hidden-input" hold-keyboard type="digit" >
51+
<keyboard-accessory>
52+
<cover-view class="custom__list">
53+
<cover-view class="custom__item" v-for="i in 4" :key="i" @click="change(i)">{{i * 5}}</cover-view>
54+
</cover-view>
55+
</keyboard-accessory>
56+
</input>
57+
</template>`)
58+
})
59+
60+
test('no end input tag', () => {
61+
expect(formatInputTag(`<template><input ></template>`)).toBe(
62+
`<template><input /></template>`
63+
)
64+
65+
expect(
66+
formatInputTag(
67+
`<template><input id="input1" class="test" type="text" ></template>`
68+
)
69+
).toBe(
70+
`<template><input id="input1" class="test" type="text" /></template>`
71+
)
72+
73+
expect(
74+
formatInputTag(
75+
`<template><input id="input1" class="test" type="text" ><input type="text" /></template>`
76+
)
77+
).toBe(
78+
`<template><input id="input1" class="test" type="text" /><input type="text" /></template>`
79+
)
80+
81+
expect(
82+
formatInputTag(`
83+
<template>
84+
<input type="text">
85+
<input type="checkbox"></input>
86+
<input type="radio" />
87+
<input type="file">Some content</input>
88+
<input type="text">xxx</input>
89+
<input>
90+
<keyboard-accessory>
91+
234
92+
</keyboard-accessory>
93+
</input>
94+
<input type="text" disabled></template>`)
95+
).toBe(`
96+
<template>
97+
<input type="text" />
98+
<input type="checkbox"></input>
99+
<input type="radio" />
100+
<input type="file">Some content</input>
101+
<input type="text">xxx</input>
102+
<input>
103+
<keyboard-accessory>
104+
234
105+
</keyboard-accessory>
106+
</input>
107+
<input type="text" disabled /></template>`)
108+
})
109+
})

packages/uni-mp-vite/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { uniMainJsPlugin } from './plugins/mainJs'
2525
import { uniManifestJsonPlugin } from './plugins/manifestJson'
2626
import { uniPagesJsonPlugin } from './plugins/pagesJson'
2727
import { uniEntryPlugin } from './plugins/entry'
28-
28+
import { uniInputAutoClosePlugin } from './plugins/input'
2929
import { uniRenderjsPlugin } from './plugins/renderjs'
3030
import { uniRuntimeHooksPlugin } from './plugins/runtimeHooks'
3131
import { uniSubpackagePlugin } from './plugins/subpackage'
@@ -52,6 +52,7 @@ export default (options: UniMiniProgramPluginOptions) => {
5252
)
5353

5454
return [
55+
uniInputAutoClosePlugin(),
5556
...(isEnableConsole() ? [uniHBuilderXConsolePlugin('uni.__f__')] : []),
5657
...(process.env.UNI_APP_X === 'true'
5758
? [
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
export function formatInputTag(code: string) {
2+
const inputStartRegex = /<input(\s+[^>]*)?>/g
3+
let result = code
4+
let match
5+
6+
// 收集所有<input>标签的位置
7+
const inputStarts: Array<{
8+
index: number
9+
length: number
10+
fullMatch: string
11+
}> = []
12+
while ((match = inputStartRegex.exec(code)) !== null) {
13+
inputStarts.push({
14+
index: match.index,
15+
length: match[0].length,
16+
fullMatch: match[0],
17+
})
18+
}
19+
20+
// 检查每个<input>是否有对应的</input>
21+
const toReplace: Array<{
22+
index: number
23+
length: number
24+
replacement: string
25+
}> = []
26+
for (let i = 0; i < inputStarts.length; i++) {
27+
const start = inputStarts[i]
28+
const nextStart = inputStarts[i + 1]?.index || code.length
29+
const substring = code.slice(start.index + start.length, nextStart)
30+
31+
// 检查是否有对应的</input>
32+
const hasClosingTag = substring.includes('</input>')
33+
34+
// 检查是否是自闭合形式(以/>结尾)
35+
const isSelfClosing = start.fullMatch.endsWith('/>')
36+
37+
if (!hasClosingTag && !isSelfClosing) {
38+
// 需要转换的标签
39+
toReplace.push({
40+
index: start.index,
41+
length: start.length,
42+
replacement: start.fullMatch.replace(/>$/, ' />'),
43+
})
44+
}
45+
}
46+
47+
// 从后往前替换,避免影响索引
48+
for (let i = toReplace.length - 1; i >= 0; i--) {
49+
const item = toReplace[i]
50+
result =
51+
result.slice(0, item.index) +
52+
item.replacement +
53+
result.slice(item.index + item.length)
54+
}
55+
56+
return result
57+
}
58+
59+
export function uniInputAutoClosePlugin() {
60+
return {
61+
name: 'uni:mp-input-auto-close',
62+
enforce: 'pre',
63+
transform(code, id) {
64+
if (!id.endsWith('.vue')) {
65+
return
66+
}
67+
68+
return formatInputTag(code)
69+
},
70+
}
71+
}

0 commit comments

Comments
 (0)