v-slot Directive
Consider the following components:
<!-- Comp.vue -->
<template>
<div>
<slot name="header" :count="1" />
<slot />
<slot name="footer" />
</div>
</template><!-- Parent.vue -->
<script setup>
import Comp from "./Comp.vue";
</script>
<template>
<Comp>
<template #header="{ count }">Header: {{ count }}</template>
<template #default>Default Content</template>
<template #footer>Footer</template>
</Comp>
</template>Compilation Result and Overview
Implicit Default Slot
<template>
<Comp>
<div></div>
</Comp>
</template>Compilation result:
import {
resolveComponent as _resolveComponent,
createComponent as _createComponent,
template as _template,
} from "vue/vapor";
const t0 = _template("<div></div>");
function _sfc_render(_ctx) {
const _component_Comp = _resolveComponent("Comp");
const n1 = _createComponent(_component_Comp, null, [
{
default: () => {
const n0 = t0();
return n0;
},
},
], true);
return n1;
}Slots are passed as an array to the 3rd argument of createComponent.
Static slots are in object format, and each slot is defined as a function.
Named Slots
<template>
<Comp>
<template #one>foo</template>
<template #default>
bar
<span></span>
</template>
</Comp>
</template>Compilation result:
const n4 = _createComponent(_component_Comp, null, [
{
one: () => {
const n0 = t0();
return n0;
},
default: () => {
const n2 = t1();
const n3 = t2();
return [n2, n3];
},
},
], true);Slot Props (Scoped Slots)
<template>
<Comp v-slot="{ foo }">
{{ foo + bar }}
</Comp>
</template>Compilation result:
const n1 = _createComponent(_component_Comp, null, [
{
default: _withDestructure(
({ foo }) => [foo],
(_ctx0) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar]);
return n0;
}
),
},
], true);withDestructure is used to destructure slot props and convert them to array format._ctx0[0] corresponds to foo.
Dynamic Slot Name
<template>
<Comp>
<template #[name]>foo</template>
</Comp>
</template>Compilation result:
const n2 = _createComponent(_component_Comp, null, [
() => ({
name: _ctx.name,
fn: () => {
const n0 = t0();
return n0;
},
}),
], true);For dynamic slot names, it's wrapped in a function that returns an object with name and fn properties.
Conditional Slots (v-if)
<template>
<Comp>
<template v-if="condition" #condition>condition slot</template>
<template v-else-if="anotherCondition" #condition="{ foo, bar }">
another condition
</template>
<template v-else #condition>else condition</template>
</Comp>
</template>Compilation result:
const n6 = _createComponent(_component_Comp, null, [
() =>
_ctx.condition
? {
name: "condition",
fn: () => {
const n0 = t0();
return n0;
},
}
: _ctx.anotherCondition
? {
name: "condition",
fn: _withDestructure(({ foo, bar }) => [foo, bar], (_ctx0) => {
const n2 = t1();
return n2;
}),
}
: {
name: "condition",
fn: () => {
const n4 = t2();
return n4;
},
},
], true);Conditional branches are compiled into ternary operators.
Loop Slots (v-for)
<template>
<Comp>
<template v-for="item in list" #[item]="{ bar }">foo</template>
</Comp>
</template>Compilation result:
const n2 = _createComponent(_component_Comp, null, [
() =>
_createForSlots(_ctx.list, (item) => ({
name: item,
fn: _withDestructure(({ bar }) => [bar], (_ctx0) => {
const n0 = t0();
return n0;
}),
})),
], true);createForSlots is used to generate slots with a loop.
Reading the Compiler
IR
Let's look at the slot-related IRs.
34export enum IRSlotType {
35 STATIC,
36 DYNAMIC,
37 LOOP,
38 CONDITIONAL,
39}
40export type IRSlotsStatic = {
41 slotType: IRSlotType.STATIC
42 slots: Record<string, SlotBlockIRNode>
43}
44export interface IRSlotDynamicBasic {
45 slotType: IRSlotType.DYNAMIC
46 name: SimpleExpressionNode
47 fn: SlotBlockIRNode
48}
49export interface IRSlotDynamicLoop {
50 slotType: IRSlotType.LOOP
51 name: SimpleExpressionNode
52 fn: SlotBlockIRNode
53 loop: IRFor
54}
55export interface IRSlotDynamicConditional {
56 slotType: IRSlotType.CONDITIONAL
57 condition: SimpleExpressionNode
58 positive: IRSlotDynamicBasic
59 negative?: IRSlotDynamicBasic | IRSlotDynamicConditional
60}
61
62export type IRSlotDynamic =
63 | IRSlotDynamicBasic
64 | IRSlotDynamicLoop
65 | IRSlotDynamicConditional
66export type IRSlots = IRSlotsStatic | IRSlotDynamicexport enum IRSlotType {
STATIC,
DYNAMIC,
LOOP,
CONDITIONAL,
}
export type IRSlots =
| IRSlotsStatic
| IRSlotDynamicBasic
| IRSlotDynamicLoop
| IRSlotDynamicConditional;
export interface IRSlotsStatic {
slotType: IRSlotType.STATIC;
slots: Record<string, SlotBlockIRNode>;
}
export interface IRSlotDynamicBasic {
slotType: IRSlotType.DYNAMIC;
name: SimpleExpressionNode;
fn: SlotBlockIRNode;
}
export interface IRSlotDynamicLoop {
slotType: IRSlotType.LOOP;
name: SimpleExpressionNode;
fn: SlotBlockIRNode;
loop: IRFor;
}
export interface IRSlotDynamicConditional {
slotType: IRSlotType.CONDITIONAL;
condition: SimpleExpressionNode;
positive: IRSlotDynamicBasic | IRSlotDynamicConditional;
negative?: IRSlotDynamicBasic | IRSlotDynamicConditional;
}There are 4 types of slots:
- STATIC: Static slot name
- DYNAMIC: Dynamic slot name
- LOOP: Slots with v-for
- CONDITIONAL: Slots with v-if
Transformer
transformVSlot handles 2 cases:
- Component slot:
<Comp v-slot:default>format - Template slot:
<template #foo>format
28export const transformVSlot: NodeTransform = (node, context) => {
29 if (node.type !== NodeTypes.ELEMENT) return
30
31 const dir = findDir(node, 'slot', true)
32 const { tagType, children } = node
33 const { parent } = context
34
35 const isComponent = tagType === ElementTypes.COMPONENT
36 const isSlotTemplate =
37 isTemplateNode(node) &&
38 parent &&
39 parent.node.type === NodeTypes.ELEMENT &&
40 parent.node.tagType === ElementTypes.COMPONENT
41
42 if (isComponent && children.length) {
43 return transformComponentSlot(
44 node,
45 dir,
46 context as TransformContext<ElementNode>,
47 )
48 } else if (isSlotTemplate && dir) {
49 return transformTemplateSlot(
50 node,
51 dir,
52 context as TransformContext<ElementNode>,
53 )
54 } else if (!isComponent && dir) {
55 context.options.onError(
56 createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, dir.loc),
57 )
58 }
59}export const transformVSlot: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT) return;
const dir = findDir(node, "slot", true);
const { tagType, children } = node;
const { parent } = context;
const isComponent = tagType === ElementTypes.COMPONENT;
const isSlotTemplate =
isTemplateNode(node) &&
parent &&
parent.node.type === NodeTypes.ELEMENT &&
parent.node.tagType === ElementTypes.COMPONENT;
if (isComponent && children.length) {
return transformComponentSlot(node, dir, context);
} else if (isSlotTemplate && dir) {
return transformTemplateSlot(node, dir, context);
}
};transformTemplateSlot handles combinations with v-if, v-else-if, and v-for:
if (!vFor && !vIf && !vElse) {
// Static slot
registerSlot(slots, arg, block);
} else if (vIf) {
// Conditional slot
registerDynamicSlot(slots, {
slotType: IRSlotType.CONDITIONAL,
condition: vIf.exp!,
positive: {
slotType: IRSlotType.DYNAMIC,
name: arg!,
fn: block,
},
});
} else if (vFor) {
// Loop slot
registerDynamicSlot(slots, {
slotType: IRSlotType.LOOP,
name: arg!,
fn: block,
loop: vFor.forParseResult as IRFor,
});
}Codegen
The genRawSlots function handles slot code generation.
function genRawSlots(slots: IRSlots[], context: CodegenContext) {
if (!slots.length) return;
return genMulti(
DELIMITERS_ARRAY_NEWLINE,
...slots.map((slot) =>
slot.slotType === IRSlotType.STATIC
? genStaticSlots(slot, context)
: genDynamicSlot(slot, context, true)
)
);
}Destructuring of slot props is wrapped with withDestructure:
if (isDestructureAssignment) {
blockFn = genCall(
context.vaporHelper("withDestructure"),
["(", rawProps, ") => ", ...genMulti(DELIMITERS_ARRAY, ...idsOfProps)],
blockFn
);
}v-slot in Vapor Mode supports the following features:
- Static/dynamic slot names
- Scoped slots (slot props)
- Combinations with v-if/v-else-if/v-else
- Combinations with v-for
- Nested slots
By using withDestructure and array-format contexts (_ctx0),
an efficient and reactive slot implementation is achieved.
