Skip to content

Commit

Permalink
fix(compiler-sfc): fix co-usage of defineModel transform options and …
Browse files Browse the repository at this point in the history
…props destructure

close vuejs#9972
  • Loading branch information
yyx990803 committed Jan 4, 2024
1 parent ae60a91 commit b20350d
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export default {
__expose();
const modelValue = _useModel(__props, "modelValue")
const c = _useModel(__props, "count")
const toString = _useModel(__props, "toString")
const c = _useModel(__props, 'count')
const toString = _useModel(__props, 'toString')
return { modelValue, c, toString }
}
Expand All @@ -40,7 +40,10 @@ export default /*#__PURE__*/_defineComponent({
setup(__props, { expose: __expose }) {
__expose();
const modelValue = _useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 }, })
const modelValue = _useModel(__props, "modelValue", {
get(v) { return v - 1 },
set: (v) => { return v + 1 },
})
return { modelValue }
}
Expand All @@ -63,7 +66,36 @@ export default /*#__PURE__*/_defineComponent({
setup(__props, { expose: __expose }) {
__expose();
const modelValue = _useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 }, })
const modelValue = _useModel(__props, "modelValue", {
get(v) { return v - 1 },
set: (v) => { return v + 1 },
})
return { modelValue }
}
})"
`;

exports[`defineModel() > usage w/ props destructure 1`] = `
"import { useModel as _useModel, mergeModels as _mergeModels, defineComponent as _defineComponent } from 'vue'
export default /*#__PURE__*/_defineComponent({
props: /*#__PURE__*/_mergeModels({
x: { type: Number, required: true }
}, {
"modelValue": {
},
"modelModifiers": {},
}),
emits: ["update:modelValue"],
setup(__props: any, { expose: __expose }) {
__expose();
const modelValue = _useModel(__props, "modelValue", {
set: (v) => { return v + __props.x }
})
return { modelValue }
}
Expand All @@ -84,7 +116,7 @@ export default {
__expose();
const count = _useModel(__props, "count")
const count = _useModel(__props, 'count')
return { count }
}
Expand Down Expand Up @@ -132,10 +164,10 @@ export default /*#__PURE__*/_defineComponent({
setup(__props, { expose: __expose }) {
__expose();
const modelValue = _useModel(__props, "modelValue")
const count = _useModel(__props, "count")
const disabled = _useModel(__props, "disabled")
const any = _useModel(__props, "any")
const modelValue = _useModel<boolean | string>(__props, "modelValue")
const count = _useModel<number>(__props, 'count')
const disabled = _useModel<number>(__props, 'disabled')
const any = _useModel<any | boolean>(__props, 'any')
return { modelValue, count, disabled, any }
}
Expand Down Expand Up @@ -163,11 +195,11 @@ export default /*#__PURE__*/_defineComponent({
setup(__props, { expose: __expose }) {
__expose();
const modelValue = _useModel(__props, "modelValue")
const fn = _useModel(__props, "fn")
const fnWithDefault = _useModel(__props, "fnWithDefault")
const str = _useModel(__props, "str")
const optional = _useModel(__props, "optional")
const modelValue = _useModel<boolean>(__props, "modelValue")
const fn = _useModel<() => void>(__props, 'fn')
const fnWithDefault = _useModel<() => void>(__props, 'fnWithDefault')
const str = _useModel<string>(__props, 'str')
const optional = _useModel<string>(__props, 'optional')
return { modelValue, fn, fnWithDefault, str, optional }
}
Expand Down
49 changes: 38 additions & 11 deletions packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ describe('defineModel()', () => {
expect(content).toMatch(
`const modelValue = _useModel(__props, "modelValue")`,
)
expect(content).toMatch(`const c = _useModel(__props, "count")`)
expect(content).toMatch(`const c = _useModel(__props, 'count')`)
expect(content).toMatch(`const toString = _useModel(__props, 'toString')`)
expect(content).toMatch(`return { modelValue, c, toString }`)
expect(content).not.toMatch('defineModel')

Expand Down Expand Up @@ -71,7 +72,7 @@ describe('defineModel()', () => {
"count": {},
"countModifiers": {},
})`)
expect(content).toMatch(`const count = _useModel(__props, "count")`)
expect(content).toMatch(`const count = _useModel(__props, 'count')`)
expect(content).not.toMatch('defineModel')
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
Expand Down Expand Up @@ -104,11 +105,15 @@ describe('defineModel()', () => {
)

expect(content).toMatch(
`const modelValue = _useModel(__props, "modelValue")`,
`const modelValue = _useModel<boolean | string>(__props, "modelValue")`,
)
expect(content).toMatch(`const count = _useModel<number>(__props, 'count')`)
expect(content).toMatch(
`const disabled = _useModel<number>(__props, 'disabled')`,
)
expect(content).toMatch(
`const any = _useModel<any | boolean>(__props, 'any')`,
)
expect(content).toMatch(`const count = _useModel(__props, "count")`)
expect(content).toMatch(`const disabled = _useModel(__props, "disabled")`)
expect(content).toMatch(`const any = _useModel(__props, "any")`)

expect(bindings).toStrictEqual({
modelValue: BindingTypes.SETUP_REF,
Expand Down Expand Up @@ -143,10 +148,10 @@ describe('defineModel()', () => {
'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]',
)
expect(content).toMatch(
`const modelValue = _useModel(__props, "modelValue")`,
`const modelValue = _useModel<boolean>(__props, "modelValue")`,
)
expect(content).toMatch(`const fn = _useModel(__props, "fn")`)
expect(content).toMatch(`const str = _useModel(__props, "str")`)
expect(content).toMatch(`const fn = _useModel<() => void>(__props, 'fn')`)
expect(content).toMatch(`const str = _useModel<string>(__props, 'str')`)
expect(bindings).toStrictEqual({
modelValue: BindingTypes.SETUP_REF,
fn: BindingTypes.SETUP_REF,
Expand All @@ -171,7 +176,10 @@ describe('defineModel()', () => {
assertCode(content)
expect(content).toMatch(/"modelValue": {\s+required: true,?\s+}/m)
expect(content).toMatch(
`_useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 }, })`,
`_useModel(__props, "modelValue", {
get(v) { return v - 1 },
set: (v) => { return v + 1 },
})`,
)

const { content: content2 } = compile(
Expand All @@ -191,7 +199,26 @@ describe('defineModel()', () => {
/"modelValue": {\s+default: 0,\s+required: true,?\s+}/m,
)
expect(content2).toMatch(
`_useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 }, })`,
`_useModel(__props, "modelValue", {
get(v) { return v - 1 },
set: (v) => { return v + 1 },
})`,
)
})

test('usage w/ props destructure', () => {
const { content } = compile(
`
<script setup lang="ts">
const { x } = defineProps<{ x: number }>()
const modelValue = defineModel({
set: (v) => { return v + x }
})
</script>
`,
{ propsDestructure: true },
)
assertCode(content)
expect(content).toMatch(`set: (v) => { return v + __props.x }`)
})
})
88 changes: 50 additions & 38 deletions packages/compiler-sfc/src/script/defineModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export function processDefineModel(
let modelName: string
let options: Node | undefined
const arg0 = node.arguments[0] && unwrapTSNode(node.arguments[0])
if (arg0 && arg0.type === 'StringLiteral') {
const hasName = arg0 && arg0.type === 'StringLiteral'
if (hasName) {
modelName = arg0.value
options = node.arguments[1]
} else {
Expand All @@ -46,39 +47,42 @@ export function processDefineModel(
}

let optionsString = options && ctx.getString(options)
let runtimeOptions = ''
let transformOptions = ''

if (options) {
if (options.type === 'ObjectExpression') {
for (let i = options.properties.length - 1; i >= 0; i--) {
const p = options.properties[i]
if (p.type === 'SpreadElement' || p.computed) {
runtimeOptions = optionsString!
break
}
if (
(p.type === 'ObjectProperty' || p.type === 'ObjectMethod') &&
((p.key.type === 'Identifier' &&
(p.key.name === 'get' || p.key.name === 'set')) ||
(p.key.type === 'StringLiteral' &&
(p.key.value === 'get' || p.key.value === 'set')))
) {
transformOptions = ctx.getString(p) + ', ' + transformOptions

// remove transform option from prop options to avoid duplicates
const offset = p.start! - options.start!
const next = options.properties[i + 1]
const end = (next ? next.start! : options.end! - 1) - options.start!
optionsString =
optionsString.slice(0, offset) + optionsString.slice(end)
}
let optionsRemoved = !options

if (
options &&
options.type === 'ObjectExpression' &&
!options.properties.some(p => p.type === 'SpreadElement' || p.computed)
) {
let removed = 0
for (let i = options.properties.length - 1; i >= 0; i--) {
const p = options.properties[i]
const next = options.properties[i + 1]
const start = p.start!
const end = next ? next.start! : options.end! - 1
if (
(p.type === 'ObjectProperty' || p.type === 'ObjectMethod') &&
((p.key.type === 'Identifier' &&
(p.key.name === 'get' || p.key.name === 'set')) ||
(p.key.type === 'StringLiteral' &&
(p.key.value === 'get' || p.key.value === 'set')))
) {
// remove runtime-only options from prop options to avoid duplicates
optionsString =
optionsString.slice(0, start - options.start!) +
optionsString.slice(end - options.start!)
} else {
// remove prop options from runtime options
removed++
ctx.s.remove(ctx.startOffset! + start, ctx.startOffset! + end)
}
if (!runtimeOptions && transformOptions) {
runtimeOptions = `{ ${transformOptions} }`
}
} else {
runtimeOptions = optionsString!
}
if (removed === options.properties.length) {
optionsRemoved = true
ctx.s.remove(
ctx.startOffset! + (hasName ? arg0.end! : options.start!),
ctx.startOffset! + options.end!,
)
}
}

Expand All @@ -91,12 +95,20 @@ export function processDefineModel(
// register binding type
ctx.bindingMetadata[modelName] = BindingTypes.PROPS

// defineModel -> useModel
ctx.s.overwrite(
ctx.startOffset! + node.start!,
ctx.startOffset! + node.end!,
`${ctx.helper('useModel')}(__props, ${JSON.stringify(modelName)}${
runtimeOptions ? `, ${runtimeOptions}` : ``
})`,
ctx.startOffset! + node.callee.start!,
ctx.startOffset! + node.callee.end!,
ctx.helper('useModel'),
)
// inject arguments
ctx.s.appendLeft(
ctx.startOffset! +
(node.arguments.length ? node.arguments[0].start! : node.end! - 1),
`__props, ` +
(hasName
? ``
: `${JSON.stringify(modelName)}${optionsRemoved ? `` : `, `}`),
)

return true
Expand Down

0 comments on commit b20350d

Please sign in to comment.