Skip to content

Commit 2e99a89

Browse files
committed
feat(compiler-vapor, runtime-vapor): implement slots + v-for
related #154
1 parent 15fb060 commit 2e99a89

File tree

5 files changed

+113
-41
lines changed

5 files changed

+113
-41
lines changed

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

+49-8
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import {
2121
} from './utils'
2222
import { genExpression } from './expression'
2323
import { genPropKey } from './prop'
24-
import { createSimpleExpression } from '@vue/compiler-dom'
24+
import {
25+
type SimpleExpressionNode,
26+
createForLoopParams,
27+
createSimpleExpression,
28+
} from '@vue/compiler-dom'
2529
import { genEventHandler } from './event'
2630
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
2731
import { genModelHandler } from './modelValue'
@@ -158,13 +162,50 @@ function genDynamicSlots(
158162
) {
159163
const slotsExpr = genMulti(
160164
dynamicSlots.length > 1 ? SEGMENTS_ARRAY_NEWLINE : SEGMENTS_ARRAY,
161-
...dynamicSlots.map(({ name, fn }) =>
162-
genMulti(
163-
SEGMENTS_OBJECT_NEWLINE,
164-
['name: ', ...genExpression(name, context)],
165-
['fn: ', ...genBlock(fn, context)],
166-
),
167-
),
165+
...dynamicSlots.map(slot => {
166+
const { name, fn, forResult } = slot
167+
return forResult
168+
? genForSlot(slot, context)
169+
: genMulti(
170+
SEGMENTS_OBJECT_NEWLINE,
171+
['name: ', ...genExpression(name, context)],
172+
['fn: ', ...genBlock(fn, context)],
173+
)
174+
}),
168175
)
169176
return ['() => ', ...slotsExpr]
170177
}
178+
179+
function genForSlot(slot: ComponentDynamicSlot, context: CodegenContext) {
180+
const { name, fn, forResult } = slot
181+
const { value, key, index, source } = forResult!
182+
const rawValue = value && value.content
183+
const rawKey = key && key.content
184+
const rawIndex = index && index.content
185+
186+
const idMap: Record<string, string> = {}
187+
if (rawValue) idMap[rawValue] = rawValue
188+
if (rawKey) idMap[rawKey] = rawKey
189+
if (rawIndex) idMap[rawIndex] = rawIndex
190+
const slotExpr = genMulti(
191+
SEGMENTS_OBJECT_NEWLINE,
192+
['name: ', ...context.withId(() => genExpression(name, context), idMap)],
193+
['fn: ', ...context.withId(() => genBlock(fn, context), idMap)],
194+
)
195+
return [
196+
...genCall(
197+
context.vaporHelper('createForSlots'),
198+
['() => (', ...genExpression(source, context), ')'],
199+
[
200+
`(`,
201+
[value, key, index]
202+
.filter(Boolean)
203+
.map(exp => exp?.content)
204+
.join(', '),
205+
') => (',
206+
...slotExpr,
207+
')',
208+
],
209+
),
210+
]
211+
}

packages/compiler-vapor/src/ir.ts

+9
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ export interface IfIRNode extends BaseIRNode {
7171
negative?: BlockIRNode | IfIRNode
7272
}
7373

