From 4253a57f1703a7f1ac701d77e0a235689203461d Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 24 Apr 2024 18:21:57 +0800 Subject: [PATCH] fix(defineModel): align prod mode runtime type generation with defineProps close #10769 --- .../__snapshots__/defineModel.spec.ts.snap | 40 ++++++++++++ .../compileScript/defineModel.spec.ts | 28 +++++++++ .../compiler-sfc/src/script/defineModel.ts | 63 ++++++++++--------- 3 files changed, 100 insertions(+), 31 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineModel.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineModel.spec.ts.snap index 6e9967fd011..6fe26a63902 100644 --- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineModel.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineModel.spec.ts.snap @@ -226,3 +226,43 @@ return { modelValue, fn, fnWithDefault, str, optional } })" `; + +exports[`defineModel() > w/ types, production mode, boolean + multiple types 1`] = ` +"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + props: { + "modelValue": { type: [Boolean, String, Object] }, + "modelModifiers": {}, + }, + emits: ["update:modelValue"], + setup(__props, { expose: __expose }) { + __expose(); + + const modelValue = _useModel(__props, "modelValue") + +return { modelValue } +} + +})" +`; + +exports[`defineModel() > w/ types, production mode, function + runtime opts + multiple types 1`] = ` +"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + props: { + "modelValue": { type: [Number, Function], ...{ default: () => 1 } }, + "modelModifiers": {}, + }, + emits: ["update:modelValue"], + setup(__props, { expose: __expose }) { + __expose(); + + const modelValue = _useModel number)>(__props, "modelValue") + +return { modelValue } +} + +})" +`; diff --git a/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts index bd048a847e4..4550aa5c4c0 100644 --- a/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts @@ -161,6 +161,34 @@ describe('defineModel()', () => { }) }) + test('w/ types, production mode, boolean + multiple types', () => { + const { content } = compile( + ` + + `, + { isProd: true }, + ) + assertCode(content) + expect(content).toMatch('"modelValue": { type: [Boolean, String, Object] }') + }) + + test('w/ types, production mode, function + runtime opts + multiple types', () => { + const { content } = compile( + ` + + `, + { isProd: true }, + ) + assertCode(content) + expect(content).toMatch( + '"modelValue": { type: [Number, Function], ...{ default: () => 1 } }', + ) + }) + test('get / set transformers', () => { const { content } = compile( ` diff --git a/packages/compiler-sfc/src/script/defineModel.ts b/packages/compiler-sfc/src/script/defineModel.ts index e5e2ed0e53f..05082800284 100644 --- a/packages/compiler-sfc/src/script/defineModel.ts +++ b/packages/compiler-sfc/src/script/defineModel.ts @@ -1,12 +1,7 @@ import type { LVal, Node, TSType } from '@babel/types' import type { ScriptCompileContext } from './context' import { inferRuntimeType } from './resolveType' -import { - UNKNOWN_TYPE, - concatStrings, - isCallOf, - toRuntimeTypeString, -} from './utils' +import { UNKNOWN_TYPE, isCallOf, toRuntimeTypeString } from './utils' import { BindingTypes, unwrapTSNode } from '@vue/compiler-dom' export const DEFINE_MODEL = 'defineModel' @@ -124,44 +119,50 @@ export function genModelProps(ctx: ScriptCompileContext) { const isProd = !!ctx.options.isProd let modelPropsDecl = '' - for (const [name, { type, options }] of Object.entries(ctx.modelDecls)) { + for (const [name, { type, options: runtimeOptions }] of Object.entries( + ctx.modelDecls, + )) { let skipCheck = false - + let codegenOptions = `` let runtimeTypes = type && inferRuntimeType(ctx, type) if (runtimeTypes) { const hasBoolean = runtimeTypes.includes('Boolean') + const hasFunction = runtimeTypes.includes('Function') const hasUnknownType = runtimeTypes.includes(UNKNOWN_TYPE) - if (isProd || hasUnknownType) { - runtimeTypes = runtimeTypes.filter( - t => - t === 'Boolean' || - (hasBoolean && t === 'String') || - (t === 'Function' && options), - ) + if (hasUnknownType) { + if (hasBoolean || hasFunction) { + runtimeTypes = runtimeTypes.filter(t => t !== UNKNOWN_TYPE) + skipCheck = true + } else { + runtimeTypes = ['null'] + } + } - skipCheck = !isProd && hasUnknownType && runtimeTypes.length > 0 + if (!isProd) { + codegenOptions = + `type: ${toRuntimeTypeString(runtimeTypes)}` + + (skipCheck ? ', skipCheck: true' : '') + } else if (hasBoolean || (runtimeOptions && hasFunction)) { + // preserve types if contains boolean, or + // function w/ runtime options that may contain default + codegenOptions = `type: ${toRuntimeTypeString(runtimeTypes)}` + } else { + // able to drop types in production } } - let runtimeType = - (runtimeTypes && - runtimeTypes.length > 0 && - toRuntimeTypeString(runtimeTypes)) || - undefined - - const codegenOptions = concatStrings([ - runtimeType && `type: ${runtimeType}`, - skipCheck && 'skipCheck: true', - ]) - let decl: string - if (runtimeType && options) { + if (codegenOptions && runtimeOptions) { decl = ctx.isTS - ? `{ ${codegenOptions}, ...${options} }` - : `Object.assign({ ${codegenOptions} }, ${options})` + ? `{ ${codegenOptions}, ...${runtimeOptions} }` + : `Object.assign({ ${codegenOptions} }, ${runtimeOptions})` + } else if (codegenOptions) { + decl = `{ ${codegenOptions} }` + } else if (runtimeOptions) { + decl = runtimeOptions } else { - decl = options || (runtimeType ? `{ ${codegenOptions} }` : '{}') + decl = `{}` } modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},`