Overview of Transformer Part 2
I split it because it got a bit long, but here's the continuation.
Although it's called an overview, before I realized it, I was reading the details of the specific implementation, but well, perhaps it's a good natural introduction (laughs).
Up to the previous page, we looked at the implementation that generates the template string passed as an argument to the template function.
Here, we'll look at index management of Block and so on.
registerTemplate
There is a function called registerTemplate in TransformContext.
131 registerTemplate(): number {
132 if (!this.template) return -1
133 const id = this.pushTemplate(this.template)
134 return (this.dynamic.template = id)
135 }This registerTemplate calls a function called pushTemplate.
123 pushTemplate(content: string): number {
124 const existing = this.ir.template.findIndex(
125 template => template === content,
126 )
127 if (existing !== -1) return existing
128 this.ir.template.push(content)
129 return this.ir.template.length - 1
130 }Templates (strings) are registered into this.ir.template (array).
this.ir is a RootIRNode.
87 constructor(
88 public ir: RootIRNode,That is, here.
56export interface RootIRNode {
57 type: IRNodeTypes.ROOT
58 node: RootNode
59 source: string
60 template: string[]And, this registerTemplate is called in three places.
- At the end of
transformNode(only when root)
264 if (context.node.type === NodeTypes.ROOT) {
265 context.registerTemplate()
266 }- After processing children in
transformChildren(only when it's a Fragment)
22 if (isFragment) {
23 childContext.reference()
24 childContext.registerTemplate()- When
context.enterBlockis called (onExit)
104 return () => {
105 // exit
106 this.registerTemplate()
107 this.block = block
108 this.template = template
109 this.dynamic = dynamic
110 this.childrenTemplate = childrenTemplate
111 this.slots = slots
112 isVFor && this.inVFor--
113 }context.enterBlock is a function called when entering a Block in transformVFor or transformVIf.
We'll look at this when we see the implementation of compiling v-for and v-if, but for now, it's fine to just grasp 1 and 2.
Regarding 1, there's nothing special, I think.
In the small component we're reading now, the template is registered here.
In other words, at this point, this.ir.template is in the state of ["<p>Hello, Vapor!</p>"].
If we have this, we know the index of the template, so,
const t0 = template("<p>Hello, Vapor!</p>");It seems we can generate such code. (We'll actually see this again during codegen.)
In the case of 2, the Fragment, it's when we write a template like:
<template>
<p>Hello, Vapor 1</p>
<p>Hello, Vapor 2</p>
<p>Hello, Vapor 3</p>
</template>In this case, three templates are registered at timing 2.
// this.ir.template
["<p>Hello, Vapor 1</p>", "<p>Hello, Vapor 2</p>", "<p>Hello, Vapor 3</p>"];
const t0 = template("<p>Hello, Vapor 1</p>");
const t1 = template("<p>Hello, Vapor 2</p>");
const t2 = template("<p>Hello, Vapor 3</p>");Return Value of the render Function
Returning to the discussion about
<template>
<p>Hello, Vapor!</p>
</template>Let's look back again at the IR obtained from this.
(Unnecessary parts are omitted)
{
"type": "RootIRNode",
"template": ["<p>Hello, Vapor!</p>"],
"block": {
"type": "BlockIRNode",
"returns": [0]
}
}Looking closely, there's something rather suspicious called "returns": [0].
From this information, we can understand that the node at index 0 seems to be the return value of the render function.
This is done in transformChildren.
26 if (
27 !(childContext.dynamic.flags & DynamicFlag.NON_TEMPLATE) ||
28 childContext.dynamic.flags & DynamicFlag.INSERT
29 ) {
30 context.block.returns.push(childContext.dynamic.id!)
31 }Under certain conditions, the id of that node is pushed into block.returns.
This id is calculated from the length when pushTemplate is called.
123 pushTemplate(content: string): number {
124 const existing = this.ir.template.findIndex(
125 template => template === content,
126 )
127 if (existing !== -1) return existing
128 this.ir.template.push(content)
129 return this.ir.template.length - 1
130 }
131 registerTemplate(): number {
132 if (!this.template) return -1
133 const id = this.pushTemplate(this.template)
134 return (this.dynamic.template = id)
135 }And,
Under certain conditions
As to what these conditions are, the first condition is when isFragment is true.
This is when the node executing transformChildren is one of Root, Element, Template, or Component.
10 const isFragment =
11 node.type === NodeTypes.ROOT ||
12 (node.type === NodeTypes.ELEMENT &&
13 (node.tagType === ElementTypes.TEMPLATE ||
14 node.tagType === ElementTypes.COMPONENT))The second condition is when dynamic.flags is not NON_TEMPLATE or is INSERT.
(Note: It might look a bit confusing at first glance, but since it's a bitmask, each flag is not exclusive.)
27 !(childContext.dynamic.flags & DynamicFlag.NON_TEMPLATE) ||
28 childContext.dynamic.flags & DynamicFlag.INSERTWhen these two conditions are met, id is pushed into block.returns.
I think the first condition is fine.
Regarding the second condition, about dynamic.flags.
dynamic.flags
dynamic is a property of TransformContext.
74 dynamic: IRDynamicInfo = this.ir.block.dynamic246export interface IRDynamicInfo {
247 id?: number
248 flags: DynamicFlag
249 anchor?: number
250 children: IRDynamicInfo[]
251 template?: number
252}Since context.ir holds this information, its reference is held in TransformContext.
In particular, this time, the information called DynamicFlag that IRDynamicInfo holds is important, so let's focus on that.
DynamicFlag is a flag that represents what kind of properties a node has.
The properties are as described in the comments.
230export enum DynamicFlag {
231 NONE = 0,
232 /**
233 * This node is referenced and needs to be saved as a variable.
234 */
235 REFERENCED = 1,
236 /**
237 * This node is not generated from template, but is generated dynamically.
238 */
239 NON_TEMPLATE = 1 << 1,
240 /**
241 * This node needs to be inserted back into the template.
242 */
243 INSERT = 1 << 2,
244}Since it's expressed with bitmasking, each property can coexist.
Let's see when each flag is marked.
DynamicFlag.REFERENCED
This node is referenced and needs to be saved as a variable.
As stated.
There are two places where DynamicFlag.REFERENCED is set.
- When
context.referenceis called.
117 reference(): number {
118 if (this.dynamic.id !== undefined) return this.dynamic.id
119 this.dynamic.flags |= DynamicFlag.REFERENCED
120 return (this.dynamic.id = this.increaseId())
121 }- When
IRDynamicInfois generated vianewDynamic(as a default value)
20export const newDynamic = (): IRDynamicInfo => ({
21 flags: DynamicFlag.REFERENCED,
22 children: [],
23})First, in case 1, context.reference is called in quite a few places.
For example, it's called in the conditional branch when isFragment is true in transformChildren we looked at earlier.
22 if (isFragment) {
23 childContext.reference()
24 childContext.registerTemplate()Then, as for what this flag is used for, it's to generate id when generating code.
We'll look at this in detail later when we see the implementation of codegen, but nodes with this flag set will generate an id and store it in a variable.
needs to be saved as a variable.
As stated.
It's understandable that this flag is set in isFragment in transformChildren.
By doing this,
const n0 = t0();
const n1 = t1();
const n2 = t2();We can output code that holds in variables like n${id}.
Conversely, nodes that don't need to be stored in variables don't have this flag set.
In this case,
const t0 = template("<p>Hello, Vapor!</p>");
function _sfc_render(_ctx) {
const n0 = t0(); // here
return n0;
}Since we need to hold n0 in a variable, this flag is set.
DynamicFlag.NON_TEMPLATE
Next is DynamicFlag.NON_TEMPLATE.
Whether this flag is set is quite important; if it's not set, id will be pushed into block.returns.
26 if (
27 !(childContext.dynamic.flags & DynamicFlag.NON_TEMPLATE) ||
28 childContext.dynamic.flags & DynamicFlag.INSERT
29 ) {
30 context.block.returns.push(childContext.dynamic.id!)
31 }This node is not generated from template, but is generated dynamically.
As stated, it seems this flag is set for nodes that are not generated from templates but are generated dynamically.
For example, this flag is set in transformComponentElement, transformSlotOutlet, transformVFor, etc.
96 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT31 context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE50 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERTSkipping a bit, an important point used in combination with this flag is DynamicFlag.INSERT.
DynamicFlag.INSERT
Whether to push the id into returns is first determined by checking whether DynamicFlag.NON_TEMPLATE is not set.
If it's not set, it is pushed into returns at this point.
If it is set, we check whether DynamicFlag.INSERT is set.
Looking at
96 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT31 context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE50 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERTyou can see that Component, SlotOutlet, and v-for have this flag set from the start.
However,
40 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE115 context.dynamic.flags |= DynamicFlag.NON_TEMPLATEdo not have this flag set at this point.
Regarding if, in the case of v-if (not v-else-if or v-else), this flag is set.
41 if (dir.name === 'if') {
42 const id = context.reference()
43 context.dynamic.flags |= DynamicFlag.INSERTAnd in the case of inserted slots like <template #foo>, this flag is not set.
110function transformTemplateSlot(
111 node: ElementNode,
112 dir: VaporDirectiveNode,
113 context: TransformContext<ElementNode>,
114) {
115 context.dynamic.flags |= DynamicFlag.NON_TEMPLATEIn this way, they select what to push into block.returns and what not to.
In the case of our small component, since the DynamicFlag.NON_TEMPLATE flag is not set, id is pushed into block.returns. With this, we've generated (transformed) all the IR that seems necessary for codegen!
Next, let's look at the implementation of codegen!
