diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
index ff692bbf6..b5075babe 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
@@ -17,6 +17,78 @@ export function render(_ctx) {
}"
`;
+exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createForSlots as _createForSlots, template as _template } from 'vue/vapor';
+const t0 = _template("foo")
+
+export function render(_ctx) {
+ const _component_Comp = _resolveComponent("Comp")
+ const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (item) => ({
+ name: item,
+ fn: () => {
+ const n0 = t0()
+ return n0
+ }
+ }))], true)
+ return n2
+}"
+`;
+
+exports[`compiler: transform slot > dynamic slots name w/ v-for and provide absent key 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createForSlots as _createForSlots, template as _template } from 'vue/vapor';
+const t0 = _template("foo")
+
+export function render(_ctx) {
+ const _component_Comp = _resolveComponent("Comp")
+ const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (_, __, index) => ({
+ name: index,
+ fn: () => {
+ const n0 = t0()
+ return n0
+ }
+ }))], true)
+ return n2
+}"
+`;
+
+exports[`compiler: transform slot > dynamic slots name w/ v-if / v-else[-if] 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
+const t0 = _template("condition slot")
+const t1 = _template("another condition")
+const t2 = _template("else condition")
+
+export function render(_ctx) {
+ const _component_Comp = _resolveComponent("Comp")
+ const n6 = _createComponent(_component_Comp, null, null, () => [_ctx.condition
+ ? {
+ name: "condition",
+ fn: () => {
+ const n0 = t0()
+ return n0
+ },
+ key: "0"
+ }
+ : _ctx.anotherCondition
+ ? {
+ name: "condition",
+ fn: () => {
+ const n2 = t1()
+ return n2
+ },
+ key: "1"
+ }
+ : {
+ name: "condition",
+ fn: () => {
+ const n4 = t2()
+ return n4
+ },
+ key: "2"
+ }], true)
+ return n6
+}"
+`;
+
exports[`compiler: transform slot > implicit default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("
")
diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
index c98b75538..ce09fb646 100644
--- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
@@ -1,5 +1,6 @@
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
import {
+ DynamicSlotType,
IRNodeTypes,
transformChildren,
transformElement,
@@ -126,6 +127,112 @@ describe('compiler: transform slot', () => {
])
})
+ test('dynamic slots name w/ v-for', () => {
+ const { ir, code } = compileWithSlots(
+ `
+ foo
+ `,
+ )
+ expect(code).toMatchSnapshot()
+ expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
+ expect(ir.block.operation).toMatchObject([
+ {
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ tag: 'Comp',
+ slots: undefined,
+ dynamicSlots: [
+ {
+ name: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'item',
+ isStatic: false,
+ },
+ fn: { type: IRNodeTypes.BLOCK },
+ loop: {
+ source: { content: 'list' },
+ value: { content: 'item' },
+ key: undefined,
+ index: undefined,
+ },
+ },
+ ],
+ },
+ ])
+ })
+
+ test('dynamic slots name w/ v-for and provide absent key', () => {
+ const { ir, code } = compileWithSlots(
+ `
+ foo
+ `,
+ )
+ expect(code).toMatchSnapshot()
+ expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
+ expect(ir.block.operation).toMatchObject([
+ {
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ tag: 'Comp',
+ slots: undefined,
+ dynamicSlots: [
+ {
+ name: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'index',
+ isStatic: false,
+ },
+ fn: { type: IRNodeTypes.BLOCK },
+ loop: {
+ source: { content: 'list' },
+ value: undefined,
+ key: undefined,
+ index: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ },
+ },
+ },
+ ],
+ },
+ ])
+ })
+
+ test('dynamic slots name w/ v-if / v-else[-if]', () => {
+ const { ir, code } = compileWithSlots(
+ `
+ condition slot
+ another condition
+ else condition
+ `,
+ )
+ expect(code).toMatchSnapshot()
+ expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
+ expect(ir.block.operation).toMatchObject([
+ {
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ tag: 'Comp',
+ slots: undefined,
+ dynamicSlots: [
+ {
+ slotType: DynamicSlotType.CONDITIONAL,
+ condition: { content: 'condition' },
+ positive: {
+ slotType: DynamicSlotType.BASIC,
+ key: 0,
+ },
+ negative: {
+ slotType: DynamicSlotType.CONDITIONAL,
+ condition: { content: 'anotherCondition' },
+ positive: {
+ slotType: DynamicSlotType.BASIC,
+ key: 1,
+ },
+ negative: { slotType: DynamicSlotType.BASIC, key: 2 },
+ },
+ },
+ ],
+ },
+ ])
+ })
+
describe('errors', () => {
test('error on extraneous children w/ named default slot', () => {
const onError = vi.fn()
diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts
index 74602d70e..4f5f5477e 100644
--- a/packages/compiler-vapor/src/generators/component.ts
+++ b/packages/compiler-vapor/src/generators/component.ts
@@ -1,9 +1,13 @@
import { camelize, extend, isArray } from '@vue/shared'
import type { CodegenContext } from '../generate'
import {
+ type ComponentBasicDynamicSlot,
+ type ComponentConditionalDynamicSlot,
type ComponentDynamicSlot,
+ type ComponentLoopDynamicSlot,
type ComponentSlots,
type CreateComponentIRNode,
+ DynamicSlotType,
IRDynamicPropsKind,
type IRProp,
type IRProps,
@@ -15,6 +19,8 @@ import {
DELIMITERS_ARRAY_NEWLINE,
DELIMITERS_OBJECT,
DELIMITERS_OBJECT_NEWLINE,
+ INDENT_END,
+ INDENT_START,
NEWLINE,
genCall,
genMulti,
@@ -155,13 +161,90 @@ function genDynamicSlots(
) {
const slotsExpr = genMulti(
dynamicSlots.length > 1 ? DELIMITERS_ARRAY_NEWLINE : DELIMITERS_ARRAY,
- ...dynamicSlots.map(({ name, fn }) =>
- genMulti(
- DELIMITERS_OBJECT_NEWLINE,
- ['name: ', ...genExpression(name, context)],
- ['fn: ', ...genBlock(fn, context)],
- ),
- ),
+ ...dynamicSlots.map(slot => genDynamicSlot(slot, context)),
)
return ['() => ', ...slotsExpr]
}
+
+function genDynamicSlot(
+ slot: ComponentDynamicSlot,
+ context: CodegenContext,
+): CodeFragment[] {
+ switch (slot.slotType) {
+ case DynamicSlotType.BASIC:
+ return genBasicDynamicSlot(slot, context)
+ case DynamicSlotType.LOOP:
+ return genLoopSlot(slot, context)
+ case DynamicSlotType.CONDITIONAL:
+ return genConditionalSlot(slot, context)
+ }
+}
+
+function genBasicDynamicSlot(
+ slot: ComponentBasicDynamicSlot,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { name, fn, key } = slot
+ return genMulti(
+ DELIMITERS_OBJECT_NEWLINE,
+ ['name: ', ...genExpression(name, context)],
+ ['fn: ', ...genBlock(fn, context)],
+ ...(key !== undefined ? [`key: "${key}"`] : []),
+ )
+}
+
+function genLoopSlot(
+ slot: ComponentLoopDynamicSlot,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { name, fn, loop } = slot
+ const { value, key, index, source } = loop
+ 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(
+ DELIMITERS_OBJECT_NEWLINE,
+ ['name: ', ...context.withId(() => genExpression(name, context), idMap)],
+ ['fn: ', ...context.withId(() => genBlock(fn, context), idMap)],
+ )
+ return [
+ ...genCall(
+ context.vaporHelper('createForSlots'),
+ genExpression(source, context),
+ [
+ ...genMulti(
+ ['(', ')', ', '],
+ rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined,
+ rawKey ? rawKey : rawIndex ? '__' : undefined,
+ rawIndex,
+ ),
+ ' => (',
+ ...slotExpr,
+ ')',
+ ],
+ ),
+ ]
+}
+
+function genConditionalSlot(
+ slot: ComponentConditionalDynamicSlot,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { condition, positive, negative } = slot
+ return [
+ ...genExpression(condition, context),
+ INDENT_START,
+ NEWLINE,
+ '? ',
+ ...genDynamicSlot(positive, context),
+ NEWLINE,
+ ': ',
+ ...(negative ? [...genDynamicSlot(negative, context)] : ['void 0']),
+ INDENT_END,
+ ]
+}
diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts
index c86e7c2ea..83e03126f 100644
--- a/packages/compiler-vapor/src/ir.ts
+++ b/packages/compiler-vapor/src/ir.ts
@@ -73,13 +73,16 @@ export interface IfIRNode extends BaseIRNode {
once?: boolean
}
-export interface ForIRNode extends BaseIRNode {
- type: IRNodeTypes.FOR
- id: number
+export interface IRFor {
source: SimpleExpressionNode
value?: SimpleExpressionNode
key?: SimpleExpressionNode
index?: SimpleExpressionNode
+}
+
+export interface ForIRNode extends BaseIRNode, IRFor {
+ type: IRNodeTypes.FOR
+ id: number
keyProp?: SimpleExpressionNode
render: BlockIRNode
once: boolean
@@ -208,12 +211,39 @@ export interface ComponentSlotBlockIRNode extends BlockIRNode {
// TODO slot props
}
export type ComponentSlots = Record
-export interface ComponentDynamicSlot {
+
+export enum DynamicSlotType {
+ BASIC,
+ LOOP,
+ CONDITIONAL,
+}
+
+export interface ComponentBasicDynamicSlot {
+ slotType: DynamicSlotType.BASIC
name: SimpleExpressionNode
fn: ComponentSlotBlockIRNode
- key?: string
+ key?: number
}
+export interface ComponentLoopDynamicSlot {
+ slotType: DynamicSlotType.LOOP
+ name: SimpleExpressionNode
+ fn: ComponentSlotBlockIRNode
+ loop: IRFor
+}
+
+export interface ComponentConditionalDynamicSlot {
+ slotType: DynamicSlotType.CONDITIONAL
+ condition: SimpleExpressionNode
+ positive: ComponentBasicDynamicSlot
+ negative?: ComponentBasicDynamicSlot | ComponentConditionalDynamicSlot
+}
+
+export type ComponentDynamicSlot =
+ | ComponentBasicDynamicSlot
+ | ComponentLoopDynamicSlot
+ | ComponentConditionalDynamicSlot
+
export interface CreateComponentIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_COMPONENT_NODE
id: number
diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts
index ffd093a65..7aeb2abc9 100644
--- a/packages/compiler-vapor/src/transforms/vSlot.ts
+++ b/packages/compiler-vapor/src/transforms/vSlot.ts
@@ -10,7 +10,15 @@ 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,
+ type ComponentBasicDynamicSlot,
+ type ComponentConditionalDynamicSlot,
+ DynamicFlag,
+ DynamicSlotType,
+ type IRFor,
+ type VaporDirectiveNode,
+} from '../ir'
import { findDir, resolveExpression } from '../utils'
// TODO dynamic slots
@@ -69,6 +77,9 @@ export const transformVSlot: NodeTransform = (node, context) => {
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
+ const vFor = findDir(node, 'for')
+ const vIf = findDir(node, 'if')
+ const vElse = findDir(node, /^else(-if)?$/, true /* allowEmpty */)
const slots = context.slots!
const dynamicSlots = context.dynamicSlots!
@@ -79,7 +90,7 @@ export const transformVSlot: NodeTransform = (node, context) => {
arg &&= resolveExpression(arg)
- if (!arg || arg.isStatic) {
+ if ((!arg || arg.isStatic) && !vFor && !vIf && !vElse) {
const slotName = arg ? arg.content : 'default'
if (slots[slotName]) {
@@ -92,12 +103,75 @@ export const transformVSlot: NodeTransform = (node, context) => {
} else {
slots[slotName] = block
}
+ } else if (vIf) {
+ dynamicSlots.push({
+ slotType: DynamicSlotType.CONDITIONAL,
+ condition: vIf.exp!,
+ positive: {
+ slotType: DynamicSlotType.BASIC,
+ name: arg!,
+ fn: block,
+ key: 0,
+ },
+ })
+ } else if (vElse) {
+ const vIfIR = dynamicSlots[dynamicSlots.length - 1]
+ if (vIfIR.slotType === DynamicSlotType.CONDITIONAL) {
+ let ifNode = vIfIR
+ while (
+ ifNode.negative &&
+ ifNode.negative.slotType === DynamicSlotType.CONDITIONAL
+ )
+ ifNode = ifNode.negative
+ const negative:
+ | ComponentBasicDynamicSlot
+ | ComponentConditionalDynamicSlot = vElse.exp
+ ? {
+ slotType: DynamicSlotType.CONDITIONAL,
+ condition: vElse.exp,
+ positive: {
+ slotType: DynamicSlotType.BASIC,
+ name: arg!,
+ fn: block,
+ key: ifNode.positive.key! + 1,
+ },
+ }
+ : {
+ slotType: DynamicSlotType.BASIC,
+ name: arg!,
+ fn: block,
+ key: ifNode.positive.key! + 1,
+ }
+ ifNode.negative = negative
+ } else {
+ context.options.onError(
+ createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc),
+ )
+ }
+ } else if (vFor) {
+ if (vFor.forParseResult) {
+ dynamicSlots.push({
+ slotType: DynamicSlotType.LOOP,
+ name: arg!,
+ fn: block,
+ loop: vFor.forParseResult as IRFor,
+ })
+ } else {
+ context.options.onError(
+ createCompilerError(
+ ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ vFor.loc,
+ ),
+ )
+ }
} else {
dynamicSlots.push({
- name: arg,
+ slotType: DynamicSlotType.BASIC,
+ name: arg!,
fn: block,
})
}
+
return () => onExit()
}
}
diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts
index e487e14ac..fd7274c56 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
@@ -301,44 +302,57 @@ 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 function createForSlots(
+ source: any[] | Record | number | Set | Map,
+ getSlot: (item: any, key: any, index?: number) => DynamicSlot,
+): DynamicSlot[] {
+ 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/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index b702ff526..6b0db6cd4 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -53,11 +53,12 @@ export function initSlots(
slots = shallowReactive(slots)
const dynamicSlotKeys: Record = {}
firstEffect(instance, () => {
- const _dynamicSlots = callWithAsyncErrorHandling(
- dynamicSlots,
- instance,
- VaporErrorCodes.RENDER_FUNCTION,
- )
+ const _dynamicSlots: (DynamicSlot | DynamicSlot[])[] =
+ callWithAsyncErrorHandling(
+ dynamicSlots,
+ instance,
+ VaporErrorCodes.RENDER_FUNCTION,
+ )
for (let i = 0; i < _dynamicSlots.length; i++) {
const slot = _dynamicSlots[i]
// array of dynamic slot generated by
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index 959db2b69..62c798d41 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -126,7 +126,7 @@ export {
type FunctionPlugin,
} 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'