Transformer の概要 2
少し長くなったので分割しましたが続きです.
概要,といいつつ気付けば具体的な実装の詳細を読んでしまっていますが,まぁ,自然な導入としては良いのではないでしょうか (笑)
前ページまでで template 関数に引数として渡すテンプレートの文字列を生成する実装を見てきました.
ここでは Block の index 管理等を見ていきます.
registerTemplate
TransformContext に registerTemplate という関数があります.
131 registerTemplate(): number {
132 if (!this.template) return -1
133 const id = this.pushTemplate(this.template)
134 return (this.dynamic.template = id)
135 }この registerTemplate では 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 }template (文字列) は this.ir.template (配列) に登録されていきます.
this.ir は RootIRNode です.
87 constructor(
88 public ir: RootIRNode,つまりここです.
56export interface RootIRNode {
57 type: IRNodeTypes.ROOT
58 node: RootNode
59 source: string
60 template: string[]そして,この registerTemplate が呼び出されるのは 3 箇所です.
transformNodeの最後 (root の時のみ)
264 if (context.node.type === NodeTypes.ROOT) {
265 context.registerTemplate()
266 }transformChildrenで children を処理したあと (Fragment の時のみ)
22 if (isFragment) {
23 childContext.reference()
24 childContext.registerTemplate()context.enterBlockが呼び出された時 (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 は transformVFor や transformVIf で Block に入るときに呼び出す関数です.
これはまた v-for や v-if のコンパイルの実装を見る時に見るとして,一旦は 1 と 2 だけ把握しておけば良いでしょう.
1 に関しては特に何もないと思います.
今回読んでいる小さいコンポーネントはここで template が登録されます.
つまり,この時点で今,this.ir.template は,["<p>Hello, Vapor!</p>"] という状態になっています.
これがあれば template の index がわかるので,
const t0 = template("<p>Hello, Vapor!</p>");というコードは生成することができそうです.(ここはまた codegen の時に実際に見てみましょう.)
2 の Fragment の場合というのは,
<template>
<p>Hello, Vapor 1</p>
<p>Hello, Vapor 2</p>
<p>Hello, Vapor 3</p>
</template>のような template を書いた場合です.この場合は 2 のタイミングで 3 つの template が登録されます.
// 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>");render 関数の戻り値
<template>
<p>Hello, Vapor!</p>
</template>に話を戻して,これから得られた IR を再度見返してみます.
(不要な部分は省略しています)
{
"type": "RootIRNode",
"template": ["<p>Hello, Vapor!</p>"],
"block": {
"type": "BlockIRNode",
"returns": [0]
}
}よく見ると,"returns": [0] というなんとも怪しいものがあります.
この情報があれば,0 番目の node が render 関数の戻り値となりそうなことが分かります.
これは,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 }ある条件下の時に,その node の id を block.returns に push しています.
この id は pushTemplate した際に length から算出されています.
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 }そして,
ある条件下の時
これがどういう場合かというと,まず 1 つ目の条件は isFragment が true の時です.
これは transformChildren を実行している node が Root, Element, Template, Component のいずれかの場合です.
10 const isFragment =
11 node.type === NodeTypes.ROOT ||
12 (node.type === NodeTypes.ELEMENT &&
13 (node.tagType === ElementTypes.TEMPLATE ||
14 node.tagType === ElementTypes.COMPONENT))そして 2 つめの条件は, dynamic.flags が NON_TEMPLATE では ない もしくは,INSERT である場合です.
(※ ぱっと見分かりづらいですが,ビットマスクなので各フラグは排他的なものではありません)
27 !(childContext.dynamic.flags & DynamicFlag.NON_TEMPLATE) ||
28 childContext.dynamic.flags & DynamicFlag.INSERTこの 2 つの条件に一致した場合に block.returns に id が push されます.
1 つ目の条件はまぁ良いと思います.
2 つめの条件の,dynamic.flags についてです.
dynamic.flags
dynamic は 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}context.ir がこの情報を持っているので,その参照を TransformContext に保持しています.
特に,今回はこの IRDynamicInfo が持つ,DynamicFlag という情報が重要なのでそこを重点的にみていきます.
DynamicFlag は node がどのような性質を持っているかを表すフラグです.
性質は各自コメントアウトにある通りです.
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}ビットマスクで表現されているため,各性質は共存しうります.
それぞれ,どういう時にそのフラグがマークされるかどうかみてみましょう.
DynamicFlag.REFERENCED
This node is referenced and needs to be saved as a variable.
という記載があります.
DynamicFlag.REFERENCED が設定されるタイミングは 2 箇所です.
context.referenceが呼ばれた時.
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 }newDynamicによってIRDynamicInfoを生成した時 (デフォルト値として)
20export const newDynamic = (): IRDynamicInfo => ({
21 flags: DynamicFlag.REFERENCED,
22 children: [],
23})まず 1 のケースですが,context.reference はかなり色々なところで呼ばれています.
例えば,先ほど見ていた transformChildren の isFragment が true の時の条件分岐の中で呼ばれています.
22 if (isFragment) {
23 childContext.reference()
24 childContext.registerTemplate()そして,このフラグがなんのために使われているのかというと,コードを生成する際に id を生成するためです.
これはまた後ほど codegen の実装を見ていく際に詳しく見ますが,このフラグが立っている Node は id を生成し,変数に保持します.
needs to be saved as a variable.
の通りです.
transformChildren の isFragment でこのフラグが立たせているのもこれでよくわかると思います.
こうすることで,
const n0 = t0();
const n1 = t1();
const n2 = t2();のように n${id} という変数に保持するコードを出力することができます.
逆に,変数に保持する必要のない node はこのフラグが立ません.
今回は,
const t0 = template("<p>Hello, Vapor!</p>");
function _sfc_render(_ctx) {
const n0 = t0(); // ここ
return n0;
}のように n0 という変数に保持する必要があるので,このフラグが立っているということです.
DynamicFlag.NON_TEMPLATE
続いて DynamicFlag.NON_TEMPLATE です.
このフラグが立っているかどうかはかなり重要で,立っていなければ block.returns に id が push されていく事になります.
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.
とあるように,どうやら template から生成された node ではないものにこのフラグが立つようです.
例えば,transformComponentElement や transformSlotOutlet, transformVFor などでこのフラグが立つようです.
96 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT31 context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE50 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT40 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE115 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE少し飛ばしてこのフラグと併用して重要なポイントになるのが DynamicFlag.INSERT です.
DynamicFlag.INSERT
returns に id を push するかどうかはまず,DynamicFlag.NON_TEMPLATE が立っていないかどうかを見ます.
もし立っていなければこの時点で returns に push されます.
立っていた場合は,DynamicFlag.INSERT が立っているかどうかを見ます.
96 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT31 context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE50 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERTを見るとわかる通り,Component, SlotOutlet, v-for は初めからこのフラグが立っています.
しかし,
40 context.dynamic.flags |= DynamicFlag.NON_TEMPLATE115 context.dynamic.flags |= DynamicFlag.NON_TEMPLATEはこの時点では立っていません.
if に関して言えば,v-if の場合 (v-else-if, v-else ではない場合) にこのフラグを立てます.
41 if (dir.name === 'if') {
42 const id = context.reference()
43 context.dynamic.flags |= DynamicFlag.INSERTそして,<template #foo> のような,挿入されたスロットに場合にはこのフラグが立つことはありません.
110function transformTemplateSlot(
111 node: ElementNode,
112 dir: VaporDirectiveNode,
113 context: TransformContext<ElementNode>,
114) {
115 context.dynamic.flags |= DynamicFlag.NON_TEMPLATEこのようにして,block.returns に何を push して,何を push しないかを選択しています.
今回の小さなコンポーネントの場合には,DynamicFlag.NON_TEMPLATE というフラグは立たないので,block.returns には id が push されます. これで,codegen に必要そうな IR を全て生成 (transform) することができました!
次は,codegen の実装を見ていきましょう!
