Skip to content

Codegen の概要

ここまででコードをパースし,AST を生成し,transformer によって IR に変換するまでの流れを見てきました.
最後は IR からコードを生成する codegen について見ていきましょう.
ここまで見れればコンパイラに関してはかなりの部分を理解できるでしょう.

compiler vapor codegen

実装箇所

codegen (generator) の実装は以下のあたりにあります.

transformer と構成は似ていて,generate.tsgenerate 関数や CodegenContext が実装されていて,generators ディレクトリには各ノードのコード生成関数が実装されています.

99// IR -> JS codegen
100export function generate(
101  ir: RootIRNode,
102  options: CodegenOptions = {},
103): VaporCodegenResult {
22export class CodegenContext {

例の如く,CodegenContext に関しては実際にコンポーネントのコード生成を追いながら必要なところを随時読んでいきましょう.

generate

まずは generate 関数に入ります.

99// IR -> JS codegen
100export function generate(
101  ir: RootIRNode,
102  options: CodegenOptions = {},
103): VaporCodegenResult {

code は buildCodeFragment によって得られる push という関数で次々 append していきます.

104  const [frag, push] = buildCodeFragment()
30export function buildCodeFragment(
31  ...frag: CodeFragment[]
32): [CodeFragment[], (...items: CodeFragment[]) => number] {
33  const push = frag.push.bind(frag)
34  return [frag, push]
35}

まずは render 関数のシグネチャを push します.

108  const functionName = 'render'
113    push(NEWLINE, `export function ${functionName}(_ctx) {`)

そして genBlockContent によって ir からコードを生成します.

116  push(INDENT_START)
117  push(...genBlockContent(ir.block, context, true))
118  push(INDENT_END, NEWLINE)

template の宣言や import 文の宣言は render 関数の外で行われるので,これらは,preamble として生成され,コードの先頭に追加されます.

126  const delegates = genDelegates(context)
127  const templates = genTemplates(ir.template, context)
128  const imports = genHelperImports(context)
129  const preamble = imports + templates + delegates
136  let [code, map] = codeFragmentToString(frag, context)
137  if (!inline) {
138    code = preamble + code
139  }

そしてこの code が最終的なコードになります.

141  return {
142    code,
143    ast: ir,
144    preamble,
145    map: map && map.toJSON(),
146    helpers,
147    vaporHelpers,
148  }

それでは genBlockContent を読んでいきましょう.

Tips: _sfc_render について

出力コードを確認した時,render 関数の名前は render ではなく _sfc_render でした.
しかし,generate 関数内で render として push しています.

実は,render という関数は vite-plugin-vue の実装によってのちに _sfc_render にリライトされます.
なので,_sfc_render という名前は compiler-vapor には実は登場しないのです.

71  return {
72    ...result,
73    code: result.code.replace(
74      /\nexport (function|const) (render|ssrRender)/,
75      '\n$1 _sfc_$2',
76    ),

genBlockContent

実装は packages/compiler-vapor/src/generators/block.ts にあります.

36export function genBlockContent(
37  block: BlockIRNode,
38  context: CodegenContext,
39  root?: boolean,
40  customReturns?: (returns: CodeFragment[]) => CodeFragment[],
41): CodeFragment[] {

block.dynamic.children から child を一つづ取り出して generate します.\

51  for (const child of dynamic.children) {
52    push(...genChildren(child, context, child.id!))
53  }

block.dynamic.childrentransformChildren で生成されるもので,中身としては childContext.dynamic がそのまま入る形になっています.

36    context.dynamic.children[i] = childContext.dynamic

改めて,flags 以外にどのような情報があるのか見ておくと,

246export interface IRDynamicInfo {
247  id?: number
248  flags: DynamicFlag
249  anchor?: number
250  children: IRDynamicInfo[]
251  template?: number
252}

id や template の index の情報が入っていることが分かります.
この情報を使って,genChildren によりコードを生成します.

genChildren

genChildrenpackages/compiler-vapor/src/generators/template.ts に実装されています.

18export function genChildren(
19  dynamic: IRDynamicInfo,
20  context: CodegenContext,
21  from: number,
22  paths: number[] = [],
23): CodeFragment[] {

この関数では const n${id} = t${template}() のようなコードが生成されます. つまり,今回でいうところの const n0 = t0() のようなコードが生成されるわけです.

29  if (id !== undefined && template !== undefined) {
30    push(NEWLINE, `const n${id} = t${template}()`)
31    push(...genDirectivesForElement(id, context))
32  }

ここで,後々登場する nextSiblingfirstChild のようなコードも生成されます.(今は読み飛ばします.)

genBlockContent の続き

children のコードが生成できたら.

次に operation と effect を generate します.
ここはまだ登場していませんが,テキストの書き換えやイベントハンドラの登録などのコードの生成です.

55  push(...genOperations(operation, context))
56  push(...genEffects(effect, context))

最後に,return 文の生成です.

block.returns を map し,n${idx} の識別子を生成しつつ return 文を生成します.

58  push(NEWLINE, `return `)
59
60  const returnNodes = returns.map(n => `n${n}`)
61  const returnsCode: CodeFragment[] =
62    returnNodes.length > 1
63      ? genMulti(DELIMITERS_ARRAY, ...returnNodes)
64      : [returnNodes[0] || 'null']
65  push(...(customReturns ? customReturns(returnsCode) : returnsCode))

なんと,ざっくり Codegen はこれで終わりです.
さて,これで単純なコンポーネントをコンパイルするためのコンパイラの実装は一通り追うことができました.
改めて,今回の目的と手順,残りやりたいことをまとめつつ次のステップに進みましょう!