Skip to content

Commit 1f28ae1

Browse files
Jevon617sxzz
andauthored
feat(compiler-vapor): v-model for component (#180)
Co-authored-by: 三咲智子 Kevin Deng <[email protected]>
1 parent 37df043 commit 1f28ae1

File tree

7 files changed

+322
-27
lines changed

7 files changed

+322
-27
lines changed

packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,86 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`compiler: vModel transform > component > v-model for component should generate modelModifiers 1`] = `
4+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
5+
6+
export function render(_ctx) {
7+
const n0 = _createComponent(_resolveComponent("Comp"), [{
8+
modelValue: () => (_ctx.foo),
9+
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
10+
modelModifiers: () => ({ trim: true, "bar-baz": true })
11+
}], true)
12+
return n0
13+
}"
14+
`;
15+
16+
exports[`compiler: vModel transform > component > v-model for component should work 1`] = `
17+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
18+
19+
export function render(_ctx) {
20+
const n0 = _createComponent(_resolveComponent("Comp"), [{
21+
modelValue: () => (_ctx.foo),
22+
"onUpdate:modelValue": () => $event => (_ctx.foo = $event)
23+
}], true)
24+
return n0
25+
}"
26+
`;
27+
28+
exports[`compiler: vModel transform > component > v-model with arguments for component should generate modelModifiers 1`] = `
29+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
30+
31+
export function render(_ctx) {
32+
const n0 = _createComponent(_resolveComponent("Comp"), [{
33+
foo: () => (_ctx.foo),
34+
"onUpdate:foo": () => $event => (_ctx.foo = $event),
35+
fooModifiers: () => ({ trim: true }),
36+
bar: () => (_ctx.bar),
37+
"onUpdate:bar": () => $event => (_ctx.bar = $event),
38+
barModifiers: () => ({ number: true })
39+
}], true)
40+
return n0
41+
}"
42+
`;
43+
44+
exports[`compiler: vModel transform > component > v-model with arguments for component should work 1`] = `
45+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
46+
47+
export function render(_ctx) {
48+
const n0 = _createComponent(_resolveComponent("Comp"), [{
49+
bar: () => (_ctx.foo),
50+
"onUpdate:bar": () => $event => (_ctx.foo = $event)
51+
}], true)
52+
return n0
53+
}"
54+
`;
55+
56+
exports[`compiler: vModel transform > component > v-model with dynamic arguments for component should generate modelModifiers 1`] = `
57+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
58+
59+
export function render(_ctx) {
60+
const n0 = _createComponent(_resolveComponent("Comp"), [{
61+
[_ctx.foo]: () => (_ctx.foo),
62+
["onUpdate:" + _ctx.foo]: () => $event => (_ctx.foo = $event),
63+
[_ctx.foo + "Modifiers"]: () => ({ trim: true }),
64+
[_ctx.bar]: () => (_ctx.bar),
65+
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
66+
[_ctx.bar + "Modifiers"]: () => ({ number: true })
67+
}], true)
68+
return n0
69+
}"
70+
`;
71+
72+
exports[`compiler: vModel transform > component > v-model with dynamic arguments for component should work 1`] = `
73+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
74+
75+
export function render(_ctx) {
76+
const n0 = _createComponent(_resolveComponent("Comp"), [{
77+
[_ctx.arg]: () => (_ctx.foo),
78+
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event)
79+
}], true)
80+
return n0
81+
}"
82+
`;
83+
384
exports[`compiler: vModel transform > modifiers > .lazy 1`] = `
485
"import { vModelText as _vModelText, withDirectives as _withDirectives, delegate as _delegate, template as _template } from 'vue/vapor';
586
const t0 = _template("<input>")

packages/compiler-vapor/__tests__/transforms/vModel.spec.ts

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { makeCompile } from './_utils'
2-
import { transformChildren, transformElement, transformVModel } from '../../src'
2+
import {
3+
IRNodeTypes,
4+
transformChildren,
5+
transformElement,
6+
transformVModel,
7+
} from '../../src'
38
import { BindingTypes, DOMErrorCodes } from '@vue/compiler-dom'
49

510
const compileWithVModel = makeCompile({
@@ -198,4 +203,169 @@ describe('compiler: vModel transform', () => {
198203

199204
expect(code).toMatchSnapshot()
200205
})
206+
207+
describe('component', () => {
208+
test('v-model for component should work', () => {
209+
const { code, ir } = compileWithVModel('<Comp v-model="foo" />')
210+
expect(code).toMatchSnapshot()
211+
expect(code).contains(
212+
`modelValue: () => (_ctx.foo),
213+
"onUpdate:modelValue": () => $event => (_ctx.foo = $event)`,
214+
)
215+
expect(ir.block.operation).toMatchObject([
216+
{
217+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
218+
tag: 'Comp',
219+
props: [
220+
[
221+
{
222+
key: { content: 'modelValue', isStatic: true },
223+
model: true,
224+
modelModifiers: [],
225+
values: [{ content: 'foo', isStatic: false }],
226+
},
227+
],
228+
],
229+
},
230+
])
231+
})
232+
233+
test('v-model with arguments for component should work', () => {
234+
const { code, ir } = compileWithVModel('<Comp v-model:bar="foo" />')
235+
expect(code).toMatchSnapshot()
236+
expect(code).contains(
237+
`bar: () => (_ctx.foo),
238+
"onUpdate:bar": () => $event => (_ctx.foo = $event)`,
239+
)
240+
expect(ir.block.operation).toMatchObject([
241+
{
242+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
243+
tag: 'Comp',
244+
props: [
245+
[
246+
{
247+
key: { content: 'bar', isStatic: true },
248+
model: true,
249+
modelModifiers: [],
250+
values: [{ content: 'foo', isStatic: false }],
251+
},
252+
],
253+
],
254+
},
255+
])
256+
})
257+
258+
test('v-model with dynamic arguments for component should work', () => {
259+
const { code, ir } = compileWithVModel('<Comp v-model:[arg]="foo" />')
260+
expect(code).toMatchSnapshot()
261+
expect(code).contains(
262+
`[_ctx.arg]: () => (_ctx.foo),
263+
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event)`,
264+
)
265+
expect(ir.block.operation).toMatchObject([
266+
{
267+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
268+
tag: 'Comp',
269+
props: [
270+
[
271+
{
272+
key: { content: 'arg', isStatic: false },
273+
values: [{ content: 'foo', isStatic: false }],
274+
model: true,
275+
modelModifiers: [],
276+
},
277+
],
278+
],
279+
},
280+
])
281+
})
282+
283+
test('v-model for component should generate modelModifiers', () => {
284+
const { code, ir } = compileWithVModel(
285+
'<Comp v-model.trim.bar-baz="foo" />',
286+
)
287+
expect(code).toMatchSnapshot()
288+
expect(code).contain(
289+
`modelModifiers: () => ({ trim: true, "bar-baz": true })`,
290+
)
291+
expect(ir.block.operation).toMatchObject([
292+
{
293+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
294+
tag: 'Comp',
295+
props: [
296+
[
297+
{
298+
key: { content: 'modelValue', isStatic: true },
299+
values: [{ content: 'foo', isStatic: false }],
300+
model: true,
301+
modelModifiers: ['trim', 'bar-baz'],
302+
},
303+
],
304+
],
305+
},
306+
])
307+
})
308+
309+
test('v-model with arguments for component should generate modelModifiers', () => {
310+
const { code, ir } = compileWithVModel(
311+
'<Comp v-model:foo.trim="foo" v-model:bar.number="bar" />',
312+
)
313+
expect(code).toMatchSnapshot()
314+
expect(code).contain(`fooModifiers: () => ({ trim: true })`)
315+
expect(code).contain(`barModifiers: () => ({ number: true })`)
316+
expect(ir.block.operation).toMatchObject([
317+
{
318+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
319+
tag: 'Comp',
320+
props: [
321+
[
322+
{
323+
key: { content: 'foo', isStatic: true },
324+
values: [{ content: 'foo', isStatic: false }],
325+
model: true,
326+
modelModifiers: ['trim'],
327+
},
328+
{
329+
key: { content: 'bar', isStatic: true },
330+
values: [{ content: 'bar', isStatic: false }],
331+
model: true,
332+
modelModifiers: ['number'],
333+
},
334+
],
335+
],
336+
},
337+
])
338+
})
339+
340+
test('v-model with dynamic arguments for component should generate modelModifiers ', () => {
341+
const { code, ir } = compileWithVModel(
342+
'<Comp v-model:[foo].trim="foo" v-model:[bar].number="bar" />',
343+
)
344+
expect(code).toMatchSnapshot()
345+
expect(code).contain(`[_ctx.foo + "Modifiers"]: () => ({ trim: true })`)
346+
expect(code).contain(`[_ctx.bar + "Modifiers"]: () => ({ number: true })`)
347+
expect(ir.block.operation).toMatchObject([
348+
{
349+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
350+
tag: 'Comp',
351+
props: [
352+
[
353+
{
354+
key: { content: 'foo', isStatic: false },
355+
values: [{ content: 'foo', isStatic: false }],
356+
model: true,
357+
modelModifiers: ['trim'],
358+
},
359+
{
360+
key: { content: 'bar', isStatic: false },
361+
values: [{ content: 'bar', isStatic: false }],
362+
model: true,
363+
modelModifiers: ['number'],
364+
},
365+
],
366+
],
367+
},
368+
])
369+
})
370+
})
201371
})

packages/compiler-vapor/src/generators/component.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extend, isArray } from '@vue/shared'
1+
import { camelize, extend, isArray } from '@vue/shared'
22
import type { CodegenContext } from '../generate'
33
import type { CreateComponentIRNode, IRProp } from '../ir'
44
import {
@@ -13,6 +13,8 @@ import { genExpression } from './expression'
1313
import { genPropKey } from './prop'
1414
import { createSimpleExpression } from '@vue/compiler-dom'
1515
import { genEventHandler } from './event'
16+
import { genDirectiveModifiers } from './directive'
17+
import { genModelHandler } from './modelValue'
1618

1719
// TODO: generate component slots
1820
export function genCreateComponent(
@@ -23,15 +25,15 @@ export function genCreateComponent(
2325

2426
const tag = genTag()
2527
const isRoot = oper.root
26-
const props = genProps()
28+
const rawProps = genRawProps()
2729

2830
return [
2931
NEWLINE,
3032
`const n${oper.id} = `,
3133
...genCall(
3234
vaporHelper('createComponent'),
3335
tag,
34-
props || (isRoot ? 'null' : false),
36+
rawProps || (isRoot ? 'null' : false),
3537
isRoot && 'true',
3638
),
3739
]
@@ -47,11 +49,11 @@ export function genCreateComponent(
4749
}
4850
}
4951

50-
function genProps() {
52+
function genRawProps() {
5153
const props = oper.props
5254
.map(props => {
5355
if (isArray(props)) {
54-
if (!props.length) return undefined
56+
if (!props.length) return
5557
return genStaticProps(props)
5658
} else {
5759
let expr = genExpression(props.value, context)
@@ -79,8 +81,34 @@ export function genCreateComponent(
7981
...(prop.handler
8082
? genEventHandler(context, prop.values[0])
8183
: ['() => (', ...genExpression(prop.values[0], context), ')']),
84+
...(prop.model
85+
? [...genModelEvent(prop), ...genModelModifiers(prop)]
86+
: []),
8287
]
8388
}),
8489
)
90+
91+
function genModelEvent(prop: IRProp): CodeFragment[] {
92+
const name = prop.key.isStatic
93+
? [JSON.stringify(`onUpdate:${camelize(prop.key.content)}`)]
94+
: ['["onUpdate:" + ', ...genExpression(prop.key, context), ']']
95+
const handler = genModelHandler(prop.values[0], context)
96+
97+
return [',', NEWLINE, ...name, ': ', ...handler]
98+
}
99+
100+
function genModelModifiers(prop: IRProp): CodeFragment[] {
101+
const { key, modelModifiers } = prop
102+
if (!modelModifiers || !modelModifiers.length) return []
103+
104+
const modifiersKey = key.isStatic
105+
? key.content === 'modelValue'
106+
? [`modelModifiers`]
107+
: [`${key.content}Modifiers`]
108+
: ['[', ...genExpression(key, context), ' + "Modifiers"]']
109+
110+
const modifiersVal = genDirectiveModifiers(modelModifiers)
111+
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
112+
}
85113
}
86114
}

packages/compiler-vapor/src/generators/directive.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function genWithDirective(
3535
? NULL
3636
: false
3737
const modifiers = dir.modifiers.length
38-
? ['{ ', genDirectiveModifiers(), ' }']
38+
? ['{ ', genDirectiveModifiers(dir.modifiers), ' }']
3939
: false
4040

4141
return genMulti(['[', ']', ', '], directive, value, argument, modifiers)
@@ -61,14 +61,14 @@ export function genWithDirective(
6161
}
6262
}
6363
}
64-
65-
function genDirectiveModifiers() {
66-
return dir.modifiers
67-
.map(
68-
value =>
69-
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
70-
)
71-
.join(', ')
72-
}
7364
}
7465
}
66+
67+
export function genDirectiveModifiers(modifiers: string[]) {
68+
return modifiers
69+
.map(
70+
value =>
71+
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
72+
)
73+
.join(', ')
74+
}

0 commit comments

Comments
 (0)