Skip to content

Commit

Permalink
fix(defineModel): align prod mode runtime type generation with define…
Browse files Browse the repository at this point in the history
…Props

close #10769
  • Loading branch information
yyx990803 committed Apr 24, 2024
1 parent 3724693 commit 4253a57
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 31 deletions.
Expand Up @@ -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<boolean | string | {}>(__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 | (() => number)>(__props, "modelValue")
return { modelValue }
}
})"
`;
28 changes: 28 additions & 0 deletions packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
Expand Up @@ -161,6 +161,34 @@ describe('defineModel()', () => {
})
})

test('w/ types, production mode, boolean + multiple types', () => {
const { content } = compile(
`
<script setup lang="ts">
const modelValue = defineModel<boolean | string | {}>()
</script>
`,
{ 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(
`
<script setup lang="ts">
const modelValue = defineModel<number | (() => number)>({ default: () => 1 })
</script>
`,
{ isProd: true },
)
assertCode(content)
expect(content).toMatch(
'"modelValue": { type: [Number, Function], ...{ default: () => 1 } }',
)
})

test('get / set transformers', () => {
const { content } = compile(
`
Expand Down
63 changes: 32 additions & 31 deletions 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'
Expand Down Expand Up @@ -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},`

Expand Down

0 comments on commit 4253a57

Please sign in to comment.