mirror of https://github.com/vuejs/core.git
fix(compiler-core): prevent comments from blocking static node hoisting (#13345)
close #13344
This commit is contained in:
parent
47ddf98602
commit
55dad625ac
|
@ -410,6 +410,32 @@ return function render(_ctx, _cache) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: cacheStatic transform > should hoist props for root with single element excluding comments 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
|
const _hoisted_1 = { id: "a" }
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
|
_createCommentVNode("comment"),
|
||||||
|
_createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||||
|
_createElementVNode("div", { id: "b" }, [
|
||||||
|
_createElementVNode("div", { id: "c" }, [
|
||||||
|
_createElementVNode("div", { id: "d" }, [
|
||||||
|
_createElementVNode("div", { id: "e" }, "hello")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
], -1 /* HOISTED */)
|
||||||
|
]))
|
||||||
|
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
|
exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createElementVNode: _createElementVNode } = _Vue
|
const { createElementVNode: _createElementVNode } = _Vue
|
||||||
|
|
|
@ -543,6 +543,32 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should hoist props for root with single element excluding comments', () => {
|
||||||
|
// deeply nested div to trigger stringification condition
|
||||||
|
const root = transformWithCache(
|
||||||
|
`<!--comment--><div id="a"><div id="b"><div id="c"><div id="d"><div id="e">hello</div></div></div></div></div>`,
|
||||||
|
)
|
||||||
|
expect(root.cached.length).toBe(1)
|
||||||
|
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'a' })])
|
||||||
|
|
||||||
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.COMMENT,
|
||||||
|
content: 'comment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
codegenNode: {
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
tag: `"div"`,
|
||||||
|
props: { content: `_hoisted_1` },
|
||||||
|
children: { type: NodeTypes.JS_CACHE_EXPRESSION },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
describe('prefixIdentifiers', () => {
|
describe('prefixIdentifiers', () => {
|
||||||
test('cache nested static tree with static interpolation', () => {
|
test('cache nested static tree with static interpolation', () => {
|
||||||
const root = transformWithCache(
|
const root = transformWithCache(
|
||||||
|
|
|
@ -37,7 +37,7 @@ import {
|
||||||
helperNameMap,
|
helperNameMap,
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { isVSlot } from './utils'
|
import { isVSlot } from './utils'
|
||||||
import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic'
|
import { cacheStatic, getSingleElementRoot } from './transforms/cacheStatic'
|
||||||
import type { CompilerCompatOptions } from './compat/compatConfig'
|
import type { CompilerCompatOptions } from './compat/compatConfig'
|
||||||
|
|
||||||
// There are two types of transforms:
|
// There are two types of transforms:
|
||||||
|
@ -356,12 +356,12 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
||||||
const { helper } = context
|
const { helper } = context
|
||||||
const { children } = root
|
const { children } = root
|
||||||
if (children.length === 1) {
|
if (children.length === 1) {
|
||||||
const child = children[0]
|
const singleElementRootChild = getSingleElementRoot(root)
|
||||||
// if the single child is an element, turn it into a block.
|
// if the single child is an element, turn it into a block.
|
||||||
if (isSingleElementRoot(root, child) && child.codegenNode) {
|
if (singleElementRootChild && singleElementRootChild.codegenNode) {
|
||||||
// single element root is never hoisted so codegenNode will never be
|
// single element root is never hoisted so codegenNode will never be
|
||||||
// SimpleExpressionNode
|
// SimpleExpressionNode
|
||||||
const codegenNode = child.codegenNode
|
const codegenNode = singleElementRootChild.codegenNode
|
||||||
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||||
convertToBlock(codegenNode, context)
|
convertToBlock(codegenNode, context)
|
||||||
}
|
}
|
||||||
|
@ -370,7 +370,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
||||||
// - single <slot/>, IfNode, ForNode: already blocks.
|
// - single <slot/>, IfNode, ForNode: already blocks.
|
||||||
// - single text node: always patched.
|
// - single text node: always patched.
|
||||||
// root codegen falls through via genNode()
|
// root codegen falls through via genNode()
|
||||||
root.codegenNode = child
|
root.codegenNode = children[0]
|
||||||
}
|
}
|
||||||
} else if (children.length > 1) {
|
} else if (children.length > 1) {
|
||||||
// root has multiple nodes - return a fragment block.
|
// root has multiple nodes - return a fragment block.
|
||||||
|
|
|
@ -41,20 +41,19 @@ export function cacheStatic(root: RootNode, context: TransformContext): void {
|
||||||
context,
|
context,
|
||||||
// Root node is unfortunately non-hoistable due to potential parent
|
// Root node is unfortunately non-hoistable due to potential parent
|
||||||
// fallthrough attributes.
|
// fallthrough attributes.
|
||||||
isSingleElementRoot(root, root.children[0]),
|
!!getSingleElementRoot(root),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSingleElementRoot(
|
export function getSingleElementRoot(
|
||||||
root: RootNode,
|
root: RootNode,
|
||||||
child: TemplateChildNode,
|
): PlainElementNode | ComponentNode | TemplateNode | null {
|
||||||
): child is PlainElementNode | ComponentNode | TemplateNode {
|
const children = root.children.filter(x => x.type !== NodeTypes.COMMENT)
|
||||||
const { children } = root
|
return children.length === 1 &&
|
||||||
return (
|
children[0].type === NodeTypes.ELEMENT &&
|
||||||
children.length === 1 &&
|
!isSlotOutlet(children[0])
|
||||||
child.type === NodeTypes.ELEMENT &&
|
? children[0]
|
||||||
!isSlotOutlet(child)
|
: null
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function walk(
|
function walk(
|
||||||
|
|
|
@ -75,6 +75,22 @@ return function render(_ctx, _cache) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`stringify static html > should bail for comments 1`] = `
|
||||||
|
"const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
const _hoisted_1 = { class: "a" }
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
|
_createCommentVNode(" Comment 1 "),
|
||||||
|
_createElementVNode("div", _hoisted_1, [
|
||||||
|
_createCommentVNode(" Comment 2 "),
|
||||||
|
_cache[0] || (_cache[0] = _createStaticVNode("<span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span>", 5))
|
||||||
|
])
|
||||||
|
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = `
|
exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
|
|
@ -491,6 +491,16 @@ describe('stringify static html', () => {
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should bail for comments', () => {
|
||||||
|
const { code } = compileWithStringify(
|
||||||
|
`<!-- Comment 1 --><div class="a"><!-- Comment 2 -->${repeat(
|
||||||
|
`<span class="b"/>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</div>`,
|
||||||
|
)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('should bail for <option> elements with null values', () => {
|
test('should bail for <option> elements with null values', () => {
|
||||||
const { ast, code } = compileWithStringify(
|
const { ast, code } = compileWithStringify(
|
||||||
`<div><select><option :value="null" />${repeat(
|
`<div><select><option :value="null" />${repeat(
|
||||||
|
|
Loading…
Reference in New Issue