Skip to content

Commit 4e13a57

Browse files
Doctor-wuLittleSoundsxzz
authored
feat(compiler/runtime-vapor): implement v-slots + v-for / v-if (#207)
Co-authored-by: Rizumu Ayaka <[email protected]> Co-authored-by: 三咲智子 Kevin Deng <[email protected]>
1 parent 2e2f3e2 commit 4e13a57

File tree

8 files changed

+433
-52
lines changed

8 files changed

+433
-52
lines changed

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,78 @@ export function render(_ctx) {
1717
}"
1818
`;
1919

20+
exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = `
21+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createForSlots as _createForSlots, template as _template } from 'vue/vapor';
22+
const t0 = _template("foo")
23+
24+
export function render(_ctx) {
25+
const _component_Comp = _resolveComponent("Comp")
26+
const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (item) => ({
27+
name: item,
28+
fn: () => {
29+
const n0 = t0()
30+
return n0
31+
}
32+
}))], true)
33+
return n2
34+
}"
35+
`;
36+
37+
exports[`compiler: transform slot > dynamic slots name w/ v-for and provide absent key 1`] = `
38+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createForSlots as _createForSlots, template as _template } from 'vue/vapor';
39+
const t0 = _template("foo")
40+
41+
export function render(_ctx) {
42+
const _component_Comp = _resolveComponent("Comp")
43+
const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (_, __, index) => ({
44+
name: index,
45+
fn: () => {
46+
const n0 = t0()
47+
return n0
48+
}
49+
}))], true)
50+
return n2
51+
}"
52+
`;
53+
54+
exports[`compiler: transform slot > dynamic slots name w/ v-if / v-else[-if] 1`] = `
55+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
56+
const t0 = _template("condition slot")
57+
const t1 = _template("another condition")
58+
const t2 = _template("else condition")
59+
60+
export function render(_ctx) {
61+
const _component_Comp = _resolveComponent("Comp")
62+
const n6 = _createComponent(_component_Comp, null, null, () => [_ctx.condition
63+
? {
64+
name: "condition",
65+
fn: () => {
66+
const n0 = t0()
67+
return n0
68+
},
69+
key: "0"
70+
}
71+
: _ctx.anotherCondition
72+
? {
73+
name: "condition",
74+
fn: () => {
75+
const n2 = t1()
76+
return n2
77+
},
78+
key: "1"
79+
}
80+
: {
81+
name: "condition",
82+
fn: () => {
83+
const n4 = t2()
84+
return n4
85+
},
86+
key: "2"
87+
}], true)
88+
return n6
89+
}"
90+
`;
91+
2092
exports[`compiler: transform slot > implicit default slot 1`] = `
2193
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
2294
const t0 = _template("<div></div>")

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

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
22
import {
3+
DynamicSlotType,
34
IRNodeTypes,
45
transformChildren,
56
transformElement,
@@ -126,6 +127,112 @@ describe('compiler: transform slot', () => {
126127
])
127128
})
128129