74+
export type VaporForParseResult = {
75+
source: SimpleExpressionNode
76+
value: SimpleExpressionNode | undefined
77+
key: SimpleExpressionNode | undefined
78+
index: SimpleExpressionNode | undefined
79+
finalized: boolean
80+
}
81+
7482
export interface ForIRNode extends BaseIRNode {
7583
type: IRNodeTypes.FOR
7684
id: number
@@ -207,6 +215,7 @@ export interface ComponentDynamicSlot {
207215
name: SimpleExpressionNode
208216
fn: ComponentSlotBlockIRNode
209217
key?: string
218+
forResult?: VaporForParseResult
210219
}
211220

212221
export interface CreateComponentIRNode extends BaseIRNode {

packages/compiler-vapor/src/transforms/vSlot.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import {
1010
} from '@vue/compiler-core'
1111
import type { NodeTransform, TransformContext } from '../transform'
1212
import { newBlock } from './utils'
13-
import { type BlockIRNode, DynamicFlag, type VaporDirectiveNode } from '../ir'
13+
import {
14+
type BlockIRNode,
15+
DynamicFlag,
16+
type VaporDirectiveNode,
17+
type VaporForParseResult,
18+
} from '../ir'
1419
import { findDir, resolveExpression } from '../utils'
1520

1621
// TODO dynamic slots
@@ -93,9 +98,11 @@ export const transformVSlot: NodeTransform = (node, context) => {
9398
slots[slotName] = block
9499
}
95100
} else {
101+
const vFor = findDir(node, 'for')
96102
dynamicSlots.push({
97103
name: arg,
98104
fn: block,
105+
forResult: vFor?.forParseResult as VaporForParseResult,
99106
})
100107
}
101108
return () => onExit()

packages/runtime-vapor/src/apiCreateFor.ts

+46-31
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { renderEffect } from './renderEffect'
55
import { type Block, type Fragment, fragmentKey } from './apiRender'
66
import { warn } from './warning'
77
import { componentKey } from './component'
8+
import type { DynamicSlot } from './componentSlots'
89

910
interface ForBlock extends Fragment {
1011
scope: EffectScope
@@ -299,44 +300,58 @@ export const createFor = (
299300
remove(nodes, parent!)
300301
scope.stop()
301302
}
303+
}
302304

303-
function getLength(source: any): number {
304-
if (isArray(source) || isString(source)) {
305-
return source.length
306-
} else if (typeof source === 'number') {
307-
if (__DEV__ && !Number.isInteger(source)) {
308-
warn(`The v-for range expect an integer value but got ${source}.`)
309-
}
310-
return source
311-
} else if (isObject(source)) {
312-
if (source[Symbol.iterator as any]) {
313-
return Array.from(source as Iterable<any>).length
314-
} else {
315-
return Object.keys(source).length
316-
}
305+
export const createForSlots = (
306+
src: () => any[] | Record<any, any> | number | Set<any> | Map<any, any>,
307+
getSlot: (item: any, key: any, index?: number) => DynamicSlot,
308+
) => {
309+
const source = src()
310+
const sourceLength = getLength(source)
311+
const slots = new Array(sourceLength)
312+
for (let i = 0; i < sourceLength; i++) {
313+
const [item, key, index] = getItem(source, i)
314+
slots[i] = getSlot(item, key, index)
315+
}
316+
return slots
317+
}
318+
319+
function getLength(source: any): number {
320+
if (isArray(source) || isString(source)) {
321+
return source.length
322+
} else if (typeof source === 'number') {
323+
if (__DEV__ && !Number.isInteger(source)) {
324+
warn(`The v-for range expect an integer value but got ${source}.`)
325+
}
326+
return source
327+
} else if (isObject(source)) {
328+
if (source[Symbol.iterator as any]) {
329+
return Array.from(source as Iterable<any>).length
330+
} else {
331+
return Object.keys(source).length
317332
}
318-
return 0
319333
}
334+
return 0
335+
}
320336

321-
function getItem(
322-
source: any,
323-
idx: number,
324-
): [item: any, key: any, index?: number] {
325-
if (isArray(source) || isString(source)) {
337+
function getItem(
338+
source: any,
339+
idx: number,
340+
): [item: any, key: any, index?: number] {
341+
if (isArray(source) || isString(source)) {
342+
return [source[idx], idx, undefined]
343+
} else if (typeof source === 'number') {
344+
return [idx + 1, idx, undefined]
345+
} else if (isObject(source)) {
346+
if (source && source[Symbol.iterator as any]) {
347+
source = Array.from(source as Iterable<any>)
326348
return [source[idx], idx, undefined]
327-
} else if (typeof source === 'number') {
328-
return [idx + 1, idx, undefined]
329-
} else if (isObject(source)) {
330-
if (source && source[Symbol.iterator as any]) {
331-
source = Array.from(source as Iterable<any>)
332-
return [source[idx], idx, undefined]
333-
} else {
334-
const key = Object.keys(source)[idx]
335-
return [source[key], key, idx]
336-
}
349+
} else {
350+
const key = Object.keys(source)[idx]
351+
return [source[key], key, idx]
337352
}
338-
return null!
339353
}
354+
return null!
340355
}
341356

342357
function normalizeAnchor(node: Block): Node {

packages/runtime-vapor/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export {
123123
type AppContext,
124124
} from './apiCreateVaporApp'
125125
export { createIf } from './apiCreateIf'
126-
export { createFor } from './apiCreateFor'
126+
export { createFor, createForSlots } from './apiCreateFor'
127127
export { createComponent } from './apiCreateComponent'
128128

129129
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'

0 commit comments

Comments
 (0)