From 6a75c3463b7f7ef669b070051ab231f6abb5bd6f Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 16 Oct 2019 12:00:55 -0400 Subject: [PATCH] feat(compiler-core): do not generate TEXT flag if child is constant --- .../__snapshots__/hoistStatic.spec.ts.snap | 16 +++++++++++- .../__tests__/transforms/hoistStatic.spec.ts | 25 +++++++++++++++++-- .../src/transforms/hoistStatic.ts | 15 ++++++----- .../src/transforms/transformElement.ts | 4 ++- packages/compiler-core/src/utils.ts | 5 +++- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap index 9ad4a155..b739baf4 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap @@ -190,7 +190,7 @@ exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static t "const _Vue = Vue 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() { 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`] = ` "const _Vue = Vue diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts index bf4e2370..f5567b98 100644 --- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts @@ -273,6 +273,28 @@ describe('compiler: hoistStatic transform', () => { expect(generate(root).code).toMatchSnapshot() }) + test('should NOT hoist element with dynamic ref', () => { + const { root, args } = transformWithHoist(`
`) + 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', () => { const { root, args } = transformWithHoist( `
` @@ -521,8 +543,7 @@ describe('compiler: hoistStatic transform', () => { isStatic: false, isConstant: true } - }, - '1 /* TEXT */' + } ] } ]) diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts index ecadeddf..e528536d 100644 --- a/packages/compiler-core/src/transforms/hoistStatic.ts +++ b/packages/compiler-core/src/transforms/hoistStatic.ts @@ -15,9 +15,8 @@ import { APPLY_DIRECTIVES } from '../runtimeHelpers' import { PatchFlags, isString, isSymbol } from '@vue/shared' import { isSlotOutlet, findProp } from '../utils' -function hasDynamicKey(node: ElementNode) { - const keyProp = findProp(node, 'key') - return keyProp && keyProp.type === NodeTypes.DIRECTIVE +function hasDynamicKeyOrRef(node: ElementNode) { + return findProp(node, 'key', true) || findProp(node, 'ref', true) } export function hoistStatic(root: RootNode, context: TransformContext) { @@ -57,7 +56,7 @@ function walk( if ( !doNotHoistNode && isStaticNode(child, resultCache) && - !hasDynamicKey(child) + !hasDynamicKeyOrRef(child) ) { // whole tree is static child.codegenNode = context.hoist(child.codegenNode!) @@ -70,7 +69,7 @@ function walk( (!flag || flag === PatchFlags.NEED_PATCH || flag === PatchFlags.TEXT) && - !hasDynamicKey(child) + !hasDynamicKeyOrRef(child) ) { let codegenNode = child.codegenNode as ElementCodegenNode if (codegenNode.callee === APPLY_DIRECTIVES) { @@ -107,9 +106,9 @@ function getPatchFlag(node: PlainElementNode): number | undefined { return flag ? parseInt(flag, 10) : undefined } -function isStaticNode( +export function isStaticNode( node: TemplateChildNode | SimpleExpressionNode, - resultCache: Map + resultCache: Map = new Map() ): boolean { switch (node.type) { case NodeTypes.ELEMENT: @@ -121,7 +120,7 @@ function isStaticNode( return cached } const flag = getPatchFlag(node) - if (!flag || flag === PatchFlags.TEXT) { + if (!flag) { // element self is static. check its children. for (let i = 0; i < node.children.length; i++) { if (!isStaticNode(node.children[i], resultCache)) { diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index d54f2be3..571eb89d 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -28,6 +28,7 @@ import { } from '../runtimeHelpers' import { getInnerRange, isVSlot, toValidAssetId } from '../utils' import { buildSlots } from './vSlot' +import { isStaticNode } from './hoistStatic' // some directive transforms (e.g. v-model) may return a symbol for runtime // 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) { const child = node.children[0] const type = child.type + // check for dynamic text children const hasDynamicTextChild = type === NodeTypes.INTERPOLATION || type === NodeTypes.COMPOUND_EXPRESSION - if (hasDynamicTextChild) { + if (hasDynamicTextChild && !isStaticNode(child)) { patchFlag |= PatchFlags.TEXT } // pass directly if the only child is a text node diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index a297d54c..b2f552bc 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -156,15 +156,18 @@ export function findDir( export function findProp( node: ElementNode, - name: string + name: string, + dynamicOnly: boolean = false ): ElementNode['props'][0] | undefined { for (let i = 0; i < node.props.length; i++) { const p = node.props[i] if (p.type === NodeTypes.ATTRIBUTE) { + if (dynamicOnly) continue if (p.name === name && p.value && !p.value.isEmpty) { return p } } else if ( + p.name === 'bind' && p.arg && p.arg.type === NodeTypes.SIMPLE_EXPRESSION && p.arg.isStatic &&