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
+}
BindingTypes,
UNREF,
isFunctionType,
+ isStaticNode,
unwrapTSNode,
walkIdentifiers,
} from '@vue/compiler-dom'
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
-}
`;
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("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setStyle(n0, ["color: green", { color: 'red' }]))
+ _setStyle(n0, ["color: green", { color: 'red' }])
return n0
}"
`;
return n0
}"
`;
+
+exports[`compiler v-bind > with constant value 1`] = `
+"import { setProp as _setProp, template as _template } from 'vue';
+const t0 = _template("<div f=\\"foo1\\" h=\\"1\\"></div>", 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
+}"
+`;
)
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,
+ },
+ ],
+ },
},
])
})
-import { ErrorCodes, NodeTypes } from '@vue/compiler-dom'
+import { BindingTypes, ErrorCodes, NodeTypes } from '@vue/compiler-dom'
import {
DynamicFlag,
IRNodeTypes,
expect(code).matchSnapshot()
expect(code).contains('{ depth: () => (0) }')
})
+
+ test('with constant value', () => {
+ const { code } = compileWithVBind(
+ `
+ <div
+ :a="void 0"
+ :b="1 > 2"
+ :c="1 + 2"
+ :d="1 ? 2 : 3"
+ :e="(2)"
+ :f="\`foo${1}\`"
+ :g="1"
+ :h="'1'"
+ :i="true"
+ :j="null"
+ :k="x"
+ :l="{ foo: 1 }"
+ :m="{ [x]: 1 }"
+ :n="{ ...{ foo: 1 } }"
+ :o="[1, , 3]"
+ :p="[1, ...[2, 3]]"
+ :q="[1, 2]"
+ :r="/\\s+/"
+ />`,
+ {
+ bindingMetadata: {
+ x: BindingTypes.LITERAL_CONST,
+ },
+ },
+ )
+ expect(code).matchSnapshot()
+ })
})
import {
type AllNode,
type TransformOptions as BaseTransformOptions,
+ BindingTypes,
type CommentNode,
type CompilerCompatOptions,
type ElementNode,
type TemplateChildNode,
defaultOnError,
defaultOnWarn,
+ isConstantNode,
isVSlot,
} from '@vue/compiler-dom'
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
...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 =>
}
}
}
+
+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)
+ }
+ })
+}