From: Evan You Date: Sat, 21 Nov 2020 00:26:07 +0000 (-0500) Subject: refactor(compiler): better constant hoist/stringify checks X-Git-Tag: v3.0.3~13 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=90bdf59f4c84ec0af9bab402c37090d82806cfc1;p=thirdparty%2Fvuejs%2Fcore.git refactor(compiler): better constant hoist/stringify checks --- diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index a645e631d6..d14631367c 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -3322,8 +3322,8 @@ Object { "children": Array [ Object { "content": Object { + "constType": 0, "content": "a < b", - "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -6084,8 +6084,8 @@ Object { "children": Array [ Object { "content": Object { + "constType": 0, "content": "''", - "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -6259,8 +6259,8 @@ Object { "props": Array [ Object { "arg": Object { + "constType": 0, "content": "se", - "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -6593,8 +6593,8 @@ Object { "children": Array [ Object { "content": Object { + "constType": 0, "content": "", - "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -6758,8 +6758,8 @@ Object { "props": Array [ Object { "arg": Object { + "constType": 3, "content": "class", - "isConstant": true, "isStatic": true, "loc": Object { "end": Object { @@ -6777,8 +6777,8 @@ Object { "type": 4, }, "exp": Object { + "constType": 0, "content": "{ some: condition }", - "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -6838,8 +6838,8 @@ Object { "props": Array [ Object { "arg": Object { + "constType": 3, "content": "style", - "isConstant": true, "isStatic": true, "loc": Object { "end": Object { @@ -6857,8 +6857,8 @@ Object { "type": 4, }, "exp": Object { + "constType": 0, "content": "{ color: 'red' }", - "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -6950,8 +6950,8 @@ Object { "props": Array [ Object { "arg": Object { + "constType": 3, "content": "style", - "isConstant": true, "isStatic": true, "loc": Object { "end": Object { @@ -6969,8 +6969,8 @@ Object { "type": 4, }, "exp": Object { + "constType": 0, "content": "{ color: 'red' }", - "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -7049,8 +7049,8 @@ Object { "props": Array [ Object { "arg": Object { + "constType": 3, "content": "class", - "isConstant": true, "isStatic": true, "loc": Object { "end": Object { @@ -7068,8 +7068,8 @@ Object { "type": 4, }, "exp": Object { + "constType": 0, "content": "{ some: condition }", - "isConstant": false, "isStatic": false, "loc": Object { "end": Object { diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index f4a11561cc..809c209f80 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -20,7 +20,8 @@ import { IfConditionalExpression, createVNodeCall, VNodeCall, - DirectiveArguments + DirectiveArguments, + ConstantTypes } from '../src' import { CREATE_VNODE, @@ -304,7 +305,12 @@ describe('compiler: codegen', () => { codegenNode: { type: NodeTypes.FOR, loc: locStub, - source: createSimpleExpression('1 + 2', false, locStub, true), + source: createSimpleExpression( + '1 + 2', + false, + locStub, + ConstantTypes.CAN_STRINGIFY + ), valueAlias: undefined, keyAlias: undefined, objectIndexAlias: undefined, diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 3fdb3d950a..a93b5e0404 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -9,7 +9,8 @@ import { NodeTypes, Position, TextNode, - InterpolationNode + InterpolationNode, + ConstantTypes } from '../src/ast' describe('compiler: parse', () => { @@ -177,7 +178,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `message`, isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { start: { offset: 2, line: 1, column: 3 }, end: { offset: 9, line: 1, column: 10 }, @@ -202,7 +203,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `a { type: NodeTypes.SIMPLE_EXPRESSION, content: `a { content: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, content: 'c>d', loc: { start: { offset: 12, line: 1, column: 13 }, @@ -273,8 +274,8 @@ describe('compiler: parse', () => { content: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, - // The `isConstant` is the default value and will be determined in `transformExpression`. - isConstant: false, + // The `constType` is the default value and will be determined in `transformExpression`. + constType: ConstantTypes.NOT_CONSTANT, content: '""', loc: { start: { offset: 8, line: 1, column: 9 }, @@ -303,7 +304,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `msg`, isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { start: { offset: 4, line: 1, column: 5 }, end: { offset: 7, line: 1, column: 8 }, @@ -1028,7 +1029,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { start: { offset: 11, line: 1, column: 12 }, end: { offset: 12, line: 1, column: 13 }, @@ -1054,7 +1055,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, - isConstant: true, + constType: ConstantTypes.CAN_STRINGIFY, loc: { source: 'click', @@ -1091,7 +1092,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'event', isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { source: '[event]', @@ -1164,7 +1165,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, - isConstant: true, + constType: ConstantTypes.CAN_STRINGIFY, loc: { source: 'click', @@ -1201,7 +1202,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a.b', isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { source: '[a.b]', @@ -1238,7 +1239,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isConstant: true, + constType: ConstantTypes.CAN_STRINGIFY, loc: { source: 'a', @@ -1259,7 +1260,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { start: { offset: 8, line: 1, column: 9 }, @@ -1286,7 +1287,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isConstant: true, + constType: ConstantTypes.CAN_STRINGIFY, loc: { source: 'a', @@ -1307,7 +1308,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { start: { offset: 13, line: 1, column: 14 }, @@ -1334,7 +1335,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isConstant: true, + constType: ConstantTypes.CAN_STRINGIFY, loc: { source: 'a', @@ -1355,7 +1356,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { start: { offset: 8, line: 1, column: 9 }, @@ -1382,7 +1383,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isConstant: true, + constType: ConstantTypes.CAN_STRINGIFY, loc: { source: 'a', @@ -1403,7 +1404,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, loc: { start: { offset: 14, line: 1, column: 15 }, @@ -1430,7 +1431,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isConstant: true, + constType: ConstantTypes.CAN_STRINGIFY, loc: { source: 'a', start: { @@ -1450,8 +1451,8 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: '{ b }', isStatic: false, - // The `isConstant` is the default value and will be determined in transformExpression - isConstant: false, + // The `constType` is the default value and will be determined in transformExpression + constType: ConstantTypes.NOT_CONSTANT, loc: { start: { offset: 10, line: 1, column: 11 }, end: { offset: 15, line: 1, column: 16 }, @@ -1478,7 +1479,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'foo.bar', isStatic: true, - isConstant: true, + constType: ConstantTypes.CAN_STRINGIFY, loc: { source: 'foo.bar', start: { diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts index c13f1c48f0..37e9162847 100644 --- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts @@ -7,7 +7,8 @@ import { VNodeCall, IfNode, ElementNode, - ForNode + ForNode, + ConstantTypes } from '../../src' import { FRAGMENT, RENDER_LIST, CREATE_TEXT } from '../../src/runtimeHelpers' import { transformElement } from '../../src/transforms/transformElement' @@ -469,7 +470,7 @@ describe('compiler: hoistStatic transform', () => { content: { content: `1`, isStatic: false, - isConstant: true + constType: ConstantTypes.CAN_STRINGIFY } } } @@ -505,13 +506,13 @@ describe('compiler: hoistStatic transform', () => { { key: { content: `class`, - isConstant: true, - isStatic: true + isStatic: true, + constType: ConstantTypes.CAN_STRINGIFY }, value: { content: `{ foo: true }`, - isConstant: true, - isStatic: false + isStatic: false, + constType: ConstantTypes.CAN_STRINGIFY } } ] @@ -534,8 +535,8 @@ describe('compiler: hoistStatic transform', () => { type: NodeTypes.INTERPOLATION, content: { content: `_ctx.bar`, - isConstant: false, - isStatic: false + isStatic: false, + constType: ConstantTypes.NOT_CONSTANT } }, patchFlag: `1 /* TEXT */` diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 9bddda9e79..e3d7e5058e 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -5,7 +5,8 @@ import { DirectiveNode, NodeTypes, CompilerOptions, - InterpolationNode + InterpolationNode, + ConstantTypes } from '../../src' import { transformIf } from '../../src/transforms/vIf' import { transformExpression } from '../../src/transforms/transformExpression' @@ -408,7 +409,7 @@ describe('compiler: expression transform', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `13000n`, isStatic: false, - isConstant: true + constType: ConstantTypes.CAN_STRINGIFY }) }) diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 411aba2c46..50d8d625d6 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -12,7 +12,8 @@ import { SimpleExpressionNode, ElementNode, InterpolationNode, - ForCodegenNode + ForCodegenNode, + ConstantTypes } from '../../src/ast' import { ErrorCodes } from '../../src/errors' import { CompilerOptions, generate } from '../../src' @@ -760,7 +761,7 @@ describe('compiler: v-for', () => { false /* disableTracking */ ) ).toMatchObject({ - source: { content: `10`, isConstant: true }, + source: { content: `10`, constType: ConstantTypes.CAN_STRINGIFY }, params: [{ content: `item` }], innerVNodeCall: { tag: `"p"`, @@ -772,7 +773,7 @@ describe('compiler: v-for', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'item', isStatic: false, - isConstant: false + constType: ConstantTypes.NOT_CONSTANT } }, patchFlag: genFlagText(PatchFlags.TEXT) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 35849833c6..c1eaf80cbb 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -189,11 +189,23 @@ export interface DirectiveNode extends Node { parseResult?: ForParseResult } +/** + * Static types have several levels. + * Higher levels implies lower levels. e.g. a node that can be stringified + * can always be hoisted and skipped for patch. + */ +export const enum ConstantTypes { + NOT_CONSTANT = 0, + CAN_SKIP_PATCH, + CAN_HOIST, + CAN_STRINGIFY +} + export interface SimpleExpressionNode extends Node { type: NodeTypes.SIMPLE_EXPRESSION content: string isStatic: boolean - isConstant: boolean + constType: ConstantTypes /** * Indicates this is an identifier for a hoist vnode call and points to the * hoisted node. @@ -204,11 +216,6 @@ export interface SimpleExpressionNode extends Node { * the identifiers declared inside the function body. */ identifiers?: string[] - /** - * some expressions (e.g. transformAssetUrls import identifiers) are constant, - * but cannot be stringified because they must be first evaluated at runtime. - */ - isRuntimeConstant?: boolean } export interface InterpolationNode extends Node { @@ -611,14 +618,14 @@ export function createSimpleExpression( content: SimpleExpressionNode['content'], isStatic: SimpleExpressionNode['isStatic'], loc: SourceLocation = locStub, - isConstant: boolean = false + constType: ConstantTypes = ConstantTypes.NOT_CONSTANT ): SimpleExpressionNode { return { type: NodeTypes.SIMPLE_EXPRESSION, loc, - isConstant, content, - isStatic + isStatic, + constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType } } diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 2d66e84ae5..63cb285f24 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -22,7 +22,8 @@ import { TextNode, TemplateChildNode, InterpolationNode, - createRoot + createRoot, + ConstantTypes } from './ast' type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent' @@ -656,7 +657,9 @@ function parseAttribute( type: NodeTypes.SIMPLE_EXPRESSION, content, isStatic, - isConstant: isStatic, + constType: isStatic + ? ConstantTypes.CAN_STRINGIFY + : ConstantTypes.NOT_CONSTANT, loc } } @@ -677,8 +680,8 @@ function parseAttribute( content: value.content, isStatic: false, // Treat as non-constant by default. This can be potentially set to - // true by `transformExpression` to make it eligible for hoisting. - isConstant: false, + // other values by `transformExpression` to make it eligible for hoisting. + constType: ConstantTypes.NOT_CONSTANT, loc: value.loc }, arg, @@ -785,7 +788,7 @@ function parseInterpolation( type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, // Set `isConstant` to false by default and will decide in transformExpression - isConstant: false, + constType: ConstantTypes.NOT_CONSTANT, content, loc: getSelection(context, innerStart, innerEnd) }, diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index eddd03caa1..0db0e7a0bc 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -15,7 +15,8 @@ import { CacheExpression, createCacheExpression, TemplateLiteral, - createVNodeCall + createVNodeCall, + ConstantTypes } from './ast' import { isString, @@ -245,7 +246,7 @@ export function createTransformContext( `_hoisted_${context.hoists.length}`, false, exp.loc, - true + ConstantTypes.CAN_HOIST ) identifier.hoisted = exp return identifier diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts index a4131c6d6f..1c066d7b8e 100644 --- a/packages/compiler-core/src/transforms/hoistStatic.ts +++ b/packages/compiler-core/src/transforms/hoistStatic.ts @@ -1,4 +1,5 @@ import { + ConstantTypes, RootNode, NodeTypes, TemplateChildNode, @@ -37,16 +38,10 @@ export function isSingleElementRoot( ) } -const enum StaticType { - NOT_STATIC = 0, - FULL_STATIC, - HAS_RUNTIME_CONSTANT -} - function walk( node: ParentNode, context: TransformContext, - resultCache: Map, + resultCache: Map, doNotHoistNode: boolean = false ) { let hasHoistedNode = false @@ -58,7 +53,7 @@ function walk( // @vue/compiler-dom), but doing it here allows us to perform only one full // walk of the AST and allow `stringifyStatic` to stop walking as soon as its // stringficiation threshold is met. - let hasRuntimeConstant = false + let canStringify = true const { children } = node for (let i = 0; i < children.length; i++) { @@ -68,20 +63,20 @@ function walk( child.type === NodeTypes.ELEMENT && child.tagType === ElementTypes.ELEMENT ) { - let staticType - if ( - !doNotHoistNode && - (staticType = getStaticType(child, resultCache)) > 0 - ) { - if (staticType === StaticType.HAS_RUNTIME_CONSTANT) { - hasRuntimeConstant = true + const constantType = doNotHoistNode + ? ConstantTypes.NOT_CONSTANT + : getConstantType(child, resultCache) + if (constantType > ConstantTypes.NOT_CONSTANT) { + if (constantType < ConstantTypes.CAN_STRINGIFY) { + canStringify = false + } + if (constantType >= ConstantTypes.CAN_HOIST) { + ;(child.codegenNode as VNodeCall).patchFlag = + PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``) + child.codegenNode = context.hoist(child.codegenNode!) + hasHoistedNode = true + continue } - // whole tree is static - ;(child.codegenNode as VNodeCall).patchFlag = - PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``) - child.codegenNode = context.hoist(child.codegenNode!) - hasHoistedNode = true - continue } else { // node may contain dynamic children, but its props may be eligible for // hoisting. @@ -92,7 +87,8 @@ function walk( (!flag || flag === PatchFlags.NEED_PATCH || flag === PatchFlags.TEXT) && - !hasNonHoistableProps(child) + getGeneratedPropsConstantType(child, resultCache) >= + ConstantTypes.CAN_HOIST ) { const props = getNodeProps(child) if (props) { @@ -102,13 +98,15 @@ function walk( } } } else if (child.type === NodeTypes.TEXT_CALL) { - const staticType = getStaticType(child.content, resultCache) - if (staticType > 0) { - if (staticType === StaticType.HAS_RUNTIME_CONSTANT) { - hasRuntimeConstant = true + const contentType = getConstantType(child.content, resultCache) + if (contentType > 0) { + if (contentType < ConstantTypes.CAN_STRINGIFY) { + canStringify = false + } + if (contentType >= ConstantTypes.CAN_HOIST) { + child.codegenNode = context.hoist(child.codegenNode) + hasHoistedNode = true } - child.codegenNode = context.hoist(child.codegenNode) - hasHoistedNode = true } } @@ -131,19 +129,19 @@ function walk( } } - if (!hasRuntimeConstant && hasHoistedNode && context.transformHoist) { + if (canStringify && hasHoistedNode && context.transformHoist) { context.transformHoist(children, context, node) } } -export function getStaticType( +export function getConstantType( node: TemplateChildNode | SimpleExpressionNode, - resultCache: Map = new Map() -): StaticType { + resultCache: Map = new Map() +): ConstantTypes { switch (node.type) { case NodeTypes.ELEMENT: if (node.tagType !== ElementTypes.ELEMENT) { - return StaticType.NOT_STATIC + return ConstantTypes.NOT_CONSTANT } const cached = resultCache.get(node) if (cached !== undefined) { @@ -151,40 +149,64 @@ export function getStaticType( } const codegenNode = node.codegenNode! if (codegenNode.type !== NodeTypes.VNODE_CALL) { - return StaticType.NOT_STATIC + return ConstantTypes.NOT_CONSTANT } const flag = getPatchFlag(codegenNode) - if (!flag && !hasNonHoistableProps(node)) { - // element self is static. check its children. - let returnType = StaticType.FULL_STATIC + if (!flag) { + let returnType = ConstantTypes.CAN_STRINGIFY + + // Element itself has no patch flag. However we still need to check: + + // 1. Even for a node with no patch flag, it is possible for it to contain + // non-hoistable expressions that refers to scope variables, e.g. compiler + // injected keys or cached event handlers. Therefore we need to always + // check the codegenNode's props to be sure. + const generatedPropsType = getGeneratedPropsConstantType( + node, + resultCache + ) + if (generatedPropsType === ConstantTypes.NOT_CONSTANT) { + resultCache.set(node, ConstantTypes.NOT_CONSTANT) + return ConstantTypes.NOT_CONSTANT + } + if (generatedPropsType < returnType) { + returnType = generatedPropsType + } + + // 2. its children. for (let i = 0; i < node.children.length; i++) { - const childType = getStaticType(node.children[i], resultCache) - if (childType === StaticType.NOT_STATIC) { - resultCache.set(node, StaticType.NOT_STATIC) - return StaticType.NOT_STATIC - } else if (childType === StaticType.HAS_RUNTIME_CONSTANT) { - returnType = StaticType.HAS_RUNTIME_CONSTANT + const childType = getConstantType(node.children[i], resultCache) + if (childType === ConstantTypes.NOT_CONSTANT) { + resultCache.set(node, ConstantTypes.NOT_CONSTANT) + return ConstantTypes.NOT_CONSTANT + } + if (childType < returnType) { + returnType = childType } } - // check if any of the props contain runtime constants - if (returnType !== StaticType.HAS_RUNTIME_CONSTANT) { + // 3. if the type is not already CAN_SKIP_PATCH which is the lowest non-0 + // type, check if any of the props can cause the type to be lowered + // we can skip can_patch because it's guaranteed by the absence of a + // patchFlag. + if (returnType > ConstantTypes.CAN_SKIP_PATCH) { for (let i = 0; i < node.props.length; i++) { const p = node.props[i] - if ( - p.type === NodeTypes.DIRECTIVE && - p.name === 'bind' && - p.exp && - (p.exp.type === NodeTypes.COMPOUND_EXPRESSION || - p.exp.isRuntimeConstant) - ) { - returnType = StaticType.HAS_RUNTIME_CONSTANT + if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind' && p.exp) { + const expType = getConstantType(p.exp, resultCache) + if (expType === ConstantTypes.NOT_CONSTANT) { + resultCache.set(node, ConstantTypes.NOT_CONSTANT) + return ConstantTypes.NOT_CONSTANT + } + if (expType < returnType) { + returnType = expType + } } } } // only svg/foreignObject could be block here, however if they are - // stati then they don't need to be blocks since there will be no + // static then they don't need to be blocks since there will be no // nested updates. if (codegenNode.isBlock) { codegenNode.isBlock = false @@ -193,37 +215,33 @@ export function getStaticType( resultCache.set(node, returnType) return returnType } else { - resultCache.set(node, StaticType.NOT_STATIC) - return StaticType.NOT_STATIC + resultCache.set(node, ConstantTypes.NOT_CONSTANT) + return ConstantTypes.NOT_CONSTANT } case NodeTypes.TEXT: case NodeTypes.COMMENT: - return StaticType.FULL_STATIC + return ConstantTypes.CAN_STRINGIFY case NodeTypes.IF: case NodeTypes.FOR: case NodeTypes.IF_BRANCH: - return StaticType.NOT_STATIC + return ConstantTypes.NOT_CONSTANT case NodeTypes.INTERPOLATION: case NodeTypes.TEXT_CALL: - return getStaticType(node.content, resultCache) + return getConstantType(node.content, resultCache) case NodeTypes.SIMPLE_EXPRESSION: - return node.isRuntimeConstant - ? StaticType.HAS_RUNTIME_CONSTANT - : node.isConstant - ? StaticType.FULL_STATIC - : StaticType.NOT_STATIC + return node.constType case NodeTypes.COMPOUND_EXPRESSION: - let returnType = StaticType.FULL_STATIC + let returnType = ConstantTypes.CAN_STRINGIFY for (let i = 0; i < node.children.length; i++) { const child = node.children[i] if (isString(child) || isSymbol(child)) { continue } - const childType = getStaticType(child, resultCache) - if (childType === StaticType.NOT_STATIC) { - return StaticType.NOT_STATIC - } else if (childType === StaticType.HAS_RUNTIME_CONSTANT) { - returnType = StaticType.HAS_RUNTIME_CONSTANT + const childType = getConstantType(child, resultCache) + if (childType === ConstantTypes.NOT_CONSTANT) { + return ConstantTypes.NOT_CONSTANT + } else if (childType < returnType) { + returnType = childType } } return returnType @@ -232,33 +250,40 @@ export function getStaticType( const exhaustiveCheck: never = node exhaustiveCheck } - return StaticType.NOT_STATIC + return ConstantTypes.NOT_CONSTANT } } -/** - * Even for a node with no patch flag, it is possible for it to contain - * non-hoistable expressions that refers to scope variables, e.g. compiler - * injected keys or cached event handlers. Therefore we need to always check the - * codegenNode's props to be sure. - */ -function hasNonHoistableProps(node: PlainElementNode): boolean { +function getGeneratedPropsConstantType( + node: PlainElementNode, + resultCache: Map +): ConstantTypes { + let returnType = ConstantTypes.CAN_STRINGIFY const props = getNodeProps(node) if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) { const { properties } = props for (let i = 0; i < properties.length; i++) { const { key, value } = properties[i] - if ( - key.type !== NodeTypes.SIMPLE_EXPRESSION || - !key.isStatic || - (value.type !== NodeTypes.SIMPLE_EXPRESSION || - (!value.isStatic && !value.isConstant)) - ) { - return true + const keyType = getConstantType(key, resultCache) + if (keyType === ConstantTypes.NOT_CONSTANT) { + return keyType + } + if (keyType < returnType) { + returnType = keyType + } + if (value.type !== NodeTypes.SIMPLE_EXPRESSION) { + return ConstantTypes.NOT_CONSTANT + } + const valueType = getConstantType(value, resultCache) + if (valueType === ConstantTypes.NOT_CONSTANT) { + return valueType + } + if (valueType < returnType) { + returnType = valueType } } } - return false + return returnType } function getNodeProps(node: PlainElementNode) { diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 1d4a46a133..f2f1459e05 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -18,7 +18,8 @@ import { VNodeCall, TemplateTextChildNode, DirectiveArguments, - createVNodeCall + createVNodeCall, + ConstantTypes } from '../ast' import { PatchFlags, @@ -53,7 +54,7 @@ import { isStaticExp } from '../utils' import { buildSlots } from './vSlot' -import { getStaticType } from './hoistStatic' +import { getConstantType } from './hoistStatic' import { BindingTypes } from '../options' // some directive transforms (e.g. v-model) may return a symbol for runtime @@ -166,7 +167,10 @@ export const transformElement: NodeTransform = (node, context) => { const hasDynamicTextChild = type === NodeTypes.INTERPOLATION || type === NodeTypes.COMPOUND_EXPRESSION - if (hasDynamicTextChild && !getStaticType(child)) { + if ( + hasDynamicTextChild && + getConstantType(child) === ConstantTypes.NOT_CONSTANT + ) { patchFlag |= PatchFlags.TEXT } // pass directly if the only child is a text node @@ -343,7 +347,7 @@ export function buildProps( value.type === NodeTypes.JS_CACHE_EXPRESSION || ((value.type === NodeTypes.SIMPLE_EXPRESSION || value.type === NodeTypes.COMPOUND_EXPRESSION) && - getStaticType(value) > 0) + getConstantType(value) > 0) ) { // skip if the prop is a cached handler or has constant value return diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 83f80e8755..1f2004bce3 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -14,7 +14,8 @@ import { ExpressionNode, SimpleExpressionNode, CompoundExpressionNode, - createCompoundExpression + createCompoundExpression, + ConstantTypes } from '../ast' import { advancePositionWithClone, isSimpleIdentifier } from '../utils' import { @@ -190,25 +191,26 @@ export function processExpression( // fast path if expression is a simple identifier. const rawExp = node.content - // bail on parens to prevent any possible function invocations. - const bailConstant = rawExp.indexOf(`(`) > -1 + // bail constant on parens (function invocation) and dot (member access) + const bailConstant = rawExp.indexOf(`(`) > -1 || rawExp.indexOf('.') > 0 + if (isSimpleIdentifier(rawExp)) { - // const bindings exposed from setup - we know they never change - // marking it as runtime constant will prevent it from being listed as - // a dynamic prop. - if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) { - node.isRuntimeConstant = true - } - if ( - !asParams && - !context.identifiers[rawExp] && - !isGloballyWhitelisted(rawExp) && - !isLiteralWhitelisted(rawExp) - ) { + const isScopeVarReference = context.identifiers[rawExp] + const isAllowedGlobal = isGloballyWhitelisted(rawExp) + const isLiteral = isLiteralWhitelisted(rawExp) + if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) { + // const bindings exposed from setup can be skipped for patching but + // cannot be hoisted to module scope + if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) { + node.constType = ConstantTypes.CAN_SKIP_PATCH + } node.content = rewriteIdentifier(rawExp) - } else if (!context.identifiers[rawExp] && !bailConstant) { - // mark node constant for hoisting unless it's referring a scope variable - node.isConstant = true + } else if (!isScopeVarReference) { + if (isLiteral) { + node.constType = ConstantTypes.CAN_STRINGIFY + } else { + node.constType = ConstantTypes.CAN_HOIST + } } return node } @@ -342,7 +344,7 @@ export function processExpression( start: advancePositionWithClone(node.loc.start, source, start), end: advancePositionWithClone(node.loc.start, source, end) }, - id.isConstant /* isConstant */ + id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT ) ) if (i === ids.length - 1 && end < rawExp.length) { @@ -355,7 +357,9 @@ export function processExpression( ret = createCompoundExpression(children, node.loc) } else { ret = node - ret.isConstant = !bailConstant + ret.constType = bailConstant + ? ConstantTypes.NOT_CONSTANT + : ConstantTypes.CAN_STRINGIFY } ret.identifiers = Object.keys(knownIds) return ret diff --git a/packages/compiler-core/src/transforms/transformText.ts b/packages/compiler-core/src/transforms/transformText.ts index 7419497fe1..103ceb6fb5 100644 --- a/packages/compiler-core/src/transforms/transformText.ts +++ b/packages/compiler-core/src/transforms/transformText.ts @@ -4,11 +4,13 @@ import { CompoundExpressionNode, createCallExpression, CallExpression, - ElementTypes + ElementTypes, + ConstantTypes } from '../ast' import { isText } from '../utils' import { CREATE_TEXT } from '../runtimeHelpers' import { PatchFlags, PatchFlagNames } from '@vue/shared' +import { getConstantType } from './hoistStatic' // Merge adjacent text nodes and expressions into a single expression // e.g.
abc {{ d }} {{ e }}
should have a single expression node as child. @@ -78,7 +80,10 @@ export const transformText: NodeTransform = (node, context) => { callArgs.push(child) } // mark dynamic text with flag so it gets patched inside a block - if (!context.ssr && child.type !== NodeTypes.TEXT) { + if ( + !context.ssr && + getConstantType(child) === ConstantTypes.NOT_CONSTANT + ) { callArgs.push( `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */` ) diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 63c4458c1a..7949c014fb 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -77,7 +77,7 @@ export const transformFor = createStructuralDirectiveTransform( const isStableFragment = forNode.source.type === NodeTypes.SIMPLE_EXPRESSION && - forNode.source.isConstant + forNode.source.constType > 0 const fragmentFlag = isStableFragment ? PatchFlags.STABLE_FRAGMENT : keyProp diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts index 76a79e53f2..e98604d037 100644 --- a/packages/compiler-core/src/transforms/vIf.ts +++ b/packages/compiler-core/src/transforms/vIf.ts @@ -21,7 +21,8 @@ import { createVNodeCall, AttributeNode, locStub, - CacheExpression + CacheExpression, + ConstantTypes } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { processExpression } from './transformExpression' @@ -227,7 +228,12 @@ function createChildrenCodegenNode( const { helper } = context const keyProperty = createObjectProperty( `key`, - createSimpleExpression(`${keyIndex}`, false, locStub, true) + createSimpleExpression( + `${keyIndex}`, + false, + locStub, + ConstantTypes.CAN_HOIST + ) ) const { children } = branch const firstChild = children[0] diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts index 7bdffd81c0..3151c958e3 100644 --- a/packages/compiler-core/src/transforms/vModel.ts +++ b/packages/compiler-core/src/transforms/vModel.ts @@ -6,7 +6,8 @@ import { NodeTypes, Property, ElementTypes, - ExpressionNode + ExpressionNode, + ConstantTypes } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { @@ -125,7 +126,12 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { props.push( createObjectProperty( modifiersKey, - createSimpleExpression(`{ ${modifiers} }`, false, dir.loc, true) + createSimpleExpression( + `{ ${modifiers} }`, + false, + dir.loc, + ConstantTypes.CAN_HOIST + ) ) ) } diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index 00db326800..207d53e419 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -87,7 +87,7 @@ export const transformOn: DirectiveTransform = ( context.cacheHandlers && // runtime constants don't need to be cached // (this is analyzed by compileScript in SFC