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 dba4bf1f0..9ad4a155c 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap @@ -173,7 +173,7 @@ exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static t "const _Vue = Vue const _createVNode = Vue.createVNode -const _hoisted_1 = _createVNode(\\"span\\", null, [\\"foo \\", _toString(1), _toString(2)]) +const _hoisted_1 = _createVNode(\\"span\\", null, [\\"foo \\", _toString(1), _toString(true)]) return function render() { with (this) { @@ -203,7 +203,7 @@ return function render() { }" `; -exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that with scope variable (2) 1`] = ` +exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (2) 1`] = ` "const _Vue = Vue return function render() { @@ -221,7 +221,24 @@ return function render() { }" `; -exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that with scope variable 1`] = ` +exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (v-slot) 1`] = ` +"const _Vue = Vue + +return function render() { + with (this) { + const { toString: _toString, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue + + const _component_Comp = _resolveComponent(\\"Comp\\") + + return (_openBlock(), _createBlock(_component_Comp, null, { + default: ({ foo }) => [_toString(_ctx.foo)], + _compiled: true + })) + } +}" +`; + +exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables 1`] = ` "const _Vue = Vue return function render() { diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts index d998aa26c..bf4e2370b 100644 --- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts @@ -446,7 +446,7 @@ describe('compiler: hoistStatic transform', () => { describe('prefixIdentifiers', () => { test('hoist nested static tree with static interpolation', () => { const { root, args } = transformWithHoist( - `
foo {{ 1 }} {{ 2 }}
`, + `
foo {{ 1 }} {{ true }}
`, { prefixIdentifiers: true } @@ -474,7 +474,7 @@ describe('compiler: hoistStatic transform', () => { { type: NodeTypes.INTERPOLATION, content: { - content: `2`, + content: `true`, isStatic: false, isConstant: true } @@ -600,7 +600,7 @@ describe('compiler: hoistStatic transform', () => { expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist expressions that with scope variable', () => { + test('should NOT hoist expressions that refer scope variables', () => { const { root } = transformWithHoist( `

{{ o }}

`, { @@ -612,7 +612,7 @@ describe('compiler: hoistStatic transform', () => { expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist expressions that with scope variable (2)', () => { + test('should NOT hoist expressions that refer scope variables (2)', () => { const { root } = transformWithHoist( `

{{ o + 'foo' }}

`, { @@ -623,5 +623,17 @@ describe('compiler: hoistStatic transform', () => { expect(root.hoists.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) + + test('should NOT hoist expressions that refer scope variables (v-slot)', () => { + const { root } = transformWithHoist( + `{{ foo }}`, + { + prefixIdentifiers: true + } + ) + + expect(root.hoists.length).toBe(0) + expect(generate(root).code).toMatchSnapshot() + }) }) }) diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index c369d4564..f2520d179 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -564,12 +564,9 @@ function parseAttribute( ) let content = match[2] let isStatic = true - // Non-dynamic arg is a constant. - let isConstant = true if (content.startsWith('[')) { isStatic = false - isConstant = false if (!content.endsWith(']')) { emitError( @@ -585,7 +582,7 @@ function parseAttribute( type: NodeTypes.SIMPLE_EXPRESSION, content, isStatic, - isConstant, + isConstant: isStatic, loc } } @@ -611,7 +608,8 @@ function parseAttribute( type: NodeTypes.SIMPLE_EXPRESSION, content: value.content, isStatic: false, - // Set `isConstant` to false by default and will decide in transformExpression + // Treat as non-constant by default. This can be potentially set to + // true by `transformExpression` to make it eligible for hoisting. isConstant: false, loc: value.loc }, diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 083b3023c..2bac56a84 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -91,6 +91,9 @@ export function processExpression( !literalsWhitelist.has(rawExp) ) { node.content = `_ctx.${rawExp}` + } else if (!context.identifiers[rawExp]) { + // mark node constant for hoisting unless it's referring a scope variable + node.isConstant = true } return node } @@ -109,13 +112,13 @@ export function processExpression( const ids: (Identifier & PrefixMeta)[] = [] const knownIds = Object.create(context.identifiers) - let isConstant = true // walk the AST and look for identifiers that need to be prefixed with `_ctx.`. walkJS(ast, { enter(node: Node & PrefixMeta, parent) { if (node.type === 'Identifier') { if (!ids.includes(node)) { - if (!knownIds[node.name] && shouldPrefix(node, parent)) { + const needPrefix = shouldPrefix(node, parent) + if (!knownIds[node.name] && needPrefix) { if (isPropertyShorthand(node, parent)) { // property shorthand like { foo }, we need to add the key since we // rewrite the value @@ -123,14 +126,11 @@ export function processExpression( } node.name = `_ctx.${node.name}` node.isConstant = false - isConstant = false ids.push(node) } else if (!isStaticPropertyKey(node, parent)) { - // This means this identifier is pointing to a scope variable (a v-for alias, or a v-slot prop) - // which is also dynamic and cannot be hoisted. - node.isConstant = !( - knownIds[node.name] && shouldPrefix(node, parent) - ) + // The identifier is considered constant unless it's pointing to a + // scope variable (a v-for alias, or a v-slot prop) + node.isConstant = !(needPrefix && knownIds[node.name]) // also generate sub-expressions for other identifiers for better // source map support. (except for property keys which are static) ids.push(node) @@ -220,7 +220,7 @@ export function processExpression( ret = createCompoundExpression(children, node.loc) } else { ret = node - ret.isConstant = isConstant + ret.isConstant = true } ret.identifiers = Object.keys(knownIds) return ret