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}` )}` }