diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap
index 35926709cec..721ef7eaa54 100644
--- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap
@@ -97,6 +97,44 @@ return () => {}
}"
`;
+exports[`sfc reactive props destructure > default values w/ runtime declaration & key is string 1`] = `
+"import { mergeDefaults as _mergeDefaults } from 'vue'
+
+export default {
+ props: _mergeDefaults(['foo', 'foo:bar'], {
+ foo: 1,
+ \\"foo:bar\\": 'foo-bar'
+}),
+ setup(__props) {
+
+
+
+return () => {}
+}
+
+}"
+`;
+
+exports[`sfc reactive props destructure > default values w/ type declaration & key is string 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+ props: {
+ foo: { type: Number, required: true, default: 1 },
+ bar: { type: Number, required: true, default: 2 },
+ \\"foo:bar\\": { type: String, required: true, default: 'foo-bar' },
+ \\"onUpdate:modelValue\\": { type: Function, required: true }
+ },
+ setup(__props: any) {
+
+
+
+return () => {}
+}
+
+})"
+`;
+
exports[`sfc reactive props destructure > default values w/ type declaration 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
diff --git a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
index b8912092afd..a2941872fd2 100644
--- a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
@@ -106,6 +106,28 @@ describe('sfc reactive props destructure', () => {
})`)
assertCode(content)
})
+ test('default values w/ runtime declaration & key is string', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ __propsAliases: {
+ fooBar: 'foo:bar'
+ },
+ foo: BindingTypes.PROPS,
+ 'foo:bar': BindingTypes.PROPS,
+ fooBar: BindingTypes.PROPS_ALIASED
+ })
+
+ expect(content).toMatch(`
+ props: _mergeDefaults(['foo', 'foo:bar'], {
+ foo: 1,
+ "foo:bar": 'foo-bar'
+}),`)
+ assertCode(content)
+ })
test('default values w/ type declaration', () => {
const { content } = compile(`
@@ -123,6 +145,37 @@ describe('sfc reactive props destructure', () => {
assertCode(content)
})
+ test('default values w/ type declaration & key is string', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ __propsAliases: {
+ fooBar: 'foo:bar'
+ },
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ 'foo:bar': BindingTypes.PROPS,
+ fooBar: BindingTypes.PROPS_ALIASED,
+ 'onUpdate:modelValue': BindingTypes.PROPS
+ })
+ expect(content).toMatch(`
+ props: {
+ foo: { type: Number, required: true, default: 1 },
+ bar: { type: Number, required: true, default: 2 },
+ "foo:bar": { type: String, required: true, default: 'foo-bar' },
+ "onUpdate:modelValue": { type: Function, required: true }
+ },`)
+ assertCode(content)
+ })
+
test('default values w/ type declaration, prod mode', () => {
const { content } = compile(
`
diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts
index 2a0882284fb..38968527c9a 100644
--- a/packages/compiler-sfc/src/script/defineProps.ts
+++ b/packages/compiler-sfc/src/script/defineProps.ts
@@ -133,10 +133,11 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
const defaults: string[] = []
for (const key in ctx.propsDestructuredBindings) {
const d = genDestructuredDefaultValue(ctx, key)
+ const finalKey = getEscapedKey(key)
if (d)
defaults.push(
- `${key}: ${d.valueString}${
- d.needSkipFactory ? `, __skip_${key}: true` : ``
+ `${finalKey}: ${d.valueString}${
+ d.needSkipFactory ? `, __skip_${finalKey}: true` : ``
}`
)
}
@@ -248,8 +249,9 @@ function genRuntimePropFromType(
}
}
+ const finalKey = getEscapedKey(key)
if (!ctx.options.isProd) {
- return `${key}: { ${concatStrings([
+ return `${finalKey}: { ${concatStrings([
`type: ${toRuntimeTypeString(type)}`,
`required: ${required}`,
skipCheck && 'skipCheck: true',
@@ -265,13 +267,13 @@ function genRuntimePropFromType(
// #4783 for boolean, should keep the type
// #7111 for function, if default value exists or it's not static, should keep it
// in production
- return `${key}: { ${concatStrings([
+ return `${finalKey}: { ${concatStrings([
`type: ${toRuntimeTypeString(type)}`,
defaultString
])} }`
} else {
// production: checks are useless
- return `${key}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
+ return `${finalKey}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
}
}
@@ -362,3 +364,14 @@ function inferValueType(node: Node): string | undefined {
return 'Function'
}
}
+
+/**
+ * key may contain symbols
+ * e.g. onUpdate:modelValue -> "onUpdate:modelValue"
+ */
+export const escapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
+function getEscapedKey(key: string) {
+ return escapeSymbolsRE.test(key)
+ ? JSON.stringify(key)
+ : key
+}
diff --git a/packages/compiler-sfc/src/style/cssVars.ts b/packages/compiler-sfc/src/style/cssVars.ts
index f232d09695d..a3e2104999a 100644
--- a/packages/compiler-sfc/src/style/cssVars.ts
+++ b/packages/compiler-sfc/src/style/cssVars.ts
@@ -8,6 +8,7 @@ import {
BindingMetadata
} from '@vue/compiler-dom'
import { SFCDescriptor } from '../parse'
+import { escapeSymbolsRE } from '../script/defineProps'
import { PluginCreator } from 'postcss'
import hash from 'hash-sum'
@@ -32,7 +33,7 @@ function genVarName(id: string, raw: string, isProd: boolean): string {
} else {
// escape ASCII Punctuation & Symbols
return `${id}-${raw.replace(
- /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g,
+ escapeSymbolsRE,
s => `\\${s}`
)}`
}