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