Vue3 源码内参<五> Runtime-core 核心之 Codegen

90 阅读2分钟

Vue3 源码内参<五>Compiler-core 之 Codegen

往期文章(都有相应的源码)

本节代码演示地址,配合源码食用更开胃噢!

  • 代码是参照 mini-vue 实现,有兴趣也可以看原作者的实现

看得见的思考

  • 经过 generate 函数生成的 code 字符串是什么?

template 经过 parse 生成了 ast 语法树,然后 transform 又进一步的解析了 ast 语法树,将其中的 v-if、v-for、静态变量等都做了标识处理,再通过 generate 函数生成 render 函数,可以直接被 js 引擎渲染。

开局一张图

generate.png

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

回头看轻舟已过万重山,往前看前路漫漫亦灿灿。