feat(compiler-vapor): invalid html nesting
This commit is contained in:
parent
a68445bdac
commit
761f785b30
|
@ -10,9 +10,11 @@ export function render(_ctx) {
|
|||
`;
|
||||
|
||||
exports[`compiler: element transform > component > generate multi root component 1`] = `
|
||||
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||
"import { createComponent as _createComponent, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("123")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n1 = t0()
|
||||
const n0 = _createComponent(_ctx.Comp)
|
||||
return [n0, n1]
|
||||
}"
|
||||
|
@ -168,6 +170,23 @@ export function render(_ctx) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: element transform > invalid html nesting 1`] = `
|
||||
"import { insert as _insert, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div>123</div>")
|
||||
const t1 = _template("<p></p>")
|
||||
const t2 = _template("<form></form>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n1 = t1()
|
||||
const n0 = t0()
|
||||
const n3 = t2()
|
||||
const n2 = t2()
|
||||
_insert(n0, n1)
|
||||
_insert(n2, n3)
|
||||
return [n1, n3]
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: element transform > props + children 1`] = `
|
||||
"import { template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div id=\\"foo\\"><span></span></div>")
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
IRNodeTypes,
|
||||
transformChildren,
|
||||
transformElement,
|
||||
transformText,
|
||||
transformVBind,
|
||||
transformVOn,
|
||||
} from '../../src'
|
||||
|
@ -13,7 +14,7 @@ import {
|
|||
} from '@vue/compiler-core'
|
||||
|
||||
const compileWithElementTransform = makeCompile({
|
||||
nodeTransforms: [transformElement, transformChildren],
|
||||
nodeTransforms: [transformElement, transformChildren, transformText],
|
||||
directiveTransforms: {
|
||||
bind: transformVBind,
|
||||
on: transformVOn,
|
||||
|
@ -689,4 +690,24 @@ describe('compiler: element transform', () => {
|
|||
])
|
||||
expect(code).contains('_setDynamicEvents(n0, _ctx.obj)')
|
||||
})
|
||||
|
||||
test('invalid html nesting', () => {
|
||||
const { code, ir } = compileWithElementTransform(
|
||||
`<p><div>123</div></p>
|
||||
<form><form/></form>`,
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(ir.template).toEqual(['<div>123</div>', '<p></p>', '<form></form>'])
|
||||
expect(ir.block.dynamic).toMatchObject({
|
||||
children: [
|
||||
{ id: 1, template: 1, children: [{ id: 0, template: 0 }] },
|
||||
{ id: 3, template: 2, children: [{ id: 2, template: 2 }] },
|
||||
],
|
||||
})
|
||||
|
||||
expect(ir.block.operation).toMatchObject([
|
||||
{ type: IRNodeTypes.INSERT_NODE, parent: 1, elements: [0] },
|
||||
{ type: IRNodeTypes.INSERT_NODE, parent: 3, elements: [2] },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"dependencies": {
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"source-map-js": "^1.0.2"
|
||||
"source-map-js": "^1.0.2",
|
||||
"validate-html-nesting": "^1.2.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,8 @@ export interface TransformContext<T extends AllNode = AllNode> {
|
|||
enterBlock(ir: TransformContext['block'], isVFor?: boolean): () => void
|
||||
reference(): number
|
||||
increaseId(): number
|
||||
registerTemplate(): number
|
||||
pushTemplate(template: string): number
|
||||
registerTemplate(customTemplate?: string): number
|
||||
registerEffect(
|
||||
expressions: SimpleExpressionNode[],
|
||||
operation: OperationNode[],
|
||||
|
@ -133,6 +134,7 @@ function createRootContext(
|
|||
inVFor && this.inVFor++
|
||||
return () => {
|
||||
// exit
|
||||
this.registerTemplate()
|
||||
this.block = block
|
||||
this.template = template
|
||||
this.dynamic = dynamic
|
||||
|
@ -180,20 +182,16 @@ function createRootContext(
|
|||
|
||||
template: '',
|
||||
childrenTemplate: [],
|
||||
pushTemplate(content) {
|
||||
const existing = root.template.findIndex(template => template === content)
|
||||
if (existing !== -1) return existing
|
||||
root.template.push(content)
|
||||
return root.template.length - 1
|
||||
},
|
||||
registerTemplate() {
|
||||
if (!this.template) {
|
||||
return -1
|
||||
}
|
||||
|
||||
const existing = root.template.findIndex(
|
||||
template => template === this.template,
|
||||
)
|
||||
if (existing !== -1) {
|
||||
return (this.dynamic.template = existing)
|
||||
}
|
||||
|
||||
root.template.push(this.template)
|
||||
return (this.dynamic.template = root.template.length - 1)
|
||||
if (!this.template) return -1
|
||||
const id = this.pushTemplate(this.template)
|
||||
return (this.dynamic.template = id)
|
||||
},
|
||||
registerOperation(...node) {
|
||||
this.block.operation.push(...node)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { isValidHTMLNesting } from 'validate-html-nesting'
|
||||
import {
|
||||
type AttributeNode,
|
||||
type ElementNode,
|
||||
|
@ -44,9 +45,8 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
(node.tagType === ElementTypes.ELEMENT ||
|
||||
node.tagType === ElementTypes.COMPONENT)
|
||||
)
|
||||
) {
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const { tag, tagType } = node
|
||||
const isComponent = tagType === ElementTypes.COMPONENT
|
||||
|
@ -59,7 +59,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
;(isComponent ? transformComponentElement : transformNativeElement)(
|
||||
tag,
|
||||
propsResult,
|
||||
context,
|
||||
context as TransformContext<ElementNode>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -121,12 +121,14 @@ function resolveSetupReference(name: string, context: TransformContext) {
|
|||
function transformNativeElement(
|
||||
tag: string,
|
||||
propsResult: ReturnType<typeof buildProps>,
|
||||
context: TransformContext,
|
||||
context: TransformContext<ElementNode>,
|
||||
) {
|
||||
const { scopeId } = context.options
|
||||
|
||||
context.template += `<${tag}`
|
||||
if (scopeId) context.template += ` ${scopeId}`
|
||||
let template = ''
|
||||
|
||||
template += `<${tag}`
|
||||
if (scopeId) template += ` ${scopeId}`
|
||||
|
||||
if (propsResult[0] /* dynamic props */) {
|
||||
const [, dynamicArgs, expressions] = propsResult
|
||||
|
@ -141,8 +143,8 @@ function transformNativeElement(
|
|||
for (const prop of propsResult[1]) {
|
||||
const { key, values } = prop
|
||||
if (key.isStatic && values.length === 1 && values[0].isStatic) {
|
||||
context.template += ` ${key.content}`
|
||||
if (values[0].content) context.template += `="${values[0].content}"`
|
||||
template += ` ${key.content}`
|
||||
if (values[0].content) template += `="${values[0].content}"`
|
||||
} else {
|
||||
context.registerEffect(values, [
|
||||
{
|
||||
|
@ -155,10 +157,22 @@ function transformNativeElement(
|
|||
}
|
||||
}
|
||||
|
||||
context.template += `>` + context.childrenTemplate.join('')
|
||||
template += `>` + context.childrenTemplate.join('')
|
||||
// TODO remove unnecessary close tag, e.g. if it's the last element of the template
|
||||
if (!isVoidTag(tag)) {
|
||||
context.template += `</${tag}>`
|
||||
template += `</${tag}>`
|
||||
}
|
||||
|
||||
if (
|
||||
context.parent &&
|
||||
context.parent.node.type === NodeTypes.ELEMENT &&
|
||||
!isValidHTMLNesting(context.parent.node.tag, tag)
|
||||
) {
|
||||
context.reference()
|
||||
context.dynamic.template = context.pushTemplate(template)
|
||||
context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE
|
||||
} else {
|
||||
context.template += template
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,6 @@ export function processFor(
|
|||
context.reference()
|
||||
|
||||
return () => {
|
||||
context.registerTemplate()
|
||||
exitBlock()
|
||||
context.registerOperation({
|
||||
type: IRNodeTypes.FOR,
|
||||
|
|
|
@ -128,9 +128,5 @@ export function createIfBranch(
|
|||
|
||||
const exitBlock = context.enterBlock(branch)
|
||||
context.reference()
|
||||
const onExit = () => {
|
||||
context.registerTemplate()
|
||||
exitBlock()
|
||||
}
|
||||
return [branch, onExit]
|
||||
return [branch, exitBlock]
|
||||
}
|
||||
|
|
|
@ -292,6 +292,9 @@ importers:
|
|||
source-map-js:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
validate-html-nesting:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2
|
||||
|
||||
packages/dts-built-test:
|
||||
dependencies:
|
||||
|
@ -5968,6 +5971,10 @@ packages:
|
|||
/util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
/validate-html-nesting@1.2.2:
|
||||
resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==}
|
||||
dev: false
|
||||
|
||||
/validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in New Issue