From ef6986fbc3e075f73635e9554fbc80a525ecf5d9 Mon Sep 17 00:00:00 2001 From: edison Date: Mon, 16 Dec 2024 14:30:38 +0800 Subject: [PATCH] refactor(compiler-vapor): move operation with constant values out of renderEffect (#12547) --- packages/compiler-core/src/babelUtils.ts | 76 +++++++++++++++++++ packages/compiler-sfc/src/compileScript.ts | 38 +--------- .../transformElement.spec.ts.snap | 4 +- .../__snapshots__/vBind.spec.ts.snap | 26 +++++++ .../transforms/transformElement.spec.ts | 49 +++++------- .../__tests__/transforms/vBind.spec.ts | 34 ++++++++- packages/compiler-vapor/src/transform.ts | 26 ++++++- 7 files changed, 182 insertions(+), 71 deletions(-) diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 056371245fa..f81f2691570 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -510,3 +510,79 @@ export function unwrapTSNode(node: Node): Node { return node } } + +export function isStaticNode(node: Node): boolean { + node = unwrapTSNode(node) + + switch (node.type) { + case 'UnaryExpression': // void 0, !true + return isStaticNode(node.argument) + + case 'LogicalExpression': // 1 > 2 + case 'BinaryExpression': // 1 + 2 + return isStaticNode(node.left) && isStaticNode(node.right) + + case 'ConditionalExpression': { + // 1 ? 2 : 3 + return ( + isStaticNode(node.test) && + isStaticNode(node.consequent) && + isStaticNode(node.alternate) + ) + } + + case 'SequenceExpression': // (1, 2) + case 'TemplateLiteral': // `foo${1}` + return node.expressions.every(expr => isStaticNode(expr)) + + case 'ParenthesizedExpression': // (1) + return isStaticNode(node.expression) + + case 'StringLiteral': + case 'NumericLiteral': + case 'BooleanLiteral': + case 'NullLiteral': + case 'BigIntLiteral': + return true + } + return false +} + +export function isConstantNode( + node: Node, + onIdentifier: (name: string) => boolean, +): boolean { + if (isStaticNode(node)) return true + + node = unwrapTSNode(node) + switch (node.type) { + case 'Identifier': + return onIdentifier(node.name) + case 'RegExpLiteral': + return true + case 'ObjectExpression': + return node.properties.every(prop => { + // { bar() {} } object methods are not considered static nodes + if (prop.type === 'ObjectMethod') return false + // { ...{ foo: 1 } } + if (prop.type === 'SpreadElement') + return isConstantNode(prop.argument, onIdentifier) + // { foo: 1 } + return ( + (!prop.computed || isConstantNode(prop.key, onIdentifier)) && + isConstantNode(prop.value, onIdentifier) + ) + }) + case 'ArrayExpression': + return node.elements.every(element => { + // [1, , 3] + if (element === null) return true + // [1, ...[2, 3]] + if (element.type === 'SpreadElement') + return isConstantNode(element.argument, onIdentifier) + // [1, 2] + return isConstantNode(element, onIdentifier) + }) + } + return false +} diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 750770e20f4..0d1dc0976f4 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -2,6 +2,7 @@ import { BindingTypes, UNREF, isFunctionType, + isStaticNode, unwrapTSNode, walkIdentifiers, } from '@vue/compiler-dom' @@ -1266,40 +1267,3 @@ function canNeverBeRef(node: Node, userReactiveImport?: string): boolean { return false } } - -function isStaticNode(node: Node): boolean { - node = unwrapTSNode(node) - - switch (node.type) { - case 'UnaryExpression': // void 0, !true - return isStaticNode(node.argument) - - case 'LogicalExpression': // 1 > 2 - case 'BinaryExpression': // 1 + 2 - return isStaticNode(node.left) && isStaticNode(node.right) - - case 'ConditionalExpression': { - // 1 ? 2 : 3 - return ( - isStaticNode(node.test) && - isStaticNode(node.consequent) && - isStaticNode(node.alternate) - ) - } - - case 'SequenceExpression': // (1, 2) - case 'TemplateLiteral': // `foo${1}` - return node.expressions.every(expr => isStaticNode(expr)) - - case 'ParenthesizedExpression': // (1) - return isStaticNode(node.expression) - - case 'StringLiteral': - case 'NumericLiteral': - case 'BooleanLiteral': - case 'NullLiteral': - case 'BigIntLiteral': - return true - } - return false -} diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap index efec7b9ccb7..1c37dad0f0d 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -317,12 +317,12 @@ export function render(_ctx) { `; exports[`compiler: element transform > props merging: style 1`] = ` -"import { setStyle as _setStyle, renderEffect as _renderEffect, template as _template } from 'vue'; +"import { setStyle as _setStyle, template as _template } from 'vue'; const t0 = _template("
", true) export function render(_ctx) { const n0 = t0() - _renderEffect(() => _setStyle(n0, ["color: green", { color: 'red' }])) + _setStyle(n0, ["color: green", { color: 'red' }]) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap index 30d5249fd76..857b3a5d00b 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap @@ -400,3 +400,29 @@ export function render(_ctx) { return n0 }" `; + +exports[`compiler v-bind > with constant value 1`] = ` +"import { setProp as _setProp, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx, $props, $emit, $attrs, $slots) { + const n0 = t0() + _setProp(n0, "a", void 0) + _setProp(n0, "b", 1 > 2) + _setProp(n0, "c", 1 + 2) + _setProp(n0, "d", 1 ? 2 : 3) + _setProp(n0, "e", (2)) + _setProp(n0, "g", 1) + _setProp(n0, "i", true) + _setProp(n0, "j", null) + _setProp(n0, "k", _ctx.x) + _setProp(n0, "l", { foo: 1 }) + _setProp(n0, "m", { [_ctx.x]: 1 }) + _setProp(n0, "n", { ...{ foo: 1 } }) + _setProp(n0, "o", [1, , 3]) + _setProp(n0, "p", [1, ...[2, 3]]) + _setProp(n0, "q", [1, 2]) + _setProp(n0, "r", /\\s+/) + return n0 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts index b8d6f1166e9..030f32eea2b 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -720,40 +720,29 @@ describe('compiler: element transform', () => { ) expect(code).toMatchSnapshot() - expect(ir.block.effect).toMatchObject([ + expect(ir.block.operation).toMatchObject([ { - expressions: [ - { + type: IRNodeTypes.SET_PROP, + element: 0, + prop: { + key: { type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ color: 'red' }`, - isStatic: false, + content: 'style', + isStatic: true, }, - ], - operations: [ - { - type: IRNodeTypes.SET_PROP, - element: 0, - prop: { - key: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: 'style', - isStatic: true, - }, - values: [ - { - type: NodeTypes.SIMPLE_EXPRESSION, - content: 'color: green', - isStatic: true, - }, - { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ color: 'red' }`, - isStatic: false, - }, - ], + values: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'color: green', + isStatic: true, }, - }, - ], + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `{ color: 'red' }`, + isStatic: false, + }, + ], + }, }, ]) }) diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts index 292b05d40c6..d785adfe810 100644 --- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts @@ -1,4 +1,4 @@ -import { ErrorCodes, NodeTypes } from '@vue/compiler-dom' +import { BindingTypes, ErrorCodes, NodeTypes } from '@vue/compiler-dom' import { DynamicFlag, IRNodeTypes, @@ -661,4 +661,36 @@ describe('compiler v-bind', () => { expect(code).matchSnapshot() expect(code).contains('{ depth: () => (0) }') }) + + test('with constant value', () => { + const { code } = compileWithVBind( + ` +
`, + { + bindingMetadata: { + x: BindingTypes.LITERAL_CONST, + }, + }, + ) + expect(code).matchSnapshot() + }) }) diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 3855808998a..431054a6e43 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -1,6 +1,7 @@ import { type AllNode, type TransformOptions as BaseTransformOptions, + BindingTypes, type CommentNode, type CompilerCompatOptions, type ElementNode, @@ -11,6 +12,7 @@ import { type TemplateChildNode, defaultOnError, defaultOnWarn, + isConstantNode, isVSlot, } from '@vue/compiler-dom' import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared' @@ -139,7 +141,11 @@ export class TransformContext { ...operations: OperationNode[] ): void { expressions = expressions.filter(exp => !isConstantExpression(exp)) - if (this.inVOnce || expressions.length === 0) { + if ( + this.inVOnce || + expressions.length === 0 || + isStaticExpression(this.root, expressions) + ) { return this.registerOperation(...operations) } const existing = this.block.effect.find(e => @@ -298,3 +304,21 @@ export function createStructuralDirectiveTransform( } } } + +function isStaticExpression( + context: TransformContext, + expressions: SimpleExpressionNode[], +) { + const { + options: { bindingMetadata }, + } = context + const isLiteralConst = (name: string) => + bindingMetadata[name] === BindingTypes.LITERAL_CONST + return expressions.every(node => { + if (node.ast) { + return isConstantNode(node.ast, isLiteralConst) + } else if (node.ast === null) { + return isLiteralConst(node.content) + } + }) +}