perf: also hoist all-static children array

This commit is contained in:
Evan You 2021-07-08 16:12:04 -04:00
parent 8bc50cb995
commit b7ea7c1485
7 changed files with 275 additions and 230 deletions
packages
compiler-core
compiler-dom/__tests__/transforms

@ -5,14 +5,15 @@ exports[`compiler: hoistStatic transform hoist element with static key 1`] = `
const { createElementVNode: _createElementVNode } = _Vue const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"div\\", { key: \\"foo\\" }, null, -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"div\\", { key: \\"foo\\" }, null, -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
_hoisted_1
]))
} }
}" }"
`; `;
@ -25,14 +26,15 @@ const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"p\\", null, [
/*#__PURE__*/_createElementVNode(\\"span\\"), /*#__PURE__*/_createElementVNode(\\"span\\"),
/*#__PURE__*/_createElementVNode(\\"span\\") /*#__PURE__*/_createElementVNode(\\"span\\")
], -1 /* HOISTED */) ], -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
_hoisted_1
]))
} }
}" }"
`; `;
@ -44,14 +46,15 @@ const { createElementVNode: _createElementVNode, createCommentVNode: _createComm
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"div\\", null, [ const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"div\\", null, [
/*#__PURE__*/_createCommentVNode(\\"comment\\") /*#__PURE__*/_createCommentVNode(\\"comment\\")
], -1 /* HOISTED */) ], -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
_hoisted_1
]))
} }
}" }"
`; `;
@ -62,15 +65,16 @@ const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"div\\", null, null, -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"div\\", null, null, -1 /* HOISTED */)
const _hoisted_3 = [
_hoisted_1,
_hoisted_2
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_3))
_hoisted_1,
_hoisted_2
]))
} }
}" }"
`; `;
@ -80,14 +84,15 @@ exports[`compiler: hoistStatic transform hoist simple element 1`] = `
const { createElementVNode: _createElementVNode } = _Vue const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
_hoisted_1
]))
} }
}" }"
`; `;
@ -175,14 +180,15 @@ exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static t
const { createElementVNode: _createElementVNode } = _Vue const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", null, \\"foo \\" + /*#__PURE__*/_toDisplayString(1) + \\" \\" + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", null, \\"foo \\" + /*#__PURE__*/_toDisplayString(1) + \\" \\" + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
_hoisted_1
]))
} }
}" }"
`; `;
@ -192,14 +198,15 @@ exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static t
const { createElementVNode: _createElementVNode } = _Vue const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
_hoisted_1
]))
} }
}" }"
`; `;
@ -368,6 +375,9 @@ const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" } const _hoisted_1 = { id: \\"foo\\" }
const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */)
const _hoisted_3 = [
_hoisted_2
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
@ -375,9 +385,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => { (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createElementBlock(\\"div\\", _hoisted_1, [ return (_openBlock(), _createElementBlock(\\"div\\", _hoisted_1, _hoisted_3))
_hoisted_2
]))
}), 256 /* UNKEYED_FRAGMENT */)) }), 256 /* UNKEYED_FRAGMENT */))
])) ]))
} }
@ -393,6 +401,9 @@ const _hoisted_1 = {
id: \\"foo\\" id: \\"foo\\"
} }
const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */)
const _hoisted_3 = [
_hoisted_2
]
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
@ -400,9 +411,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(\\"div\\", null, [ return (_openBlock(), _createElementBlock(\\"div\\", null, [
ok ok
? (_openBlock(), _createElementBlock(\\"div\\", _hoisted_1, [ ? (_openBlock(), _createElementBlock(\\"div\\", _hoisted_1, _hoisted_3))
_hoisted_2
]))
: _createCommentVNode(\\"v-if\\", true) : _createCommentVNode(\\"v-if\\", true)
])) ]))
} }

