diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index bb18faf0..bb59f494 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -62,10 +62,31 @@ export type HoistTransform = ( ) => void export const enum BindingTypes { + /** + * returned from data() + */ DATA = 'data', + /** + * decalred as a prop + */ PROPS = 'props', - SETUP = 'setup', - CONST = 'const', + /** + * a let binding (may or may not be a ref) + */ + SETUP_LET = 'setup-let', + /** + * a const binding that can never be a ref. + * these bindings don't need `unref()` calls when processed in inlined + * template expressions. + */ + SETUP_CONST = 'setup-const', + /** + * a const binding that may be a ref. + */ + SETUP_CONST_REF = 'setup-const-ref', + /** + * declared by other options, e.g. computed, inject + */ OPTIONS = 'options' } diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index e9e1ac58..e3989a80 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -263,20 +263,22 @@ export function resolveComponentType( return resolvedTag } } - const tagFromSetup = checkType(BindingTypes.SETUP) - if (tagFromSetup) { - return context.inline - ? // setup scope bindings may be refs so they need to be unrefed - `${context.helperString(UNREF)}(${tagFromSetup})` - : `$setup[${JSON.stringify(tagFromSetup)}]` - } - const tagFromConst = checkType(BindingTypes.CONST) + const tagFromConst = checkType(BindingTypes.SETUP_CONST) if (tagFromConst) { return context.inline ? // in inline mode, const setup bindings (e.g. imports) can be used as-is tagFromConst : `$setup[${JSON.stringify(tagFromConst)}]` } + const tagFromSetup = + checkType(BindingTypes.SETUP_LET) || + checkType(BindingTypes.SETUP_CONST_REF) + if (tagFromSetup) { + return context.inline + ? // setup scope bindings that may be refs need to be unrefed + `${context.helperString(UNREF)}(${tagFromSetup})` + : `$setup[${JSON.stringify(tagFromSetup)}]` + } } // 4. user component (resolve) diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 0a6cc8ef..9c33cf67 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -104,9 +104,12 @@ export function processExpression( const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw] if (inline) { // setup inline mode - if (type === BindingTypes.CONST) { + if (type === BindingTypes.SETUP_CONST) { return raw - } else if (type === BindingTypes.SETUP) { + } else if ( + type === BindingTypes.SETUP_CONST_REF || + type === BindingTypes.SETUP_LET + ) { return `${context.helperString(UNREF)}(${raw})` } else if (type === BindingTypes.PROPS) { // use __props which is generated by compileScript so in ts mode @@ -114,8 +117,12 @@ export function processExpression( return `__props.${raw}` } } else { - if (type === BindingTypes.CONST) { - // setup const binding in non-inline mode + if ( + type === BindingTypes.SETUP_LET || + type === BindingTypes.SETUP_CONST || + type === BindingTypes.SETUP_CONST_REF + ) { + // setup bindings in non-inline mode return `$setup.${raw}` } else if (type) { return `$${type}.${raw}` @@ -131,7 +138,9 @@ export function processExpression( const bailConstant = rawExp.indexOf(`(`) > -1 if (isSimpleIdentifier(rawExp)) { // const bindings exposed from setup - we know they never change - if (bindingMetadata[node.content] === BindingTypes.CONST) { + // 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 ( diff --git a/packages/compiler-sfc/__tests__/compileScript.spec.ts b/packages/compiler-sfc/__tests__/compileScript.spec.ts index 41771fd2..180b2473 100644 --- a/packages/compiler-sfc/__tests__/compileScript.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript.spec.ts @@ -1,3 +1,4 @@ +import { BindingTypes } from '@vue/compiler-dom/src' import { compileSFCScript as compile, assertCode } from './utils' describe('SFC compile `) - expect(bindings).toStrictEqual({ foo: 'props', bar: 'props' }) + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.PROPS + }) }) it('recognizes props object declaration', () => { @@ -702,10 +706,10 @@ describe('SFC analyze `) expect(bindings).toStrictEqual({ - foo: 'props', - bar: 'props', - baz: 'props', - qux: 'props' + foo: BindingTypes.PROPS, + bar: BindingTypes.PROPS, + baz: BindingTypes.PROPS, + qux: BindingTypes.PROPS }) }) @@ -723,7 +727,10 @@ describe('SFC analyze `) - expect(bindings).toStrictEqual({ foo: 'setup', bar: 'setup' }) + expect(bindings).toStrictEqual({ + foo: BindingTypes.SETUP_CONST_REF, + bar: BindingTypes.SETUP_CONST_REF + }) }) it('recognizes async setup return', () => { @@ -740,7 +747,10 @@ describe('SFC analyze `) - expect(bindings).toStrictEqual({ foo: 'setup', bar: 'setup' }) + expect(bindings).toStrictEqual({ + foo: BindingTypes.SETUP_CONST_REF, + bar: BindingTypes.SETUP_CONST_REF + }) }) it('recognizes data return', () => { @@ -757,7 +767,10 @@ describe('SFC analyze `) - expect(bindings).toStrictEqual({ foo: 'data', bar: 'data' }) + expect(bindings).toStrictEqual({ + foo: BindingTypes.DATA, + bar: BindingTypes.DATA + }) }) it('recognizes methods', () => { @@ -770,7 +783,7 @@ describe('SFC analyze `) - expect(bindings).toStrictEqual({ foo: 'options' }) + expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS }) }) it('recognizes computeds', () => { @@ -787,7 +800,10 @@ describe('SFC analyze `) - expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) + expect(bindings).toStrictEqual({ + foo: BindingTypes.OPTIONS, + bar: BindingTypes.OPTIONS + }) }) it('recognizes injections array declaration', () => { @@ -798,7 +814,10 @@ describe('SFC analyze `) - expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) + expect(bindings).toStrictEqual({ + foo: BindingTypes.OPTIONS, + bar: BindingTypes.OPTIONS + }) }) it('recognizes injections object declaration', () => { @@ -812,7 +831,10 @@ describe('SFC analyze `) - expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) + expect(bindings).toStrictEqual({ + foo: BindingTypes.OPTIONS, + bar: BindingTypes.OPTIONS + }) }) it('works for mixed bindings', () => { @@ -843,12 +865,12 @@ describe('SFC analyze `) expect(bindings).toStrictEqual({ - foo: 'options', - bar: 'props', - baz: 'setup', - qux: 'data', - quux: 'options', - quuz: 'options' + foo: BindingTypes.OPTIONS, + bar: BindingTypes.PROPS, + baz: BindingTypes.SETUP_CONST_REF, + qux: BindingTypes.DATA, + quux: BindingTypes.OPTIONS, + quuz: BindingTypes.OPTIONS }) }) @@ -864,7 +886,7 @@ describe('SFC analyze `) expect(bindings).toStrictEqual({ - foo: 'props' + foo: BindingTypes.PROPS }) }) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index aa44b99c..bdbd0b1d 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -159,11 +159,8 @@ export function compileScript( source: string } > = Object.create(null) - const setupBindings: Record< - string, - BindingTypes.SETUP | BindingTypes.CONST - > = Object.create(null) - const refBindings: Record = Object.create(null) + const setupBindings: Record = Object.create(null) + const refBindings: Record = Object.create(null) const refIdentifiers: Set = new Set() const enableRefSugar = options.refSugar !== false let defaultExport: Node | undefined @@ -311,7 +308,7 @@ export function compileScript( if (id.name[0] === '$') { error(`ref variable identifiers cannot start with $.`, id) } - refBindings[id.name] = setupBindings[id.name] = BindingTypes.SETUP + refBindings[id.name] = setupBindings[id.name] = BindingTypes.SETUP_CONST_REF refIdentifiers.add(id) } @@ -787,8 +784,8 @@ export function compileScript( } for (const [key, { source }] of Object.entries(userImports)) { bindingMetadata[key] = source.endsWith('.vue') - ? BindingTypes.CONST - : BindingTypes.SETUP + ? BindingTypes.SETUP_CONST + : BindingTypes.SETUP_CONST_REF } for (const key in setupBindings) { bindingMetadata[key] = setupBindings[key] @@ -966,8 +963,10 @@ function walkDeclaration( init!.type !== 'Identifier' && // const a = b init!.type !== 'CallExpression' && // const a = ref() init!.type !== 'MemberExpression') // const a = b.c - ? BindingTypes.CONST - : BindingTypes.SETUP + ? BindingTypes.SETUP_CONST + : isConst + ? BindingTypes.SETUP_CONST_REF + : BindingTypes.SETUP_LET } else if (id.type === 'ObjectPattern') { walkObjectPattern(id, bindings, isConst, isUseOptionsCall) } else if (id.type === 'ArrayPattern') { @@ -980,7 +979,7 @@ function walkDeclaration( ) { // export function foo() {} / export class Foo {} // export declarations must be named. - bindings[node.id!.name] = BindingTypes.CONST + bindings[node.id!.name] = BindingTypes.SETUP_CONST } } @@ -997,8 +996,10 @@ function walkObjectPattern( if (p.key === p.value) { // const { x } = ... bindings[p.key.name] = isUseOptionsCall - ? BindingTypes.CONST - : BindingTypes.SETUP + ? BindingTypes.SETUP_CONST + : isConst + ? BindingTypes.SETUP_CONST_REF + : BindingTypes.SETUP_LET } else { walkPattern(p.value, bindings, isConst, isUseOptionsCall) } @@ -1007,8 +1008,8 @@ function walkObjectPattern( // ...rest // argument can only be identifer when destructuring bindings[(p.argument as Identifier).name] = isConst - ? BindingTypes.CONST - : BindingTypes.SETUP + ? BindingTypes.SETUP_CONST + : BindingTypes.SETUP_LET } } } @@ -1032,13 +1033,15 @@ function walkPattern( ) { if (node.type === 'Identifier') { bindings[node.name] = isUseOptionsCall - ? BindingTypes.CONST - : BindingTypes.SETUP + ? BindingTypes.SETUP_CONST + : isConst + ? BindingTypes.SETUP_CONST_REF + : BindingTypes.SETUP_LET } else if (node.type === 'RestElement') { // argument can only be identifer when destructuring bindings[(node.argument as Identifier).name] = isConst - ? BindingTypes.CONST - : BindingTypes.SETUP + ? BindingTypes.SETUP_CONST + : BindingTypes.SETUP_LET } else if (node.type === 'ObjectPattern') { walkObjectPattern(node, bindings, isConst) } else if (node.type === 'ArrayPattern') { @@ -1046,8 +1049,10 @@ function walkPattern( } else if (node.type === 'AssignmentPattern') { if (node.left.type === 'Identifier') { bindings[node.left.name] = isUseOptionsCall - ? BindingTypes.CONST - : BindingTypes.SETUP + ? BindingTypes.SETUP_CONST + : isConst + ? BindingTypes.SETUP_CONST_REF + : BindingTypes.SETUP_LET } else { walkPattern(node.left, bindings, isConst) } @@ -1490,7 +1495,7 @@ function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata { for (const key of getObjectExpressionKeys(bodyItem.argument)) { bindings[key] = property.key.name === 'setup' - ? BindingTypes.SETUP + ? BindingTypes.SETUP_CONST_REF : BindingTypes.DATA } }