diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap
index c5f50be5d36..d349a3701aa 100644
--- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap
@@ -18,6 +18,66 @@ return { props, bar }
}"
`;
+exports[`defineProps > custom element retains the props type & default value & production mode 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Props {
+ foo?: number;
+ }
+
+export default /*#__PURE__*/_defineComponent({
+ __name: 'app.ce',
+ props: {
+ foo: { default: 5.5, type: Number }
+ },
+ setup(__props: any, { expose: __expose }) {
+ __expose();
+
+ const props = __props;
+
+return { props }
+}
+
+})"
+`;
+
+exports[`defineProps > custom element retains the props type & production mode 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+ __name: 'app.ce',
+ props: {
+ foo: {type: Number}
+ },
+ setup(__props: any, { expose: __expose }) {
+ __expose();
+
+ const props = __props
+
+return { props }
+}
+
+})"
+`;
+
+exports[`defineProps > custom element retains the props type w/ production mode 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+ __name: 'app.ce',
+ props: {
+ foo: {type: Number}
+ },
+ setup(__props: any, { expose: __expose }) {
+ __expose();
+
+ const props = __props
+
+return { props }
+}
+
+})"
+`;
+
exports[`defineProps > defineProps w/ runtime options 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
index 23d6a806f0d..7d46c4d2187 100644
--- a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
@@ -710,4 +710,35 @@ const props = defineProps({ foo: String })
'da-sh': BindingTypes.PROPS
})
})
+
+ // #8989
+ test('custom element retains the props type & production mode', () => {
+ const { content } = compile(
+ ``,
+ { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
+ { filename: 'app.ce.vue' }
+ )
+
+ expect(content).toMatch(`foo: {type: Number}`)
+ assertCode(content)
+ })
+
+ test('custom element retains the props type & default value & production mode', () => {
+ const { content } = compile(
+ ``,
+ { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
+ { filename: 'app.ce.vue' }
+ )
+ expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
+ assertCode(content)
+ })
})
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index 9a05d3b327f..91f52707d68 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -130,6 +130,10 @@ export interface SFCScriptCompileOptions {
* using it, disable this and switch to the [Vue Macros implementation](https://vue-macros.sxzz.moe/features/reactivity-transform.html).
*/
reactivityTransform?: boolean
+ /**
+ * Transform Vue SFCs into custom elements.
+ */
+ customElement?: boolean | ((filename: string) => boolean)
}
export interface ImportBinding {
diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts
index b05b8d910ee..3dc5d3ff0cd 100644
--- a/packages/compiler-sfc/src/script/context.ts
+++ b/packages/compiler-sfc/src/script/context.ts
@@ -12,6 +12,7 @@ import { TypeScope } from './resolveType'
export class ScriptCompileContext {
isJS: boolean
isTS: boolean
+ isCE = false
scriptAst: Program | null
scriptSetupAst: Program | null
@@ -95,6 +96,14 @@ export class ScriptCompileContext {
scriptSetupLang === 'ts' ||
scriptSetupLang === 'tsx'
+ const customElement = options.customElement
+ const filename = this.descriptor.filename
+ if (customElement) {
+ this.isCE =
+ typeof customElement === 'boolean'
+ ? customElement
+ : customElement(filename)
+ }
// resolve parser plugins
const plugins: ParserPlugin[] = resolveParserPlugins(
(scriptLang || scriptSetupLang)!,
diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts
index 0d35cdd776c..431f8d949a6 100644
--- a/packages/compiler-sfc/src/script/defineProps.ts
+++ b/packages/compiler-sfc/src/script/defineProps.ts
@@ -276,6 +276,17 @@ function genRuntimePropFromType(
defaultString
])} }`
} else {
+ // #8989 for custom element, should keep the type
+ if (ctx.isCE) {
+ if (defaultString) {
+ return `${finalKey}: ${`{ ${defaultString}, type: ${toRuntimeTypeString(
+ type
+ )} }`}`
+ } else {
+ return `${finalKey}: {type: ${toRuntimeTypeString(type)}}`
+ }
+ }
+
// production: checks are useless
return `${finalKey}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
}