Vue3 源码内参<五>Compiler-core 之 Codegen
往期文章(都有相应的源码)
- Vue3 源码内参<四>Compiler-core之Transform
- Vue3 源码内参<三>Compiler-core之Parse
- Vue3 源码内参<二>Reactivity核心
- Vue3 源码内参<一>手写mini-vue3前置准备
- 代码是参照 mini-vue 实现,有兴趣也可以看原作者的实现
看得见的思考
- 经过 generate 函数生成的 code 字符串是什么?
template 经过 parse 生成了 ast 语法树,然后 transform 又进一步的解析了 ast 语法树,将其中的 v-if、v-for、静态变量等都做了标识处理,再通过 generate 函数生成 render 函数,可以直接被 js 引擎渲染。
开局一张图
mini-vue 最简实现 compiler-core 中的 generate 模块
目标:将 <div>hi,{{message}}</div>
转换为 render 函数。
// codegen.spec.ts
import { generate } from "../src/codegen";
import { baseParse } from "../src/parse";
import { transform } from "../src/transform";
import { transformExpression } from "../src/transforms/transformExpression";
import { transformElement } from './../src/transforms/transformElement';
import { transformText } from './../src/transforms/transformText';
describe("codegen", () => {
it("element", () => {
// 1. parse 生成 ast,详情可以看往期文章
const ast: any = baseParse("<div>hi,{{message}}</div>");
// 2. transform 转换 ast,详情可以看往期文章,3 个插件的具体实现可以看下顶部的源码链接
transform(ast, {
nodeTransforms: [transformExpression,transformElement, transformText],
});
// 3. 生成 render 函数
const { code } = generate(ast);
expect(code).toMatchSnapshot();
});
});
import { isString } from "../../shared"
import { NodeTypes } from "./ast"
import { CREATE_ELEMENT_VNODE, helperMapName, TO_DISPLAY_STRING } from "./runtimeHelpers"
export function generate(ast) {
const context = createCodegenContext()
const { push } = context
genFunctionPreamble(ast, context)
const functionName = "render"
const args = ["_ctx", "_cache"]
const signature = args.join(", ")
push(`function ${functionName}(${signature}){`)
push("return ")
genNode(ast.codegenNode, context)
push("}")
return {
code: context.code
}
}
function createCodegenContext() {
const context = {
code: "",
push(source) {
context.code += source
},
helper(key) {
return `_${helperMapName[key]}`
}
}
return context
}
function genNode(node: any, context) {
// 这里之前只处理 text 之后还需要处理别的类型 使用一个 switch
switch (node.type) {
case NodeTypes.TEXT:
genText(node, context)
break;
case NodeTypes.INTERPOLATION:
genInterpolation(node, context)
break
case NodeTypes.SIMPLE_EXPRESSION:
genExpression(node, context)
break
case NodeTypes.ELEMENT:
genElement(node, context)
break
case NodeTypes.COMPOUND_EXPRESSION:
genCompoundExpression(node, context)
break
default:
break;
}
// const { push } = context
// push(`'${node.content}'`)
}
function genFunctionPreamble(ast: any, context) {
const { push } = context
const VueBinging = "Vue"
// const helpers = ["toDisplayString"] // 帮助函数 后期需要实现 修改写在一个 helper 里面
const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}` // 别名 带下划线
if (ast.helpers.length > 0) {
push(`const { ${ast.helpers.map(aliasHelper).join(", ")}} = ${VueBinging}`)
}
push("\n")
push("return ")
}
function genText(node: any, context: any) {
const { push } = context
push(`'${node.content}'`)
}
function genInterpolation(node: any, context: any) {
const { push, helper } = context
push(`${helper(TO_DISPLAY_STRING)}(`)
genNode(node.content, context)
push(")")
}
function genExpression(node: any, context: any) {
const { push } = context
push(`${node.content}`)
}
function genElement(node, context) {
const { push, helper } = context
const { tag, children, props } = node
// console.log('children', children)
// [ { type: 3, content: 'h1,' },
// { type: 0, content: { type: 1, content: 'message' } }
// ]
// push(`${helper(CREATE_ELEMENT_VNODE)}("${tag}"), null, "hi," + _toDisplayString(_ctx.message)`)
// element 里面的 children 一个一个拼接 循环遍历
// const child = children[0]
push(`${helper(CREATE_ELEMENT_VNODE)}(`)
// for (let i = 0; i < children.length; i++) {
// const child = children[i];
// genNode(child, context)
// }
genNodeList(genNullable([tag, props, children]), context)
// genNode(children, context)
push(")")
}
function genNodeList(nodes, context) {
const { push } = context
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (isString(node)) {
push(node)
} else {
genNode(node, context)
}
if (i < nodes.length - 1) {
push(", ")
}
}
}
function genNullable(args) {
return args.map((arg) => arg || "null")
}
function genCompoundExpression(node: any, context: any) {
const { push } = context
const { children } = node
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (isString(child)) {
push(child)
} else {
genNode(child, context)
}
}
}
END
回头看轻舟已过万重山,往前看前路漫漫亦灿灿。