feat(compiler-core): do not generate TEXT flag if child is constant

This commit is contained in:
Evan You 2019-10-16 12:00:55 -04:00
parent 6607edab2d
commit 6a75c3463b
5 changed files with 52 additions and 13 deletions

View File

@ -190,7 +190,7 @@ exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static t
"const _Vue = Vue "const _Vue = Vue
const _createVNode = Vue.createVNode const _createVNode = Vue.createVNode
const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1), 1 /* TEXT */) const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1))
return function render() { return function render() {
with (this) { with (this) {
@ -300,6 +300,20 @@ return function render() {
}" }"
`; `;
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic ref 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", { ref: foo }, null, 32 /* NEED_PATCH */)
]))
}
}"
`;
exports[`compiler: hoistStatic transform should NOT hoist root node 1`] = ` exports[`compiler: hoistStatic transform should NOT hoist root node 1`] = `
"const _Vue = Vue "const _Vue = Vue

View File

@ -273,6 +273,28 @@ describe('compiler: hoistStatic transform', () => {
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('should NOT hoist element with dynamic ref', () => {
const { root, args } = transformWithHoist(`<div><div :ref="foo"/></div>`)
expect(root.hoists.length).toBe(0)
expect(args[2]).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
createObjectMatcher({
ref: `[foo]`
}),
`null`,
genFlagText(PatchFlags.NEED_PATCH)
]
}
}
])
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with directives', () => { test('hoist static props for elements with directives', () => {
const { root, args } = transformWithHoist( const { root, args } = transformWithHoist(
`<div><div id="foo" v-foo/></div>` `<div><div id="foo" v-foo/></div>`
@ -521,8 +543,7 @@ describe('compiler: hoistStatic transform', () => {
isStatic: false, isStatic: false,
isConstant: true isConstant: true
} }
}, }
'1 /* TEXT */'
] ]
} }
]) ])

View File

@ -15,9 +15,8 @@ import { APPLY_DIRECTIVES } from '../runtimeHelpers'
import { PatchFlags, isString, isSymbol } from '@vue/shared' import { PatchFlags, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet, findProp } from '../utils' import { isSlotOutlet, findProp } from '../utils'
function hasDynamicKey(node: ElementNode) { function hasDynamicKeyOrRef(node: ElementNode) {
const keyProp = findProp(node, 'key') return findProp(node, 'key', true) || findProp(node, 'ref', true)
return keyProp && keyProp.type === NodeTypes.DIRECTIVE
} }
export function hoistStatic(root: RootNode, context: TransformContext) { export function hoistStatic(root: RootNode, context: TransformContext) {
@ -57,7 +56,7 @@ function walk(
if ( if (
!doNotHoistNode && !doNotHoistNode &&
isStaticNode(child, resultCache) && isStaticNode(child, resultCache) &&
!hasDynamicKey(child) !hasDynamicKeyOrRef(child)
) { ) {
// whole tree is static // whole tree is static
child.codegenNode = context.hoist(child.codegenNode!) child.codegenNode = context.hoist(child.codegenNode!)
@ -70,7 +69,7 @@ function walk(
(!flag || (!flag ||
flag === PatchFlags.NEED_PATCH || flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) && flag === PatchFlags.TEXT) &&
!hasDynamicKey(child) !hasDynamicKeyOrRef(child)
) { ) {
let codegenNode = child.codegenNode as ElementCodegenNode let codegenNode = child.codegenNode as ElementCodegenNode
if (codegenNode.callee === APPLY_DIRECTIVES) { if (codegenNode.callee === APPLY_DIRECTIVES) {
@ -107,9 +106,9 @@ function getPatchFlag(node: PlainElementNode): number | undefined {
return flag ? parseInt(flag, 10) : undefined return flag ? parseInt(flag, 10) : undefined
} }
function isStaticNode( export function isStaticNode(
node: TemplateChildNode | SimpleExpressionNode, node: TemplateChildNode | SimpleExpressionNode,
resultCache: Map<TemplateChildNode, boolean> resultCache: Map<TemplateChildNode, boolean> = new Map()
): boolean { ): boolean {
switch (node.type) { switch (node.type) {
case NodeTypes.ELEMENT: case NodeTypes.ELEMENT:
@ -121,7 +120,7 @@ function isStaticNode(
return cached return cached
} }
const flag = getPatchFlag(node) const flag = getPatchFlag(node)
if (!flag || flag === PatchFlags.TEXT) { if (!flag) {
// element self is static. check its children. // element self is static. check its children.
for (let i = 0; i < node.children.length; i++) { for (let i = 0; i < node.children.length; i++) {
if (!isStaticNode(node.children[i], resultCache)) { if (!isStaticNode(node.children[i], resultCache)) {

View File

@ -28,6 +28,7 @@ import {
} from '../runtimeHelpers' } from '../runtimeHelpers'
import { getInnerRange, isVSlot, toValidAssetId } from '../utils' import { getInnerRange, isVSlot, toValidAssetId } from '../utils'
import { buildSlots } from './vSlot' import { buildSlots } from './vSlot'
import { isStaticNode } from './hoistStatic'
// some directive transforms (e.g. v-model) may return a symbol for runtime // some directive transforms (e.g. v-model) may return a symbol for runtime
// import, which should be used instead of a resolveDirective call. // import, which should be used instead of a resolveDirective call.
@ -93,10 +94,11 @@ export const transformElement: NodeTransform = (node, context) => {
} else if (node.children.length === 1) { } else if (node.children.length === 1) {
const child = node.children[0] const child = node.children[0]
const type = child.type const type = child.type
// check for dynamic text children
const hasDynamicTextChild = const hasDynamicTextChild =
type === NodeTypes.INTERPOLATION || type === NodeTypes.INTERPOLATION ||
type === NodeTypes.COMPOUND_EXPRESSION type === NodeTypes.COMPOUND_EXPRESSION
if (hasDynamicTextChild) { if (hasDynamicTextChild && !isStaticNode(child)) {
patchFlag |= PatchFlags.TEXT patchFlag |= PatchFlags.TEXT
} }
// pass directly if the only child is a text node // pass directly if the only child is a text node

View File

@ -156,15 +156,18 @@ export function findDir(
export function findProp( export function findProp(
node: ElementNode, node: ElementNode,
name: string name: string,
dynamicOnly: boolean = false
): ElementNode['props'][0] | undefined { ): ElementNode['props'][0] | undefined {
for (let i = 0; i < node.props.length; i++) { for (let i = 0; i < node.props.length; i++) {
const p = node.props[i] const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) { if (p.type === NodeTypes.ATTRIBUTE) {
if (dynamicOnly) continue
if (p.name === name && p.value && !p.value.isEmpty) { if (p.name === name && p.value && !p.value.isEmpty) {
return p return p
} }
} else if ( } else if (
p.name === 'bind' &&
p.arg && p.arg &&
p.arg.type === NodeTypes.SIMPLE_EXPRESSION && p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
p.arg.isStatic && p.arg.isStatic &&