@ -26,6 +26,17 @@ import { createObjectMatcher, genFlagText } from '../testUtils'
import { transformText } from '../../src/transforms/transformText' import { transformText } from '../../src/transforms/transformText'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
const hoistedChildrenArrayMatcher = (startIndex = 1, length = 1) => ({
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: new Array(length).fill(0).map((_, i) => ({
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_${startIndex + i}`
}
}))
})
function transformWithHoist(template: string, options: CompilerOptions = {}) { function transformWithHoist(template: string, options: CompilerOptions = {}) {
const ast = parse(template) const ast = parse(template)
transform(ast, { transform(ast, {
@ -75,20 +86,13 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `hello` content: `hello`
} }
} },
hoistedChildrenArrayMatcher()
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: [ children: { content: `_hoisted_2` }
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
}
}
]
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -104,17 +108,12 @@ describe('compiler: hoistStatic transform', () => {
{ type: NodeTypes.ELEMENT, tag: `span` }, { type: NodeTypes.ELEMENT, tag: `span` },
{ type: NodeTypes.ELEMENT, tag: `span` } { type: NodeTypes.ELEMENT, tag: `span` }
] ]
} },
]) hoistedChildrenArrayMatcher()
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
}
}
]) ])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: '_hoisted_2'
})
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -126,17 +125,12 @@ describe('compiler: hoistStatic transform', () => {
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: [{ type: NodeTypes.COMMENT, content: `comment` }] children: [{ type: NodeTypes.COMMENT, content: `comment` }]
} },
]) hoistedChildrenArrayMatcher()
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
}
}
]) ])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: `_hoisted_2`
})
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -150,24 +144,12 @@ describe('compiler: hoistStatic transform', () => {
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"` tag: `"div"`
}
])
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
}
}, },
{ hoistedChildrenArrayMatcher(1, 2)
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_2`
}
}
]) ])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: '_hoisted_3'
})
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -213,26 +195,19 @@ describe('compiler: hoistStatic transform', () => {
test('hoist element with static key', () => { test('hoist element with static key', () => {
const root = transformWithHoist(`<div><div key="foo"/></div>`) const root = transformWithHoist(`<div><div key="foo"/></div>`)
expect(root.hoists.length).toBe(1) expect(root.hoists.length).toBe(2)
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: 'foo' }) props: createObjectMatcher({ key: 'foo' })
} },
hoistedChildrenArrayMatcher()
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: [ children: { content: `_hoisted_2` }
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
}
}
]
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -348,7 +323,8 @@ describe('compiler: hoistStatic transform', () => {
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"span"` tag: `"span"`
} },
hoistedChildrenArrayMatcher(2)
]) ])
expect( expect(
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode ((root.children[0] as ElementNode).children[0] as IfNode).codegenNode
@ -359,11 +335,7 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: { content: `_hoisted_1` }, props: { content: `_hoisted_1` },
children: [ children: { content: `_hoisted_3` }
{
codegenNode: { content: `_hoisted_2` }
}
]
} }
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -380,7 +352,8 @@ describe('compiler: hoistStatic transform', () => {
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"span"` tag: `"span"`
} },
hoistedChildrenArrayMatcher(2)
]) ])
const forBlockCodegen = ((root.children[0] as ElementNode) const forBlockCodegen = ((root.children[0] as ElementNode)
.children[0] as ForNode).codegenNode .children[0] as ForNode).codegenNode
@ -399,11 +372,7 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: { content: `_hoisted_1` }, props: { content: `_hoisted_1` },
children: [ children: { content: `_hoisted_3` }
{
codegenNode: { content: `_hoisted_2` }
}
]
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -423,6 +392,17 @@ describe('compiler: hoistStatic transform', () => {
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"` tag: `"div"`
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{
type: NodeTypes.TEXT_CALL
},
{
type: NodeTypes.ELEMENT
}
]
} }
]) ])
}) })
@ -443,20 +423,16 @@ describe('compiler: hoistStatic transform', () => {
children: { children: {
type: NodeTypes.COMPOUND_EXPRESSION type: NodeTypes.COMPOUND_EXPRESSION
} }
} },
hoistedChildrenArrayMatcher()
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: [ children: {
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1` content: `_hoisted_2`
} }
}
]
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -482,20 +458,16 @@ describe('compiler: hoistStatic transform', () => {
constType: ConstantTypes.CAN_STRINGIFY constType: ConstantTypes.CAN_STRINGIFY
} }
} }
} },
hoistedChildrenArrayMatcher()
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: [ children: {
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1` content: `_hoisted_2`
} }
}
]
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })

