From 6c9ac2db32984e51638860adb12dbb73926ae366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Fri, 31 Mar 2023 22:21:20 +0800 Subject: [PATCH 01/26] wip: defineModel --- packages/compiler-sfc/src/compileScript.ts | 6 ++++ packages/dts-test/setupHelpers.test-d.ts | 25 +++++++++++++- packages/runtime-core/src/apiSetupHelpers.ts | 33 +++++++++++++++++-- packages/runtime-core/src/index.ts | 1 + .../types/scriptSetupHelpers.d.ts | 2 ++ 5 files changed, 64 insertions(+), 3 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 8d22d7e1348..d980835044f 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -151,6 +151,11 @@ type EmitsDeclType = FromNormalScript< TSFunctionType | TSTypeLiteral | TSInterfaceBody > +export interface ModelDecl { + type: TSType + option: Node +} + /** * Compile ` + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead' + ) + }) }) test('defineExpose()', () => { From f72c1f26c20c57bc4d32cd351f82a7d2b12f6f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 5 Apr 2023 00:12:59 +0800 Subject: [PATCH 03/26] wip --- .../__snapshots__/compileScript.spec.ts.snap | 86 +++++++ .../__tests__/compileScript.spec.ts | 107 ++++++++ packages/compiler-sfc/src/compileScript.ts | 237 +++++++++++++----- .../__tests__/apiSetupHelpers.spec.ts | 44 +++- packages/runtime-core/src/apiSetupHelpers.ts | 83 +++++- packages/runtime-core/src/index.ts | 1 + packages/sfc-playground/src/App.vue | 3 +- 7 files changed, 491 insertions(+), 70 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 818ea02e303..20f78db165a 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -653,6 +653,48 @@ return { } }" `; +exports[`SFC compile + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch('props: {\n "modelValue": { required: true }') + expect(content).toMatch('"count": {},') + expect(content).toMatch('emits: ["update:modelValue", "update:count"],') + expect(content).toMatch( + `const modelValue = _useModel("modelValue", { required: true })` + ) + expect(content).toMatch(`const c = _useModel("count")`) + expect(content).toMatch(`return { modelValue, c }`) + expect(content).not.toMatch('defineModel') + + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + count: BindingTypes.PROPS, + c: BindingTypes.SETUP_REF + }) + }) + + test('w/ defineProps and defineEmits', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch(`props: _mergeModels({ foo: String }`) + expect(content).toMatch(`"modelValue": { default: 0 }`) + expect(content).toMatch( + `const count = _useModel("modelValue", { default: 0 })` + ) + expect(content).not.toMatch('defineModel') + expect(bindings).toStrictEqual({ + count: BindingTypes.SETUP_REF, + foo: BindingTypes.PROPS, + modelValue: BindingTypes.PROPS + }) + }) + }) + test(' + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch('"modelValue": [Boolean, String]') + expect(content).toMatch('"count": Number') + expect(content).toMatch('emits: ["update:modelValue", "update:count"]') + expect(content).toMatch(`const modelValue = _useModel("modelValue")`) + expect(content).toMatch(`const count = _useModel("count")`) + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + count: BindingTypes.SETUP_REF + }) + }) + + test('w/ production mode', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true, isProd: true } + ) + assertCode(content) + expect(content).toMatch('"modelValue": Boolean') + expect(content).toMatch('"fn": Function') + expect(content).toMatch('"str": {}') + expect(content).toMatch( + 'emits: ["update:modelValue", "update:fn", "update:str"]' + ) + expect(content).toMatch(`const modelValue = _useModel("modelValue")`) + expect(content).toMatch(`const fn = _useModel("fn")`) + expect(content).toMatch(`const str = _useModel("str")`) + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + fn: BindingTypes.SETUP_REF, + str: BindingTypes.SETUP_REF + }) + }) + }) + test('runtime Enum', () => { const { content, bindings } = compile( ` `, { defineModel: true } ) assertCode(content) - expect(content).toMatch('"modelValue": [Boolean, String]') - expect(content).toMatch('"count": Number') - expect(content).toMatch('emits: ["update:modelValue", "update:count"]') + expect(content).toMatch( + '"modelValue": { type: [Boolean, String], required: true }' + ) + expect(content).toMatch('"count": { type: Number, required: true }') + expect(content).toMatch( + '"disabled": { type: Number, required: true, ...{ required: false } },' + ) + expect(content).toMatch( + 'emits: ["update:modelValue", "update:count", "update:disabled"]' + ) + expect(content).toMatch(`const modelValue = _useModel("modelValue")`) + expect(content).toMatch(`const count = _useModel("count")`) + expect(content).toMatch(`const disabled = _useModel("disabled")`) + expect(content).toMatch(`const count = _useModel("count")`) expect(bindings).toStrictEqual({ modelValue: BindingTypes.SETUP_REF, - count: BindingTypes.SETUP_REF + count: BindingTypes.SETUP_REF, + disabled: BindingTypes.SETUP_REF }) }) @@ -1795,6 +1808,7 @@ const emit = defineEmits(['a', 'b']) const modelValue = defineModel() const fn = defineModel<() => void>('fn') const str = defineModel('str') + const optional = defineModel('optional', { required: false }) `, { defineModel: true, isProd: true } @@ -1802,9 +1816,10 @@ const emit = defineEmits(['a', 'b']) assertCode(content) expect(content).toMatch('"modelValue": Boolean') expect(content).toMatch('"fn": Function') - expect(content).toMatch('"str": {}') + expect(content).toMatch('"str": { }') + expect(content).toMatch('"optional": { required: false }') expect(content).toMatch( - 'emits: ["update:modelValue", "update:fn", "update:str"]' + 'emits: ["update:modelValue", "update:fn", "update:str", "update:optional"]' ) expect(content).toMatch(`const modelValue = _useModel("modelValue")`) expect(content).toMatch(`const fn = _useModel("fn")`) @@ -1812,7 +1827,8 @@ const emit = defineEmits(['a', 'b']) expect(bindings).toStrictEqual({ modelValue: BindingTypes.SETUP_REF, fn: BindingTypes.SETUP_REF, - str: BindingTypes.SETUP_REF + str: BindingTypes.SETUP_REF, + optional: BindingTypes.SETUP_REF }) }) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 5fd8c861161..aaf71350f89 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -663,14 +663,7 @@ export function compileScript( s.overwrite( startOffset + node.start!, startOffset + node.end!, - `${helper('useModel')}(${JSON.stringify(modelName)}${ - options - ? `, ${s.slice( - startOffset + options.start!, - startOffset + options.end! - )}` - : '' - })` + `${helper('useModel')}(${JSON.stringify(modelName)})` ) return true @@ -946,9 +939,8 @@ export function compileScript( function genRuntimeProps() { function genPropsFromTS() { const keys = Object.keys(typeDeclaredProps) - if (!keys.length) { - return - } + if (!keys.length) return + const hasStaticDefaults = hasStaticWithDefaults() const scriptSetupSource = scriptSetup!.content let propsDecls = `{ @@ -1043,13 +1035,24 @@ export function compileScript( undefined let decl: string - if (runtimeType && options) { - decl = isTS - ? `{ type: ${runtimeType}, ...${options} }` - : `Object.assign({ type: ${runtimeType} }, ${options})` + + if (runtimeType && isProd && !options) { + decl = runtimeType } else { - decl = runtimeType || options || '{}' + const defaultOptions: string[] = [] + if (runtimeType) defaultOptions.push(`type: ${runtimeType}`) + if (!isProd) defaultOptions.push('required: true') + decl = defaultOptions.join(', ') + + if (decl && options) { + decl = isTS + ? `{ ${decl}, ...${options} }` + : `Object.assign({ ${decl} }, ${options})` + } else { + decl = options || `{ ${decl} }` + } } + modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},` } return `{${modelPropsDecl}\n }` @@ -1451,8 +1454,7 @@ export function compileScript( processDefineEmits(expr) || processDefineOptions(expr) || processWithDefaults(expr) || - processDefineSlots(expr) || - processDefineModel(expr) + processDefineSlots(expr) ) { s.remove(node.start! + startOffset, node.end! + startOffset) } else if (processDefineExpose(expr)) { @@ -1463,6 +1465,8 @@ export function compileScript( callee.end! + startOffset, '__expose' ) + } else { + processDefineModel(expr) } } @@ -1917,11 +1921,11 @@ export function compileScript( emitsDecl = genRuntimeEmits(typeDeclaredEmits) } if (hasDefineModelCall) { - let modelEmitsDecl = stringifyStringArray( - Object.keys(modelDecls).map(n => `update:${n}`) - ) + let modelEmitsDecl = `[${Object.keys(modelDecls) + .map(n => JSON.stringify(`update:${n}`)) + .join(', ')}]` emitsDecl = emitsDecl - ? `merge(${emitsDecl}, ${modelEmitsDecl})` + ? `${helper('mergeModels')}(${emitsDecl}, ${modelEmitsDecl})` : modelEmitsDecl } if (emitsDecl) runtimeOptions += `\n emits: ${emitsDecl},` @@ -2493,12 +2497,12 @@ function extractEventNames( } } -function stringifyStringArray(strs: string[]) { - return `[${strs.map(s => JSON.stringify(s)).join(', ')}]` -} - function genRuntimeEmits(emits: Set) { - return emits.size ? stringifyStringArray(Array.from(emits)) : `` + return emits.size + ? `[${Array.from(emits) + .map(k => JSON.stringify(k)) + .join(', ')}]` + : `` } function canNeverBeRef(node: Node, userReactiveImport?: string): boolean { diff --git a/packages/dts-test/setupHelpers.test-d.ts b/packages/dts-test/setupHelpers.test-d.ts index 14229d168c3..3eb258df050 100644 --- a/packages/dts-test/setupHelpers.test-d.ts +++ b/packages/dts-test/setupHelpers.test-d.ts @@ -205,21 +205,25 @@ describe('defineSlots', () => { }) describe('defineModel', () => { + // overload 1 + const modelValueRequired = defineModel({ required: false }) + expectType>(modelValueRequired) + + // overload 2 const modelValue = defineModel() - expectType>(modelValue) + expectType>(modelValue) modelValue.value = 'new value' - const modelValueRequired = defineModel({ required: true }) - expectType>(modelValueRequired) - const modelValueDefault = defineModel({ default: true }) expectType>(modelValueDefault) - const count = defineModel('count') - expectType>(count) + // overload 3 + const countRequired = defineModel('count', { required: false }) + expectType>(countRequired) - const countRequired = defineModel('count', { required: true }) - expectType>(countRequired) + // overload 4 + const count = defineModel('count') + expectType>(count) const countDefault = defineModel('count', { default: 1 }) expectType>(countDefault) diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts index b402b782aa8..6122be8a935 100644 --- a/packages/runtime-core/src/apiSetupHelpers.ts +++ b/packages/runtime-core/src/apiSetupHelpers.ts @@ -3,7 +3,8 @@ import { isPromise, isFunction, Prettify, - UnionToIntersection + UnionToIntersection, + isObject } from '@vue/shared' import { getCurrentInstance, @@ -22,7 +23,8 @@ import { import { ComponentPropsOptions, ComponentObjectPropsOptions, - ExtractPropTypes + ExtractPropTypes, + NormalizedProps } from './componentProps' import { warn } from './warning' import { SlotsType, TypedSlots } from './componentSlots' @@ -210,26 +212,19 @@ export function defineSlots< } export function defineModel( - options: { required: true } & Record -): WritableComputedRef -export function defineModel( - options: { default: any } & Record -): WritableComputedRef -export function defineModel( - options?: Record + options: { required: false } & Record ): WritableComputedRef export function defineModel( - name: string, - options: { required: true } & Record + options?: Record ): WritableComputedRef export function defineModel( name: string, - options: { default: any } & Record -): WritableComputedRef + options: { required: false } & Record +): WritableComputedRef export function defineModel( name: string, options?: Record -): WritableComputedRef +): WritableComputedRef export function defineModel(): any { if (__DEV__) { warnRuntimeUsage('defineModel') @@ -297,21 +292,18 @@ export function useAttrs(): SetupContext['attrs'] { return getContext().attrs } -export function useModel( - name: string, - options: { - passive?: boolean - deep?: boolean - } = {} -): WritableComputedRef { - const { passive, deep } = options +export function useModel(name: string): WritableComputedRef { const i = getCurrentInstance()! if (__DEV__ && !i) { warn(`useModel() called without active instance.`) return null as any } - if (passive) { + const options = (i.propsOptions[0] as NormalizedProps)[name] + if ( + isObject(options) && + (options.required === false || 'default' in options) + ) { const proxy = ref(i.props[name]) watch( @@ -319,15 +311,11 @@ export function useModel( v => (proxy.value = v) ) - watch( - proxy, - value => { - if (value !== i.props[name] || deep) { - i.emit(`update:${name}`, value) - } - }, - { deep } - ) + watch(proxy, value => { + if (value !== i.props[name]) { + i.emit(`update:${name}`, value) + } + }) return proxy } else { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index c54f0fac99e..2001706057b 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -73,6 +73,7 @@ export { defineSlots, defineModel, withDefaults, + useModel, // internal mergeDefaults, mergeModelsOptions, From 2f66129bd63581791e172e067a9c158cf7959c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 6 Apr 2023 01:43:48 +0800 Subject: [PATCH 05/26] test: add case --- .../__snapshots__/compileScript.spec.ts.snap | 24 +++++++++++++++++-- .../__tests__/compileScript.spec.ts | 24 ++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 515574a5ada..eb211fd72ce 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -674,6 +674,26 @@ return { modelValue, c } }" `; +exports[`SFC compile + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch(`_mergeModels(['foo', 'bar'], {`) + expect(content).toMatch(`"count": { required: true }`) + expect(content).toMatch(`const count = _useModel("count")`) + expect(content).not.toMatch('defineModel') + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.PROPS, + count: BindingTypes.SETUP_REF + }) + }) }) test(' @@ -1837,11 +1838,14 @@ const emit = defineEmits(['a', 'b']) ) assertCode(content) expect(content).toMatch('"modelValue": Boolean') - expect(content).toMatch('"fn": Function') + expect(content).toMatch('"fn": { }') + expect(content).toMatch( + '"fnWithDefault": { type: Function, ...{ default: () => null } },' + ) expect(content).toMatch('"str": { }') expect(content).toMatch('"optional": { required: false }') expect(content).toMatch( - 'emits: ["update:modelValue", "update:fn", "update:str", "update:optional"]' + 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]' ) expect(content).toMatch(`const modelValue = _useModel("modelValue")`) expect(content).toMatch(`const fn = _useModel("fn")`) @@ -1849,6 +1853,7 @@ const emit = defineEmits(['a', 'b']) expect(bindings).toStrictEqual({ modelValue: BindingTypes.SETUP_REF, fn: BindingTypes.SETUP_REF, + fnWithDefault: BindingTypes.SETUP_REF, str: BindingTypes.SETUP_REF, optional: BindingTypes.SETUP_REF }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index aaf71350f89..2f543469e22 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1025,7 +1025,7 @@ export function compileScript( let runtimeTypes = type && inferRuntimeType(type, declaredTypes) if (runtimeTypes && isProd) { runtimeTypes = runtimeTypes.filter( - el => el === 'Boolean' || el === 'Function' + el => el === 'Boolean' || (el === 'Function' && options) ) } const runtimeType = From 12b9a910293653e26a26550341bcdc7bebce2587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 6 Apr 2023 02:22:45 +0800 Subject: [PATCH 07/26] chore: rename --- packages/compiler-sfc/src/compileScript.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 2f543469e22..1a59d6e7cce 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1039,10 +1039,10 @@ export function compileScript( if (runtimeType && isProd && !options) { decl = runtimeType } else { - const defaultOptions: string[] = [] - if (runtimeType) defaultOptions.push(`type: ${runtimeType}`) - if (!isProd) defaultOptions.push('required: true') - decl = defaultOptions.join(', ') + const pairs: string[] = [] + if (runtimeType) pairs.push(`type: ${runtimeType}`) + if (!isProd) pairs.push('required: true') + decl = pairs.join(', ') if (decl && options) { decl = isTS From 45d078435e4028303e49c007c983abe313d26ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 6 Apr 2023 02:27:52 +0800 Subject: [PATCH 08/26] refactor: re-organize --- packages/compiler-sfc/src/compileScript.ts | 53 ++++++++++++---------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 1a59d6e7cce..e7f79f44a25 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1912,22 +1912,7 @@ export function compileScript( let propsDecl = genRuntimeProps() if (propsDecl) runtimeOptions += `\n props: ${propsDecl},` - let emitsDecl = '' - if (emitsRuntimeDecl) { - emitsDecl = scriptSetup.content - .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!) - .trim() - } else if (emitsTypeDecl) { - emitsDecl = genRuntimeEmits(typeDeclaredEmits) - } - if (hasDefineModelCall) { - let modelEmitsDecl = `[${Object.keys(modelDecls) - .map(n => JSON.stringify(`update:${n}`)) - .join(', ')}]` - emitsDecl = emitsDecl - ? `${helper('mergeModels')}(${emitsDecl}, ${modelEmitsDecl})` - : modelEmitsDecl - } + let emitsDecl = genRuntimeEmits() if (emitsDecl) runtimeOptions += `\n emits: ${emitsDecl},` let definedOptions = '' @@ -2008,6 +1993,34 @@ export function compileScript( scriptAst: scriptAst?.body, scriptSetupAst: scriptSetupAst?.body } + + function genRuntimeEmits() { + function genEmitsFromTS() { + return typeDeclaredEmits.size + ? `[${Array.from(typeDeclaredEmits) + .map(k => JSON.stringify(k)) + .join(', ')}]` + : `` + } + + let emitsDecl = '' + if (emitsRuntimeDecl) { + emitsDecl = scriptSetup!.content + .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!) + .trim() + } else if (emitsTypeDecl) { + emitsDecl = genEmitsFromTS() + } + if (hasDefineModelCall) { + let modelEmitsDecl = `[${Object.keys(modelDecls) + .map(n => JSON.stringify(`update:${n}`)) + .join(', ')}]` + emitsDecl = emitsDecl + ? `${helper('mergeModels')}(${emitsDecl}, ${modelEmitsDecl})` + : modelEmitsDecl + } + return emitsDecl + } } function registerBinding( @@ -2497,14 +2510,6 @@ function extractEventNames( } } -function genRuntimeEmits(emits: Set) { - return emits.size - ? `[${Array.from(emits) - .map(k => JSON.stringify(k)) - .join(', ')}]` - : `` -} - function canNeverBeRef(node: Node, userReactiveImport?: string): boolean { if (isCallOf(node, userReactiveImport)) { return true From 87c4e0036fcf16ed69c4b7cef9f96a6561f5affe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 6 Apr 2023 03:01:32 +0800 Subject: [PATCH 09/26] test: add cases --- .../__tests__/apiSetupHelpers.spec.ts | 118 +++++++++++++++++- packages/runtime-core/src/apiSetupHelpers.ts | 5 +- 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts index f69c3703e93..de26f3b999f 100644 --- a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts +++ b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts @@ -13,7 +13,9 @@ import { Suspense, computed, ComputedRef, - shallowReactive + shallowReactive, + nextTick, + ref } from '@vue/runtime-test' import { defineEmits, @@ -25,7 +27,8 @@ import { mergeDefaults, withAsyncContext, createPropsRestProxy, - mergeModelsOptions + mergeModelsOptions, + useModel } from '../src/apiSetupHelpers' describe('SFC `, { defineModel: true } @@ -1802,21 +1803,23 @@ const emit = defineEmits(['a', 'b']) expect(content).toMatch('"modelValue": { type: [Boolean, String] }') expect(content).toMatch('"count": { type: Number }') expect(content).toMatch( - '"disabled": { type: Number, ...{ required: false } },' + '"disabled": { type: Number, ...{ required: false } }' ) + expect(content).toMatch('"any": { type: Boolean, skipCheck: true }') expect(content).toMatch( - 'emits: ["update:modelValue", "update:count", "update:disabled"]' + 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]' ) expect(content).toMatch(`const modelValue = _useModel("modelValue")`) expect(content).toMatch(`const count = _useModel("count")`) expect(content).toMatch(`const disabled = _useModel("disabled")`) + expect(content).toMatch(`const any = _useModel("any")`) - expect(content).toMatch(`const count = _useModel("count")`) expect(bindings).toStrictEqual({ modelValue: BindingTypes.SETUP_REF, count: BindingTypes.SETUP_REF, - disabled: BindingTypes.SETUP_REF + disabled: BindingTypes.SETUP_REF, + any: BindingTypes.SETUP_REF }) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index a254962b105..210a3fd6ac5 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1026,26 +1026,39 @@ export function compileScript( let modelPropsDecl = '' for (const [name, { type, options }] of Object.entries(modelDecls)) { + let skipCheck = false + let runtimeTypes = type && inferRuntimeType(type, declaredTypes) - if (runtimeTypes && isProd) { - runtimeTypes = runtimeTypes.filter( - el => el === 'Boolean' || (el === 'Function' && options) - ) + if (runtimeTypes) { + const hasUnknownType = runtimeTypes.includes(UNKNOWN_TYPE) + + runtimeTypes = runtimeTypes.filter(el => { + if (el === UNKNOWN_TYPE) return false + return isProd + ? el === 'Boolean' || (el === 'Function' && options) + : true + }) + skipCheck = !isProd && hasUnknownType && runtimeTypes.length > 0 } + let runtimeType = (runtimeTypes && runtimeTypes.length > 0 && toRuntimeTypeString(runtimeTypes)) || undefined + const skipCheckString = skipCheck ? ', skipCheck: true' : '' + if (runtimeType) runtimeType = `type: ${runtimeType}` let decl: string if (runtimeType && options) { decl = isTS - ? `{ ${runtimeType}, ...${options} }` - : `Object.assign({ ${runtimeType} }, ${options})` + ? `{ ${runtimeType}${skipCheckString}, ...${options} }` + : `Object.assign({ ${runtimeType}${skipCheckString} }, ${options})` } else { - decl = options || (runtimeType ? `{ ${runtimeType} }` : '{}') + decl = + options || + (runtimeType ? `{ ${runtimeType}${skipCheckString} }` : '{}') } modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},` } From a7481c09d1f0f0d54d7f8e217eaaf8b2b14b2e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 6 Apr 2023 19:31:29 +0800 Subject: [PATCH 17/26] fix: resolve PR comments --- packages/compiler-sfc/src/compileScript.ts | 60 ++++++++++---------- packages/dts-test/setupHelpers.test-d.ts | 14 ++--- packages/runtime-core/src/apiSetupHelpers.ts | 27 ++++----- 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 210a3fd6ac5..9c02282a550 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1197,6 +1197,34 @@ export function compileScript( } } + function genRuntimeEmits() { + function genEmitsFromTS() { + return typeDeclaredEmits.size + ? `[${Array.from(typeDeclaredEmits) + .map(k => JSON.stringify(k)) + .join(', ')}]` + : `` + } + + let emitsDecl = '' + if (emitsRuntimeDecl) { + emitsDecl = scriptSetup!.content + .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!) + .trim() + } else if (emitsTypeDecl) { + emitsDecl = genEmitsFromTS() + } + if (hasDefineModelCall) { + let modelEmitsDecl = `[${Object.keys(modelDecls) + .map(n => JSON.stringify(`update:${n}`)) + .join(', ')}]` + emitsDecl = emitsDecl + ? `${helper('mergeModels')}(${emitsDecl}, ${modelEmitsDecl})` + : modelEmitsDecl + } + return emitsDecl + } + // 0. parse both `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch( + `_useModel(__props, "modelValue", { local: true })` + ) + expect(content).toMatch(`_useModel(__props, "bar", { [key]: true })`) + expect(content).toMatch(`_useModel(__props, "baz", { ...x })`) + expect(content).toMatch(`_useModel(__props, "qux", x)`) + expect(content).toMatch(`_useModel(__props, "foo2", { local: true })`) + expect(content).toMatch(`_useModel(__props, "hoist", { local })`) + }) }) test('