From 2e99a89eadafb203aa12d3aadb560261a853b186 Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Sat, 11 May 2024 15:40:25 +0800 Subject: [PATCH] feat(compiler-vapor, runtime-vapor): implement slots + v-for related #154 --- .../src/generators/component.ts | 57 ++++++++++++-- packages/compiler-vapor/src/ir.ts | 9 +++ .../compiler-vapor/src/transforms/vSlot.ts | 9 ++- packages/runtime-vapor/src/apiCreateFor.ts | 77 +++++++++++-------- packages/runtime-vapor/src/index.ts | 2 +- 5 files changed, 113 insertions(+), 41 deletions(-) diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 5b91a8446..89e583d46 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -21,7 +21,11 @@ import { } from './utils' import { genExpression } from './expression' import { genPropKey } from './prop' -import { createSimpleExpression } from '@vue/compiler-dom' +import { + type SimpleExpressionNode, + createForLoopParams, + createSimpleExpression, +} from '@vue/compiler-dom' import { genEventHandler } from './event' import { genDirectiveModifiers, genDirectivesForElement } from './directive' import { genModelHandler } from './modelValue' @@ -158,13 +162,50 @@ function genDynamicSlots( ) { const slotsExpr = genMulti( dynamicSlots.length > 1 ? SEGMENTS_ARRAY_NEWLINE : SEGMENTS_ARRAY, - ...dynamicSlots.map(({ name, fn }) => - genMulti( - SEGMENTS_OBJECT_NEWLINE, - ['name: ', ...genExpression(name, context)], - ['fn: ', ...genBlock(fn, context)], - ), - ), + ...dynamicSlots.map(slot => { + const { name, fn, forResult } = slot + return forResult + ? genForSlot(slot, context) + : genMulti( + SEGMENTS_OBJECT_NEWLINE, + ['name: ', ...genExpression(name, context)], + ['fn: ', ...genBlock(fn, context)], + ) + }), ) return ['() => ', ...slotsExpr] } + +function genForSlot(slot: ComponentDynamicSlot, context: CodegenContext) { + const { name, fn, forResult } = slot + const { value, key, index, source } = forResult! + const rawValue = value && value.content + const rawKey = key && key.content + const rawIndex = index && index.content + + const idMap: Record = {} + if (rawValue) idMap[rawValue] = rawValue + if (rawKey) idMap[rawKey] = rawKey + if (rawIndex) idMap[rawIndex] = rawIndex + const slotExpr = genMulti( + SEGMENTS_OBJECT_NEWLINE, + ['name: ', ...context.withId(() => genExpression(name, context), idMap)], + ['fn: ', ...context.withId(() => genBlock(fn, context), idMap)], + ) + return [ + ...genCall( + context.vaporHelper('createForSlots'), + ['() => (', ...genExpression(source, context), ')'], + [ + `(`, + [value, key, index] + .filter(Boolean) + .map(exp => exp?.content) + .join(', '), + ') => (', + ...slotExpr, + ')', + ], + ), + ] +} diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 23e3114ec..08a19a21e 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -71,6 +71,14 @@ export interface IfIRNode extends BaseIRNode { negative?: BlockIRNode | IfIRNode } +export type VaporForParseResult = { + source: SimpleExpressionNode + value: SimpleExpressionNode | undefined + key: SimpleExpressionNode | undefined + index: SimpleExpressionNode | undefined + finalized: boolean +} + export interface ForIRNode extends BaseIRNode { type: IRNodeTypes.FOR id: number @@ -207,6 +215,7 @@ export interface ComponentDynamicSlot { name: SimpleExpressionNode fn: ComponentSlotBlockIRNode key?: string + forResult?: VaporForParseResult } export interface CreateComponentIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts index 8d4554d9e..4a4a1b42b 100644 --- a/packages/compiler-vapor/src/transforms/vSlot.ts +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@ -10,7 +10,12 @@ import { } from '@vue/compiler-core' import type { NodeTransform, TransformContext } from '../transform' import { newBlock } from './utils' -import { type BlockIRNode, DynamicFlag, type VaporDirectiveNode } from '../ir' +import { + type BlockIRNode, + DynamicFlag, + type VaporDirectiveNode, + type VaporForParseResult, +} from '../ir' import { findDir, resolveExpression } from '../utils' // TODO dynamic slots @@ -93,9 +98,11 @@ export const transformVSlot: NodeTransform = (node, context) => { slots[slotName] = block } } else { + const vFor = findDir(node, 'for') dynamicSlots.push({ name: arg, fn: block, + forResult: vFor?.forParseResult as VaporForParseResult, }) } return () => onExit() diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 362ac762c..d65cba55d 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -5,6 +5,7 @@ import { renderEffect } from './renderEffect' import { type Block, type Fragment, fragmentKey } from './apiRender' import { warn } from './warning' import { componentKey } from './component' +import type { DynamicSlot } from './componentSlots' interface ForBlock extends Fragment { scope: EffectScope @@ -299,44 +300,58 @@ export const createFor = ( remove(nodes, parent!) scope.stop() } +} - function getLength(source: any): number { - if (isArray(source) || isString(source)) { - return source.length - } else if (typeof source === 'number') { - if (__DEV__ && !Number.isInteger(source)) { - warn(`The v-for range expect an integer value but got ${source}.`) - } - return source - } else if (isObject(source)) { - if (source[Symbol.iterator as any]) { - return Array.from(source as Iterable).length - } else { - return Object.keys(source).length - } +export const createForSlots = ( + src: () => any[] | Record | number | Set | Map, + getSlot: (item: any, key: any, index?: number) => DynamicSlot, +) => { + const source = src() + const sourceLength = getLength(source) + const slots = new Array(sourceLength) + for (let i = 0; i < sourceLength; i++) { + const [item, key, index] = getItem(source, i) + slots[i] = getSlot(item, key, index) + } + return slots +} + +function getLength(source: any): number { + if (isArray(source) || isString(source)) { + return source.length + } else if (typeof source === 'number') { + if (__DEV__ && !Number.isInteger(source)) { + warn(`The v-for range expect an integer value but got ${source}.`) + } + return source + } else if (isObject(source)) { + if (source[Symbol.iterator as any]) { + return Array.from(source as Iterable).length + } else { + return Object.keys(source).length } - return 0 } + return 0 +} - function getItem( - source: any, - idx: number, - ): [item: any, key: any, index?: number] { - if (isArray(source) || isString(source)) { +function getItem( + source: any, + idx: number, +): [item: any, key: any, index?: number] { + if (isArray(source) || isString(source)) { + return [source[idx], idx, undefined] + } else if (typeof source === 'number') { + return [idx + 1, idx, undefined] + } else if (isObject(source)) { + if (source && source[Symbol.iterator as any]) { + source = Array.from(source as Iterable) return [source[idx], idx, undefined] - } else if (typeof source === 'number') { - return [idx + 1, idx, undefined] - } else if (isObject(source)) { - if (source && source[Symbol.iterator as any]) { - source = Array.from(source as Iterable) - return [source[idx], idx, undefined] - } else { - const key = Object.keys(source)[idx] - return [source[key], key, idx] - } + } else { + const key = Object.keys(source)[idx] + return [source[key], key, idx] } - return null! } + return null! } function normalizeAnchor(node: Block): Node { diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index b5f4235c4..29c103983 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -123,7 +123,7 @@ export { type AppContext, } from './apiCreateVaporApp' export { createIf } from './apiCreateIf' -export { createFor } from './apiCreateFor' +export { createFor, createForSlots } from './apiCreateFor' export { createComponent } from './apiCreateComponent' export { resolveComponent, resolveDirective } from './helpers/resolveAssets'