130+
test('dynamic slots name w/ v-for', () => {
131+
const { ir, code } = compileWithSlots(
132+
`<Comp>
133+
<template v-for="item in list" #[item]>foo</template>
134+
</Comp>`,
135+
)
136+
expect(code).toMatchSnapshot()
137+
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
138+
expect(ir.block.operation).toMatchObject([
139+
{
140+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
141+
tag: 'Comp',
142+
slots: undefined,
143+
dynamicSlots: [
144+
{
145+
name: {
146+
type: NodeTypes.SIMPLE_EXPRESSION,
147+
content: 'item',
148+
isStatic: false,
149+
},
150+
fn: { type: IRNodeTypes.BLOCK },
151+
loop: {
152+
source: { content: 'list' },
153+
value: { content: 'item' },
154+
key: undefined,
155+
index: undefined,
156+
},
157+
},
158+
],
159+
},
160+
])
161+
})
162+
163+
test('dynamic slots name w/ v-for and provide absent key', () => {
164+
const { ir, code } = compileWithSlots(
165+
`<Comp>
166+
<template v-for="(,,index) in list" #[index]>foo</template>
167+
</Comp>`,
168+
)
169+
expect(code).toMatchSnapshot()
170+
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
171+
expect(ir.block.operation).toMatchObject([
172+
{
173+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
174+
tag: 'Comp',
175+
slots: undefined,
176+
dynamicSlots: [
177+
{
178+
name: {
179+
type: NodeTypes.SIMPLE_EXPRESSION,
180+
content: 'index',
181+
isStatic: false,
182+
},
183+
fn: { type: IRNodeTypes.BLOCK },
184+
loop: {
185+
source: { content: 'list' },
186+
value: undefined,
187+
key: undefined,
188+
index: {
189+
type: NodeTypes.SIMPLE_EXPRESSION,
190+
},
191+
},
192+
},
193+
],
194+
},
195+
])
196+
})
197+
198+
test('dynamic slots name w/ v-if / v-else[-if]', () => {
199+
const { ir, code } = compileWithSlots(
200+
`<Comp>
201+
<template v-if="condition" #condition>condition slot</template>
202+
<template v-else-if="anotherCondition" #condition>another condition</template>
203+
<template v-else #condition>else condition</template>
204+
</Comp>`,
205+
)
206+
expect(code).toMatchSnapshot()
207+
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
208+
expect(ir.block.operation).toMatchObject([
209+
{
210+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
211+
tag: 'Comp',
212+
slots: undefined,
213+
dynamicSlots: [
214+
{
215+
slotType: DynamicSlotType.CONDITIONAL,
216+
condition: { content: 'condition' },
217+
positive: {
218+
slotType: DynamicSlotType.BASIC,
219+
key: 0,
220+
},
221+
negative: {
222+
slotType: DynamicSlotType.CONDITIONAL,
223+
condition: { content: 'anotherCondition' },
224+
positive: {
225+
slotType: DynamicSlotType.BASIC,
226+
key: 1,
227+
},
228+
negative: { slotType: DynamicSlotType.BASIC, key: 2 },
229+
},
230+
},
231+
],
232+
},
233+
])
234+
})
235+
129236
describe('errors', () => {
130237
test('error on extraneous children w/ named default slot', () => {
131238
const onError = vi.fn()

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

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { camelize, extend, isArray } from '@vue/shared'
22
import type { CodegenContext } from '../generate'
33
import {
4+
type ComponentBasicDynamicSlot,
5+
type ComponentConditionalDynamicSlot,
46
type ComponentDynamicSlot,
7+
type ComponentLoopDynamicSlot,
58
type ComponentSlots,
69
type CreateComponentIRNode,
10+
DynamicSlotType,
711
IRDynamicPropsKind,
812
type IRProp,
913
type IRProps,
@@ -15,6 +19,8 @@ import {
1519
DELIMITERS_ARRAY_NEWLINE,
1620
DELIMITERS_OBJECT,
1721
DELIMITERS_OBJECT_NEWLINE,
22+
INDENT_END,
23+
INDENT_START,
1824
NEWLINE,
1925
genCall,
2026
genMulti,
@@ -155,13 +161,90 @@ function genDynamicSlots(
155161
) {
156162
const slotsExpr = genMulti(
157163
dynamicSlots.length > 1 ? DELIMITERS_ARRAY_NEWLINE : DELIMITERS_ARRAY,
158-
...dynamicSlots.map(({ name, fn }) =>
159-
genMulti(
160-
DELIMITERS_OBJECT_NEWLINE,
161-
['name: ', ...genExpression(name, context)],
162-
['fn: ', ...genBlock(fn, context)],
163-
),
164-
),
164+
...dynamicSlots.map(slot => genDynamicSlot(slot, context)),
165165
)
166166
return ['() => ', ...slotsExpr]
167167
}
168+
169+
function genDynamicSlot(
170+
slot: ComponentDynamicSlot,
171+
context: CodegenContext,
172+
): CodeFragment[] {
173+
switch (slot.slotType) {
174+
case DynamicSlotType.BASIC:
175+
return genBasicDynamicSlot(slot, context)
176+
case DynamicSlotType.LOOP:
177+
return genLoopSlot(slot, context)
178+
case DynamicSlotType.CONDITIONAL:
179+
return genConditionalSlot(slot, context)
180+
}
181+
}
182+
183+
function genBasicDynamicSlot(
184+
slot: ComponentBasicDynamicSlot,
185+
context: CodegenContext,
186+
): CodeFragment[] {
187+
const { name, fn, key } = slot
188+
return genMulti(
189+
DELIMITERS_OBJECT_NEWLINE,
190+
['name: ', ...genExpression(name, context)],
191+
['fn: ', ...genBlock(fn, context)],
192+
...(key !== undefined ? [`key: "${key}"`] : []),
193+
)
194+
}
195+
196+
function genLoopSlot(
197+
slot: ComponentLoopDynamicSlot,
198+
context: CodegenContext,
199+
): CodeFragment[] {
200+
const { name, fn, loop } = slot
201+
const { value, key, index, source } = loop
202+
const rawValue = value && value.content
203+
const rawKey = key && key.content
204+
const rawIndex = index && index.content
205+
206+
const idMap: Record<string, string> = {}
207+
if (rawValue) idMap[rawValue] = rawValue
208+
if (rawKey) idMap[rawKey] = rawKey
209+
if (rawIndex) idMap[rawIndex] = rawIndex
210+
const slotExpr = genMulti(
211+
DELIMITERS_OBJECT_NEWLINE,
212+
['name: ', ...context.withId(() => genExpression(name, context), idMap)],
213+
['fn: ', ...context.withId(() => genBlock(fn, context), idMap)],
214+
)
215+
return [
216+
...genCall(
217+
context.vaporHelper('createForSlots'),
218+
genExpression(source, context),
219+
[
220+
...genMulti(
221+
['(', ')', ', '],
222+
rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined,
223+
rawKey ? rawKey : rawIndex ? '__' : undefined,
224+
rawIndex,
225+
),
226+
' => (',
227+
...slotExpr,
228+
')',
229+
],
230+
),
231+
]
232+
}
233+
234+
function genConditionalSlot(
235+
slot: ComponentConditionalDynamicSlot,
236+
context: CodegenContext,
237+
): CodeFragment[] {
238+
const { condition, positive, negative } = slot
239+
return [
240+
...genExpression(condition, context),
241+
INDENT_START,
242+
NEWLINE,
243+
'? ',
244+
...genDynamicSlot(positive, context),
245+
NEWLINE,
246+
': ',
247+
...(negative ? [...genDynamicSlot(negative, context)] : ['void 0']),
248+
INDENT_END,
249+
]
250+
}

packages/compiler-vapor/src/ir.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,16 @@ export interface IfIRNode extends BaseIRNode {
7373
once?: boolean
7474
}
7575

76-
export interface ForIRNode extends BaseIRNode {
77-
type: IRNodeTypes.FOR
78-
id: number
76+
export interface IRFor {
7977
source: SimpleExpressionNode
8078
value?: SimpleExpressionNode
8179
key?: SimpleExpressionNode
8280
index?: SimpleExpressionNode
81+
}
82+
83+
export interface ForIRNode extends BaseIRNode, IRFor {
84+
type: IRNodeTypes.FOR
85+
id: number
8386
keyProp?: SimpleExpressionNode
8487
render: BlockIRNode
8588
once: boolean
@@ -208,12 +211,39 @@ export interface ComponentSlotBlockIRNode extends BlockIRNode {
208211
// TODO slot props
209212
}
210213
export type ComponentSlots = Record<string, ComponentSlotBlockIRNode>
211-
export interface ComponentDynamicSlot {
214+
215+
export enum DynamicSlotType {
216+
BASIC,
217+
LOOP,
218+
CONDITIONAL,
219+
}
220+
221+
export interface ComponentBasicDynamicSlot {
222+
slotType: DynamicSlotType.BASIC
212223
name: SimpleExpressionNode
213224
fn: ComponentSlotBlockIRNode
214-
key?: string
225+
key?: number
215226
}
216227

228+
export interface ComponentLoopDynamicSlot {
229+
slotType: DynamicSlotType.LOOP
230+
name: SimpleExpressionNode
231+
fn: ComponentSlotBlockIRNode
232+
loop: IRFor
233+
}
234+
235+
export interface ComponentConditionalDynamicSlot {
236+
slotType: DynamicSlotType.CONDITIONAL
237+
condition: SimpleExpressionNode
238+
positive: ComponentBasicDynamicSlot
239+
negative?: ComponentBasicDynamicSlot | ComponentConditionalDynamicSlot
240+
}
241+
242+
export type ComponentDynamicSlot =
243+
| ComponentBasicDynamicSlot
244+
| ComponentLoopDynamicSlot
245+
| ComponentConditionalDynamicSlot
246+
217247
export interface CreateComponentIRNode extends BaseIRNode {
218248
type: IRNodeTypes.CREATE_COMPONENT_NODE
219249
id: number

0 commit comments

Comments
 (0)