Overview of Codegen
Up to this point, we've looked at the process of parsing code, generating an AST, and converting it into IR via the transformer.
Finally, let's look at codegen, which generates code from the IR.
By understanding this, you'll have a substantial grasp of the compiler.
Implementation Locations
The implementation of codegen (generator) can be found in the following areas:
The structure is similar to the transformer; generate.ts
implements the generate
function and CodegenContext
, and the generators
directory contains code generation functions for each node.
99// IR -> JS codegen
100export function generate(
101 ir: RootIRNode,
102 options: CodegenOptions = {},
103): VaporCodegenResult {
22export class CodegenContext {
As usual, let's read about CodegenContext
as needed while following the code generation of the component.
generate
First, let's enter the generate
function.
99// IR -> JS codegen
100export function generate(
101 ir: RootIRNode,
102 options: CodegenOptions = {},
103): VaporCodegenResult {
The code is appended sequentially using the push
function obtained from buildCodeFragment
.
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}
First, we push the signature of the render function.
108 const functionName = 'render'
113 push(NEWLINE, `export function ${functionName}(_ctx) {`)
Then, we generate code from the IR using genBlockContent
.
116 push(INDENT_START)
117 push(...genBlockContent(ir.block, context, true))
118 push(INDENT_END, NEWLINE)
Since the declarations of templates and import statements are done outside the render function, these are generated as preamble
and added to the beginning of the code.
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 }
This code
becomes the final code.
141 return {
142 code,
143 ast: ir,
144 preamble,
145 map: map && map.toJSON(),
146 helpers,
147 vaporHelpers,
148 }
Now, let's read genBlockContent
.
Tips: About _sfc_render
When checking the output code, the name of the render function was _sfc_render
instead of render
.
However, in the generate
function, it is pushed as render
.
In fact, the render
function is later rewritten to _sfc_render
by the implementation of vite-plugin-vue
.
Therefore, the name _sfc_render
does not actually appear in compiler-vapor
.
71 return {
72 ...result,
73 code: result.code.replace(
74 /\nexport (function|const) (render|ssrRender)/,
75 '\n$1 _sfc_$2',
76 ),
genBlockContent
The implementation is located in 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[] {
We take out each child from block.dynamic.children
and generate code.
51 for (const child of dynamic.children) {
52 push(...genChildren(child, context, child.id!))
53 }
block.dynamic.children
is generated in transformChildren
, and its content directly includes childContext.dynamic
.
36 context.dynamic.children[i] = childContext.dynamic
Looking again at what information besides flags is included:
246export interface IRDynamicInfo {
247 id?: number
248 flags: DynamicFlag
249 anchor?: number
250 children: IRDynamicInfo[]
251 template?: number
252}
We can see that it includes information like id
and template index.
Using this information, we generate code with genChildren
.
genChildren
genChildren
is implemented in packages/compiler-vapor/src/generators/template.ts.
18export function genChildren(
19 dynamic: IRDynamicInfo,
20 context: CodegenContext,
21 from: number,
22 paths: number[] = [],
23): CodeFragment[] {
This function generates code like const n${id} = t${template}()
.
In this case, it generates code like const n0 = t0()
.
29 if (id !== undefined && template !== undefined) {
30 push(NEWLINE, `const n${id} = t${template}()`)
31 push(...genDirectivesForElement(id, context))
32 }
Here, code like nextSibling
and firstChild
, which will appear later, is also generated. (You can skip this for now.)
Continuing genBlockContent
Once the code for children is generated.
Next, we generate operations and effects.
These haven't appeared yet, but they involve generating code for things like text updates and event handler registrations.
55 push(...genOperations(operation, context))
56 push(...genEffects(effect, context))
Finally, we generate the return
statement.
We map over block.returns
, generate identifiers like n${idx}
, and generate the return
statement.
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))
Surprisingly, that's it for Codegen.
Now, we've been able to follow the compiler implementation needed to compile a simple component.
Let's summarize our objectives, steps, and remaining tasks, and proceed to the next step!