Skip to content
Closed
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
9 changes: 4 additions & 5 deletions packages/uni-mp-compiler/src/parserOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ export const enum DOMNamespaces {

export const parserOptions: ParserOptions = {
isVoidTag(tag) {
// 开启 input 标签判断,会导致 <input type="text"> 编译失败
// 微信小程序允许 Input 嵌套其他组件 https://ask.dcloud.net.cn/question/202776
// if (tag === 'input') {
// return false
// }
// 微信小程序允许 input 嵌套其他组件 https://ask.dcloud.net.cn/question/202776
if (tag === 'input') {
return false
}
return isVoidTagRaw(tag)
},
isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),
Expand Down
109 changes: 109 additions & 0 deletions packages/uni-mp-vite/__tests__/input.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { formatInputTag } from '../src/plugins/input'

describe('test input tag', () => {
test('with end input tag', () => {
expect(
formatInputTag(`
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">{{ title }}</text>
</view>
</view>
</template>`)
).toBe(`
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">{{ title }}</text>
</view>
</view>
</template>`)

expect(
formatInputTag(
`<template><view type="text"><input type="text" /></view></template>`
)
).toBe(
`<template><view type="text"><input type="text" /></view></template>`
)

expect(formatInputTag(`<template><input type="text" /></template>`)).toBe(
`<template><input type="text" /></template>`
)

expect(
formatInputTag(`
<template>
<input class="hidden-input" hold-keyboard type="digit" >
<keyboard-accessory>
<cover-view class="custom__list">
<cover-view class="custom__item" v-for="i in 4" :key="i" @click="change(i)">{{i * 5}}</cover-view>
</cover-view>
</keyboard-accessory>
</input>
</template>`)
).toBe(`
<template>
<input class="hidden-input" hold-keyboard type="digit" >
<keyboard-accessory>
<cover-view class="custom__list">
<cover-view class="custom__item" v-for="i in 4" :key="i" @click="change(i)">{{i * 5}}</cover-view>
</cover-view>
</keyboard-accessory>
</input>
</template>`)
})

test('no end input tag', () => {
expect(formatInputTag(`<template><input ></template>`)).toBe(
`<template><input /></template>`
)

expect(
formatInputTag(
`<template><input id="input1" class="test" type="text" ></template>`
)
).toBe(
`<template><input id="input1" class="test" type="text" /></template>`
)

expect(
formatInputTag(
`<template><input id="input1" class="test" type="text" ><input type="text" /></template>`
)
).toBe(
`<template><input id="input1" class="test" type="text" /><input type="text" /></template>`
)

expect(
formatInputTag(`
<template>
<input type="text">
<input type="checkbox"></input>
<input type="radio" />
<input type="file">Some content</input>
<input type="text">xxx</input>
<input>
<keyboard-accessory>
234
</keyboard-accessory>
</input>
<input type="text" disabled></template>`)
).toBe(`
<template>
<input type="text" />
<input type="checkbox"></input>
<input type="radio" />
<input type="file">Some content</input>
<input type="text">xxx</input>
<input>
<keyboard-accessory>
234
</keyboard-accessory>
</input>
<input type="text" disabled /></template>`)
})
})
3 changes: 2 additions & 1 deletion packages/uni-mp-vite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { uniMainJsPlugin } from './plugins/mainJs'
import { uniManifestJsonPlugin } from './plugins/manifestJson'
import { uniPagesJsonPlugin } from './plugins/pagesJson'
import { uniEntryPlugin } from './plugins/entry'

import { uniInputAutoClosePlugin } from './plugins/input'
import { uniRenderjsPlugin } from './plugins/renderjs'
import { uniRuntimeHooksPlugin } from './plugins/runtimeHooks'
import { uniSubpackagePlugin } from './plugins/subpackage'
Expand All @@ -52,6 +52,7 @@ export default (options: UniMiniProgramPluginOptions) => {
)

return [
uniInputAutoClosePlugin(),
...(isEnableConsole() ? [uniHBuilderXConsolePlugin('uni.__f__')] : []),
...(process.env.UNI_APP_X === 'true'
? [
Expand Down
74 changes: 74 additions & 0 deletions packages/uni-mp-vite/src/plugins/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
export function formatInputTag(code: string) {
const inputStartRegex = /<input(\s+[^>]*)?>/g
let result = code
let match

// 收集所有<input>标签的位置
const inputStarts: Array<{
index: number
length: number
fullMatch: string
}> = []
while ((match = inputStartRegex.exec(code)) !== null) {
inputStarts.push({
index: match.index,
length: match[0].length,
fullMatch: match[0],
})
}

// 检查每个<input>是否有对应的</input>
const toReplace: Array<{
index: number
length: number
replacement: string
}> = []
for (let i = 0; i < inputStarts.length; i++) {
const start = inputStarts[i]
const nextStart = inputStarts[i + 1]?.index || code.length
const substring = code.slice(start.index + start.length, nextStart)

// 检查是否有对应的</input>
const hasClosingTag = substring.includes('</input>')

// 检查是否是自闭合形式(以/>结尾)
const isSelfClosing = start.fullMatch.endsWith('/>')

if (!hasClosingTag && !isSelfClosing) {
// 需要转换的标签
toReplace.push({
index: start.index,
length: start.length,
replacement: start.fullMatch.replace(/>$/, ' />'),
})
}
}

// 从后往前替换,避免影响索引
for (let i = toReplace.length - 1; i >= 0; i--) {
const item = toReplace[i]
result =
result.slice(0, item.index) +
item.replacement +
result.slice(item.index + item.length)
}

return result
}

export function uniInputAutoClosePlugin() {
return {
name: 'uni:mp-input-auto-close',
enforce: 'pre',
transform(code, id) {
if (!/\.(vue|nvue|uvue)$/.test(id)) {
return
}
if (!code.includes('<input')) {
return
}

return formatInputTag(code)
},
}
}
Loading