Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit 761f785

Browse files
committed
feat(compiler-vapor): invalid html nesting
1 parent a68445b commit 761f785

File tree

8 files changed

+88
-33
lines changed

8 files changed

+88
-33
lines changed

packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ export function render(_ctx) {
1010
`;
1111

1212
exports[`compiler: element transform > component > generate multi root component 1`] = `
13-
"import { createComponent as _createComponent } from 'vue/vapor';
13+
"import { createComponent as _createComponent, template as _template } from 'vue/vapor';
14+
const t0 = _template("123")
1415
1516
export function render(_ctx) {
17+
const n1 = t0()
1618
const n0 = _createComponent(_ctx.Comp)
1719
return [n0, n1]
1820
}"
@@ -168,6 +170,23 @@ export function render(_ctx) {
168170
}"
169171
`;
170172

173+
exports[`compiler: element transform > invalid html nesting 1`] = `
174+
"import { insert as _insert, template as _template } from 'vue/vapor';
175+
const t0 = _template("<div>123</div>")
176+
const t1 = _template("<p></p>")
177+
const t2 = _template("<form></form>")
178+
179+
export function render(_ctx) {
180+
const n1 = t1()
181+
const n0 = t0()
182+
const n3 = t2()
183+
const n2 = t2()
184+
_insert(n0, n1)
185+
_insert(n2, n3)
186+
return [n1, n3]
187+
}"
188+
`;
189+
171190
exports[`compiler: element transform > props + children 1`] = `
172191
"import { template as _template } from 'vue/vapor';
173192
const t0 = _template("<div id=\\"foo\\"><span></span></div>")

packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
IRNodeTypes,
44
transformChildren,
55
transformElement,
6+
transformText,
67
transformVBind,
78
transformVOn,
89
} from '../../src'
@@ -13,7 +14,7 @@ import {
1314
} from '@vue/compiler-core'
1415

1516
const compileWithElementTransform = makeCompile({
16-
nodeTransforms: [transformElement, transformChildren],
17+
nodeTransforms: [transformElement, transformChildren, transformText],
1718
directiveTransforms: {
1819
bind: transformVBind,
1920
on: transformVOn,
@@ -689,4 +690,24 @@ describe('compiler: element transform', () => {
689690
])
690691
expect(code).contains('_setDynamicEvents(n0, _ctx.obj)')
691692
})
693+
694+
test('invalid html nesting', () => {
695+
const { code, ir } = compileWithElementTransform(
696+
`<p><div>123</div></p>
697+
<form><form/></form>`,
698+
)
699+
expect(code).toMatchSnapshot()
700+
expect(ir.template).toEqual(['<div>123</div>', '<p></p>', '<form></form>'])
701+
expect(ir.block.dynamic).toMatchObject({
702+
children: [
703+
{ id: 1, template: 1, children: [{ id: 0, template: 0 }] },
704+
{ id: 3, template: 2, children: [{ id: 2, template: 2 }] },
705+
],
706+
})
707+
708+
expect(ir.block.operation).toMatchObject([
709+
{ type: IRNodeTypes.INSERT_NODE, parent: 1, elements: [0] },
710+
{ type: IRNodeTypes.INSERT_NODE, parent: 3, elements: [2] },
711+
])
712+
})
692713
})

packages/compiler-vapor/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"dependencies": {
4040
"@vue/compiler-dom": "workspace:*",
4141
"@vue/shared": "workspace:*",
42-
"source-map-js": "^1.0.2"
42+
"source-map-js": "^1.0.2",
43+
"validate-html-nesting": "^1.2.2"
4344
}
4445
}