@ -286,6 +286,7 @@ export interface VNodeCall extends Node {
| TemplateTextChildNode // single text child | TemplateTextChildNode // single text child
| SlotsExpression // component slots | SlotsExpression // component slots
| ForRenderListExpression // v-for fragment call | ForRenderListExpression // v-for fragment call
| SimpleExpressionNode // hoisted
| undefined | undefined
patchFlag: string | undefined patchFlag: string | undefined
dynamicProps: string | SimpleExpressionNode | undefined dynamicProps: string | SimpleExpressionNode | undefined
@ -338,7 +339,7 @@ export interface Property extends Node {
export interface ArrayExpression extends Node { export interface ArrayExpression extends Node {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION
elements: Array<string | JSChildNode> elements: Array<string | Node>
} }
export interface FunctionExpression extends Node { export interface FunctionExpression extends Node {

@ -840,7 +840,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
} }
function genArrayExpression(node: ArrayExpression, context: CodegenContext) { function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
genNodeListAsArray(node.elements, context) genNodeListAsArray(node.elements as CodegenNode[], context)
} }
function genFunctionExpression( function genFunctionExpression(

@ -16,7 +16,8 @@ import {
createCacheExpression, createCacheExpression,
TemplateLiteral, TemplateLiteral,
createVNodeCall, createVNodeCall,
ConstantTypes ConstantTypes,
ArrayExpression
} from './ast' } from './ast'
import { import {
isString, isString,
@ -113,7 +114,7 @@ export interface TransformContext
onNodeRemoved(): void onNodeRemoved(): void
addIdentifiers(exp: ExpressionNode | string): void addIdentifiers(exp: ExpressionNode | string): void
removeIdentifiers(exp: ExpressionNode | string): void removeIdentifiers(exp: ExpressionNode | string): void
hoist(exp: string | JSChildNode): SimpleExpressionNode hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
constantCache: Map<TemplateChildNode, ConstantTypes> constantCache: Map<TemplateChildNode, ConstantTypes>

@ -11,10 +11,11 @@ import {
VNodeCall, VNodeCall,
ParentNode, ParentNode,
JSChildNode, JSChildNode,
CallExpression CallExpression,
createArrayExpression
} from '../ast' } from '../ast'
import { TransformContext } from '../transform' import { TransformContext } from '../transform'
import { PatchFlags, isString, isSymbol } from '@vue/shared' import { PatchFlags, isString, isSymbol, isArray } from '@vue/shared'
import { getVNodeBlockHelper, getVNodeHelper, isSlotOutlet } from '../utils' import { getVNodeBlockHelper, getVNodeHelper, isSlotOutlet } from '../utils'
import { import {
OPEN_BLOCK, OPEN_BLOCK,
@ -51,7 +52,6 @@ function walk(
context: TransformContext, context: TransformContext,
doNotHoistNode: boolean = false doNotHoistNode: boolean = false
) { ) {
let hasHoistedNode = false
// Some transforms, e.g. transformAssetUrls from @vue/compiler-sfc, replaces // Some transforms, e.g. transformAssetUrls from @vue/compiler-sfc, replaces
// static bindings with expressions. These expressions are guaranteed to be // static bindings with expressions. These expressions are guaranteed to be
// constant so they are still eligible for hoisting, but they are only // constant so they are still eligible for hoisting, but they are only
@ -63,6 +63,9 @@ function walk(
let canStringify = true let canStringify = true
const { children } = node const { children } = node
const originalCount = children.length
let hoistedCount = 0
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
const child = children[i] const child = children[i]
// only plain elements & text calls are eligible for hoisting. // only plain elements & text calls are eligible for hoisting.
@ -81,7 +84,7 @@ function walk(
;(child.codegenNode as VNodeCall).patchFlag = ;(child.codegenNode as VNodeCall).patchFlag =
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``) PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
child.codegenNode = context.hoist(child.codegenNode!) child.codegenNode = context.hoist(child.codegenNode!)
hasHoistedNode = true hoistedCount++
continue continue
} }
} else { } else {
@ -115,7 +118,7 @@ function walk(
} }
if (contentType >= ConstantTypes.CAN_HOIST) { if (contentType >= ConstantTypes.CAN_HOIST) {
child.codegenNode = context.hoist(child.codegenNode) child.codegenNode = context.hoist(child.codegenNode)
hasHoistedNode = true hoistedCount++
} }
} }
} }
@ -145,9 +148,24 @@ function walk(
} }
} }
if (canStringify && hasHoistedNode && context.transformHoist) { if (canStringify && hoistedCount && context.transformHoist) {
context.transformHoist(children, context, node) context.transformHoist(children, context, node)
} }
// all children were hoisted - the entire children array is hoistable.
if (
hoistedCount &&
hoistedCount === originalCount &&
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT &&
node.codegenNode &&
node.codegenNode.type === NodeTypes.VNODE_CALL &&
isArray(node.codegenNode.children)
) {
node.codegenNode.children = context.hoist(
createArrayExpression(node.codegenNode.children)
)
}
} }
export function getConstantType( export function getConstantType(

@ -30,7 +30,6 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div><div><div>hello</div><div>hello</div></div></div>` `<div><div><div>hello</div><div>hello</div></div></div>`
) )
expect(ast.hoists.length).toBe(1)
// should be a normal vnode call // should be a normal vnode call
expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL) expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
}) })
@ -42,9 +41,9 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div></div>` )}</div></div>`
) )
expect(ast.hoists.length).toBe(1)
// should be optimized now // should be optimized now
expect(ast.hoists[0]).toMatchObject({ expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC, callee: CREATE_STATIC,
arguments: [ arguments: [
@ -56,7 +55,11 @@ describe('stringify static html', () => {
), ),
'1' '1'
] ]
}) }, // the children array is hoisted as well
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
test('should work on eligible content (elements > 20)', () => { test('should work on eligible content (elements > 20)', () => {
@ -66,9 +69,9 @@ describe('stringify static html', () => {
StringifyThresholds.NODE_COUNT StringifyThresholds.NODE_COUNT
)}</div></div>` )}</div></div>`
) )
expect(ast.hoists.length).toBe(1)
// should be optimized now // should be optimized now
expect(ast.hoists[0]).toMatchObject({ expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC, callee: CREATE_STATIC,
arguments: [ arguments: [
@ -80,7 +83,12 @@ describe('stringify static html', () => {
), ),
'1' '1'
] ]
}) },
// the children array is hoisted as well
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
test('should work for multiple adjacent nodes', () => { test('should work for multiple adjacent nodes', () => {
@ -90,13 +98,10 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div>` )}</div>`
) )
// should have 5 hoisted nodes, but the other 4 should be null // should have 6 hoisted nodes (including the entire array),
expect(ast.hoists.length).toBe(5) // but 2~5 should be null because they are merged into 1
for (let i = 1; i < 5; i++) { expect(ast.hoists).toMatchObject([
expect(ast.hoists[i]).toBe(null) {
}
// should be optimized now
expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC, callee: CREATE_STATIC,
arguments: [ arguments: [
@ -108,7 +113,15 @@ describe('stringify static html', () => {
), ),
'5' '5'
] ]
}) },
null,
null,
null,
null,
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
test('serializing constant bindings', () => { test('serializing constant bindings', () => {
@ -118,9 +131,9 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div></div>` )}</div></div>`
) )
expect(ast.hoists.length).toBe(1)
// should be optimized now // should be optimized now
expect(ast.hoists[0]).toMatchObject({ expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC, callee: CREATE_STATIC,
arguments: [ arguments: [
@ -132,7 +145,11 @@ describe('stringify static html', () => {
), ),
'1' '1'
] ]
}) },
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
test('escape', () => { test('escape', () => {
@ -143,9 +160,9 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div></div>` )}</div></div>`
) )
expect(ast.hoists.length).toBe(1)
// should be optimized now // should be optimized now
expect(ast.hoists[0]).toMatchObject({ expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC, callee: CREATE_STATIC,
arguments: [ arguments: [
@ -157,7 +174,11 @@ describe('stringify static html', () => {
), ),
'1' '1'
] ]
}) },
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
test('should bail on runtime constant v-bind bindings', () => { test('should bail on runtime constant v-bind bindings', () => {
@ -192,13 +213,16 @@ describe('stringify static html', () => {
] ]
} }
) )
expect(ast.hoists).toMatchObject([
{
// the expression and the tree are still hoistable // the expression and the tree are still hoistable
expect(ast.hoists.length).toBe(1) // but if it's stringified it will be NodeTypes.CALL_EXPRESSION
// ...but the hoisted tree should not be stringified
expect(ast.hoists[0]).toMatchObject({
// if it's stringified it will be NodeTypes.CALL_EXPRESSION
type: NodeTypes.VNODE_CALL type: NodeTypes.VNODE_CALL
}) },
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
// #1128 // #1128
@ -209,10 +233,14 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div></div>` )}</div></div>`
) )
expect(ast.hoists.length).toBe(1) expect(ast.hoists).toMatchObject([
expect(ast.hoists[0]).toMatchObject({ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
}) },
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
const { ast: ast2 } = compileWithStringify( const { ast: ast2 } = compileWithStringify(
`<div><div><input :indeterminate="true">${repeat( `<div><div><input :indeterminate="true">${repeat(
@ -220,10 +248,14 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div></div>` )}</div></div>`
) )
expect(ast2.hoists.length).toBe(1) expect(ast2.hoists).toMatchObject([
expect(ast2.hoists[0]).toMatchObject({ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
}) },
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
test('should bail on non attribute bindings', () => { test('should bail on non attribute bindings', () => {
@ -233,10 +265,14 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}<input indeterminate></div></div>` )}<input indeterminate></div></div>`
) )
expect(ast.hoists.length).toBe(1) expect(ast.hoists).toMatchObject([
expect(ast.hoists[0]).toMatchObject({ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
}) },
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
const { ast: ast2 } = compileWithStringify( const { ast: ast2 } = compileWithStringify(
`<div><div>${repeat( `<div><div>${repeat(
@ -244,10 +280,14 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}<input :indeterminate="true"></div></div>` )}<input :indeterminate="true"></div></div>`
) )
expect(ast2.hoists.length).toBe(1) expect(ast2.hoists).toMatchObject([
expect(ast2.hoists[0]).toMatchObject({ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
}) },
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
test('should bail on tags that has placement constraints (eg.tables related tags)', () => { test('should bail on tags that has placement constraints (eg.tables related tags)', () => {
@ -257,10 +297,14 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</tbody></table>` )}</tbody></table>`
) )
expect(ast.hoists.length).toBe(1) expect(ast.hoists).toMatchObject([
expect(ast.hoists[0]).toMatchObject({ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
}) },
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
}) })
test('should bail inside slots', () => { test('should bail inside slots', () => {