Skip to content

Commit

Permalink
feat(compiler-vapor, runtime-vapor): implement slots + v-for
Browse files Browse the repository at this point in the history
related #154
  • Loading branch information
Doctor-wu committed May 11, 2024
1 parent 15fb060 commit 2e99a89
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 41 deletions.
57 changes: 49 additions & 8 deletions packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import {
} from './utils'
import { genExpression } from './expression'
import { genPropKey } from './prop'
import { createSimpleExpression } from '@vue/compiler-dom'
import {
type SimpleExpressionNode,

Check failure on line 25 in packages/compiler-vapor/src/generators/component.ts

View workflow job for this annotation

GitHub Actions / lint-and-test-dts

'SimpleExpressionNode' is declared but its value is never read.
createForLoopParams,

Check failure on line 26 in packages/compiler-vapor/src/generators/component.ts

View workflow job for this annotation

GitHub Actions / lint-and-test-dts

'createForLoopParams' is declared but its value is never read.
createSimpleExpression,
} from '@vue/compiler-dom'
import { genEventHandler } from './event'
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
import { genModelHandler } from './modelValue'
Expand Down Expand Up @@ -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<string, string> = {}
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,
')',
],
),
]
}
9 changes: 9 additions & 0 deletions packages/compiler-vapor/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -207,6 +215,7 @@ export interface ComponentDynamicSlot {
name: SimpleExpressionNode
fn: ComponentSlotBlockIRNode
key?: string
forResult?: VaporForParseResult
}

export interface CreateComponentIRNode extends BaseIRNode {
Expand Down
9 changes: 8 additions & 1 deletion packages/compiler-vapor/src/transforms/vSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
77 changes: 46 additions & 31 deletions packages/runtime-vapor/src/apiCreateFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<any>).length
} else {
return Object.keys(source).length
}
export const createForSlots = (
src: () => any[] | Record<any, any> | number | Set<any> | Map<any, any>,
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<any>).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<any>)
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<any>)
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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-vapor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down

0 comments on commit 2e99a89

Please sign in to comment.