Component
Consider the following components:
<!-- Child.vue -->
<script setup>
defineProps<{ msg: string }>();
</script>
<template>
<p>{{ msg }}</p>
</template><!-- Parent.vue -->
<script setup>
import Child from "./Child.vue";
</script>
<template>
<Child msg="Hello!" />
</template>Compilation Result and Overview
Basic Component
The compilation result of Parent.vue is as follows:
import { createComponent as _createComponent } from "vue/vapor";
function _sfc_render(_ctx) {
const n0 = _createComponent(_ctx.Child, { msg: () => "Hello!" }, null, true);
return n0;
}createComponent takes the following arguments:
- comp: The component definition
- rawProps: Array of props
- slots: Array of slots
- singleRoot: Whether it's a single root (for fallthrough attributes)
Resolution from Assets
For globally registered components:
<template>
<Foo />
</template>import {
resolveComponent as _resolveComponent,
createComponent as _createComponent,
} from "vue/vapor";
function _sfc_render(_ctx) {
const _component_Foo = _resolveComponent("Foo");
const n0 = _createComponent(_component_Foo, null, null, true);
return n0;
}resolveComponent resolves globally registered components.
Reading the Compiler
IR
Let's look at the CREATE_COMPONENT_NODE IR.
186export interface CreateComponentIRNode extends BaseIRNode {
187 type: IRNodeTypes.CREATE_COMPONENT_NODE
188 id: number
189 tag: string
190 props: IRProps[]
191 slots: IRSlots[]
192 asset: boolean
193 root: boolean
194 once: boolean
195}export interface CreateComponentIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_COMPONENT_NODE;
id: number;
tag: string;
props: IRProps[];
slots: IRSlots[];
asset: boolean;
root: boolean;
once: boolean;
}tag: The component tag nameprops: Array of propsslots: Array of slotsasset: Whether it's a globally registered componentroot: Whether it's the root componentonce: When combined withv-once
Transformer
Within transformElement, it determines whether the node is a component.
55 const isComponent = tagType === ElementTypes.COMPONENT
56 const propsResult = buildProps(
57 node,
58 context as TransformContext<ElementNode>,
59 isComponent,
60 )
61
62 ;(isComponent ? transformComponentElement : transformNativeElement)(
63 tag,
64 propsResult,
65 context as TransformContext<ElementNode>,
66 )const { tag, tagType } = node;
const isComponent = tagType === ElementTypes.COMPONENT;
const propsResult = buildProps(
node,
context as TransformContext<ElementNode>,
isComponent
);
(isComponent ? transformComponentElement : transformNativeElement)(
tag,
propsResult,
context as TransformContext<ElementNode>
);In transformComponentElement:
context.registerOperation({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: context.reference(),
tag,
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
asset,
root,
slots: slotsResult,
once: context.inVOnce,
});Codegen
The genCreateComponent function handles code generation.
42export function genCreateComponent(
43 oper: CreateComponentIRNode,
44 context: CodegenContext,
45): CodeFragment[] {
46 const { vaporHelper } = context
47
48 const tag = genTag()
49 const { root, props, slots, once } = oper
50 const rawProps = genRawProps(props, context)
51 const rawSlots = genRawSlots(slots, context)
52
53 return [
54 NEWLINE,
55 `const n${oper.id} = `,
56 ...genCall(
57 vaporHelper('createComponent'),
58 tag,
59 rawProps,
60 rawSlots,
61 root ? 'true' : false,
62 once && 'true',
63 ),
64 ...genDirectivesForElement(oper.id, context),
65 ]
66
67 function genTag() {
68 if (oper.asset) {
69 return toValidAssetId(oper.tag, 'component')
70 } else {
71 return genExpression(
72 extend(createSimpleExpression(oper.tag, false), { ast: null }),
73 context,
74 )
75 }
76 }
77}export function genCreateComponent(
oper: CreateComponentIRNode,
context: CodegenContext
): CodeFragment[] {
const { vaporHelper } = context;
const tag = genTag();
const { root, props, slots, once } = oper;
const rawProps = genRawProps(props, context);
const rawSlots = genRawSlots(slots, context);
return [
NEWLINE,
`const n${oper.id} = `,
...genCall(
vaporHelper("createComponent"),
tag,
rawProps,
rawSlots,
root ? "true" : false,
once && "true"
),
...genDirectivesForElement(oper.id, context),
];
function genTag() {
if (oper.asset) {
return toValidAssetId(oper.tag, "component");
} else {
return genExpression(
extend(createSimpleExpression(oper.tag, false), { ast: null }),
context
);
}
}
}Props Generation
Props are wrapped as getter functions:
function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) {
return [
...genPropKey(prop, context),
": ",
...(prop.handler
? genEventHandler(context, prop.values[0])
: isStatic
? ["() => (", ...genExpression(prop.values[0], context), ")"]
: genExpression(prop.values[0], context)),
// ...
];
}This allows props to be lazily evaluated, enabling reactive updates.
Reading the Runtime
createComponent is implemented in apiCreateComponent.ts in runtime-vapor.
12export function createComponent(
13 comp: Component,
14 rawProps: RawProps | null = null,
15 slots: RawSlots | null = null,
16 singleRoot: boolean = false,
17 once: boolean = false,
18): ComponentInternalInstance {
19 const current = currentInstance!
20 const instance = createComponentInstance(
21 comp,
22 singleRoot ? withAttrs(rawProps) : rawProps,
23 slots,
24 once,
25 )
26 setupComponent(instance, singleRoot)
27
28 // register sub-component with current component for lifecycle management
29 current.comps.add(instance)
30
31 return instance
32}export function createComponent(
comp: Component,
rawProps: RawProps | null = null,
slots: RawSlots | null = null,
singleRoot: boolean = false,
once: boolean = false
): ComponentInternalInstance {
const current = currentInstance!;
const instance = createComponentInstance(
comp,
singleRoot ? withAttrs(rawProps) : rawProps,
slots,
once
);
setupComponent(instance, singleRoot);
// register sub-component with current component for lifecycle management
current.comps.add(instance);
return instance;
}Processing Flow
- createComponentInstance: Creates the component instance
- withAttrs: For
singleRoot, processes fallthrough attributes - setupComponent: Sets up the component (props, slots, render function)
- current.comps.add: Registers the child component with the parent
singleRoot and Fallthrough Attributes
When singleRoot: true, attributes passed from the parent are automatically applied to the child component's root element:
const instance = createComponentInstance(
comp,
singleRoot ? withAttrs(rawProps) : rawProps,
slots,
once
);Lifecycle Management
Child components are registered in the parent's comps Set:
current.comps.add(instance);This ensures that child components are properly destroyed when the parent component unmounts.
Components in Vapor Mode support the same API as the Virtual DOM version.
Props are wrapped as getter functions, enabling reactive updates.
The details of createComponentInstance and setupComponent will be covered in a separate chapter.
