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.enterBlock
is 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.INSERT
When 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.dynamic
246export 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.reference
is 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
IRDynamicInfo
is 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.INSERT
31 context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE
50 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
Skipping 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.INSERT
31 context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE
50 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
you can see that Component
, SlotOutlet
, and v-for
have this flag set from the start.
However,
40 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
115 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
do 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.INSERT
And 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_TEMPLATE
In 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!