packages/compiler-vapor/src/transform.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ export interface TransformContext<T extends AllNode = AllNode> {
7878
enterBlock(ir: TransformContext['block'], isVFor?: boolean): () => void
7979
reference(): number
8080
increaseId(): number
81-
registerTemplate(): number
81+
pushTemplate(template: string): number
82+
registerTemplate(customTemplate?: string): number
8283
registerEffect(
8384
expressions: SimpleExpressionNode[],
8485
operation: OperationNode[],
@@ -133,6 +134,7 @@ function createRootContext(
133134
inVFor && this.inVFor++
134135
return () => {
135136
// exit
137+
this.registerTemplate()
136138
this.block = block
137139
this.template = template
138140
this.dynamic = dynamic
@@ -180,20 +182,16 @@ function createRootContext(
180182

181183
template: '',
182184
childrenTemplate: [],
185+
pushTemplate(content) {
186+
const existing = root.template.findIndex(template => template === content)
187+
if (existing !== -1) return existing
188+
root.template.push(content)
189+
return root.template.length - 1
190+
},
183191
registerTemplate() {
184-
if (!this.template) {
185-
return -1
186-
}
187-
188-
const existing = root.template.findIndex(
189-
template => template === this.template,
190-
)
191-
if (existing !== -1) {
192-
return (this.dynamic.template = existing)
193-
}
194-
195-
root.template.push(this.template)
196-
return (this.dynamic.template = root.template.length - 1)
192+
if (!this.template) return -1
193+
const id = this.pushTemplate(this.template)
194+
return (this.dynamic.template = id)
197195
},
198196
registerOperation(...node) {
199197
this.block.operation.push(...node)

packages/compiler-vapor/src/transforms/transformElement.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isValidHTMLNesting } from 'validate-html-nesting'
12
import {
23
type AttributeNode,
34
type ElementNode,
@@ -44,9 +45,8 @@ export const transformElement: NodeTransform = (node, context) => {
4445
(node.tagType === ElementTypes.ELEMENT ||
4546
node.tagType === ElementTypes.COMPONENT)
4647
)
47-
) {
48+
)
4849
return
49-
}
5050

5151
const { tag, tagType } = node
5252
const isComponent = tagType === ElementTypes.COMPONENT
@@ -59,7 +59,7 @@ export const transformElement: NodeTransform = (node, context) => {
5959
;(isComponent ? transformComponentElement : transformNativeElement)(
6060
tag,
6161
propsResult,
62-
context,
62+
context as TransformContext<ElementNode>,
6363
)
6464
}
6565
}
@@ -121,12 +121,14 @@ function resolveSetupReference(name: string, context: TransformContext) {
121121
function transformNativeElement(
122122
tag: string,
123123
propsResult: ReturnType<typeof buildProps>,
124-
context: TransformContext,
124+
context: TransformContext<ElementNode>,
125125
) {
126126
const { scopeId } = context.options
127127

128-
context.template += `<${tag}`
129-
if (scopeId) context.template += ` ${scopeId}`
128+
let template = ''
129+
130+
template += `<${tag}`
131+
if (scopeId) template += ` ${scopeId}`
130132

131133
if (propsResult[0] /* dynamic props */) {
132134
const [, dynamicArgs, expressions] = propsResult
@@ -141,8 +143,8 @@ function transformNativeElement(
141143
for (const prop of propsResult[1]) {
142144
const { key, values } = prop
143145
if (key.isStatic && values.length === 1 && values[0].isStatic) {
144-
context.template += ` ${key.content}`
145-
if (values[0].content) context.template += `="${values[0].content}"`
146+
template += ` ${key.content}`
147+
if (values[0].content) template += `="${values[0].content}"`
146148
} else {
147149
context.registerEffect(values, [
148150
{
@@ -155,10 +157,22 @@ function transformNativeElement(
155157
}
156158
}
157159

158-
context.template += `>` + context.childrenTemplate.join('')
160+
template += `>` + context.childrenTemplate.join('')
159161
// TODO remove unnecessary close tag, e.g. if it's the last element of the template
160162
if (!isVoidTag(tag)) {
161-
context.template += `</${tag}>`
163+
template += `</${tag}>`
164+
}
165+
166+
if (
167+
context.parent &&
168+
context.parent.node.type === NodeTypes.ELEMENT &&
169+
!isValidHTMLNesting(context.parent.node.tag, tag)
170+
) {
171+
context.reference()
172+
context.dynamic.template = context.pushTemplate(template)
173+
context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE
174+
} else {
175+
context.template += template
162176
}
163177
}
164178

packages/compiler-vapor/src/transforms/vFor.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ export function processFor(
6464
context.reference()
6565

6666
return () => {
67-
context.registerTemplate()
6867
exitBlock()
6968
context.registerOperation({
7069
type: IRNodeTypes.FOR,

packages/compiler-vapor/src/transforms/vIf.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,5 @@ export function createIfBranch(
128128

129129
const exitBlock = context.enterBlock(branch)
130130
context.reference()
131-
const onExit = () => {
132-
context.registerTemplate()
133-
exitBlock()
134-
}
135-
return [branch, onExit]
131+
return [branch, exitBlock]
136132
}

pnpm-lock.yaml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)