diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap index c6123b0767b..328f242010c 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap @@ -78,13 +78,34 @@ return (_ctx, _cache) => { }" `; -exports[`sfc props transform > default values w/ runtime declaration 1`] = ` +exports[`sfc props transform > default values w/ array runtime declaration 1`] = ` "import { mergeDefaults as _mergeDefaults } from 'vue' export default { - props: _mergeDefaults(['foo', 'bar'], { + props: _mergeDefaults(['foo', 'bar', 'baz'], { foo: 1, - bar: () => ({}) + bar: () => ({}), + func: () => {}, __skip_func: true +}), + setup(__props) { + + + +return () => {} +} + +}" +`; + +exports[`sfc props transform > default values w/ object runtime declaration 1`] = ` +"import { mergeDefaults as _mergeDefaults } from 'vue' + +export default { + props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, { + foo: 1, + bar: () => ({}), + func: () => {}, __skip_func: true, + ext: x, __skip_ext: true }), setup(__props) { @@ -102,7 +123,8 @@ exports[`sfc props transform > default values w/ type declaration 1`] = ` export default /*#__PURE__*/_defineComponent({ props: { foo: { type: Number, required: false, default: 1 }, - bar: { type: Object, required: false, default: () => ({}) } + bar: { type: Object, required: false, default: () => ({}) }, + func: { type: Function, required: false, default: () => {} } }, setup(__props: any) { @@ -124,7 +146,7 @@ export default /*#__PURE__*/_defineComponent({ baz: null, boola: { type: Boolean }, boolb: { type: [Boolean, Number] }, - func: { type: Function, default: () => (() => {}) } + func: { type: Function, default: () => {} } }, setup(__props: any) { diff --git a/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts b/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts index 5401a9e0305..9fe711fbc3d 100644 --- a/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts @@ -69,17 +69,40 @@ describe('sfc props transform', () => { }) }) - test('default values w/ runtime declaration', () => { + test('default values w/ array runtime declaration', () => { const { content } = compile(` `) // literals can be used as-is, non-literals are always returned from a // function - expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar'], { + // functions need to be marked with a skip marker + expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar', 'baz'], { foo: 1, - bar: () => ({}) + bar: () => ({}), + func: () => {}, __skip_func: true +})`) + assertCode(content) + }) + + test('default values w/ object runtime declaration', () => { + const { content } = compile(` + + `) + // literals can be used as-is, non-literals are always returned from a + // function + // functions need to be marked with a skip marker since we cannot always + // safely infer whether runtime type is Function (e.g. if the runtime decl + // is imported, or spreads another object) + expect(content) + .toMatch(`props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, { + foo: 1, + bar: () => ({}), + func: () => {}, __skip_func: true, + ext: x, __skip_ext: true })`) assertCode(content) }) @@ -87,14 +110,15 @@ describe('sfc props transform', () => { test('default values w/ type declaration', () => { const { content } = compile(` `) // literals can be used as-is, non-literals are always returned from a // function expect(content).toMatch(`props: { foo: { type: Number, required: false, default: 1 }, - bar: { type: Object, required: false, default: () => ({}) } + bar: { type: Object, required: false, default: () => ({}) }, + func: { type: Function, required: false, default: () => {} } }`) assertCode(content) }) @@ -116,7 +140,7 @@ describe('sfc props transform', () => { baz: null, boola: { type: Boolean }, boolb: { type: [Boolean, Number] }, - func: { type: Function, default: () => (() => {}) } + func: { type: Function, default: () => {} } }`) assertCode(content) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index af54641f4de..d9c59ab665e 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -862,9 +862,11 @@ export function compileScript( ${keys .map(key => { let defaultString: string | undefined - const destructured = genDestructuredDefaultValue(key) + const destructured = genDestructuredDefaultValue(key, props[key].type) if (destructured) { - defaultString = `default: ${destructured}` + defaultString = `default: ${destructured.valueString}${ + destructured.needSkipFactory ? `, skipFactory: true` : `` + }` } else if (hasStaticDefaults) { const prop = propsRuntimeDefaults!.properties.find(node => { if (node.type === 'SpreadElement') return false @@ -925,15 +927,38 @@ export function compileScript( return `\n props: ${propsDecls},` } - function genDestructuredDefaultValue(key: string): string | undefined { + function genDestructuredDefaultValue( + key: string, + inferredType?: string[] + ): + | { + valueString: string + needSkipFactory: boolean + } + | undefined { const destructured = propsDestructuredBindings[key] - if (destructured && destructured.default) { + const defaultVal = destructured && destructured.default + if (defaultVal) { const value = scriptSetup!.content.slice( - destructured.default.start!, - destructured.default.end! + defaultVal.start!, + defaultVal.end! ) - const isLiteral = isLiteralNode(destructured.default) - return isLiteral ? value : `() => (${value})` + const unwrapped = unwrapTSNode(defaultVal) + // If the default value is a function or is an identifier referencing + // external value, skip factory wrap. This is needed when using + // destructure w/ runtime declaration since we cannot safely infer + // whether tje expected runtime prop type is `Function`. + const needSkipFactory = + !inferredType && + (isFunctionType(unwrapped) || unwrapped.type === 'Identifier') + const needFactoryWrap = + !needSkipFactory && + !isLiteralNode(unwrapped) && + !inferredType?.includes('Function') + return { + valueString: needFactoryWrap ? `() => (${value})` : value, + needSkipFactory + } } } @@ -1693,7 +1718,12 @@ export function compileScript( const defaults: string[] = [] for (const key in propsDestructuredBindings) { const d = genDestructuredDefaultValue(key) - if (d) defaults.push(`${key}: ${d}`) + if (d) + defaults.push( + `${key}: ${d.valueString}${ + d.needSkipFactory ? `, __skip_${key}: true` : `` + }` + ) } if (defaults.length) { declCode = `${helper( diff --git a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts index aa4e04e80f4..65fb325f044 100644 --- a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts +++ b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts @@ -114,6 +114,17 @@ describe('SFC