41 - 实现template模板编译

72 阅读3分钟

例子

查看例子

实现思路

  1. 需要把写好 codegen 暴露到全局,在 runtime 阶段使用
  2. runtime 暴露对应函数, 让 codegen 引用这个函数,这也是 Vue 官网推荐的做法,这样做的好书
  • 降低耦合
  • 不会打包多余代码
  1. 在初始化阶段,判断用户是否写了 render,若是写了使用 render,否则查询是否写了 template。两者同时存在,取 render

实现

暴露 compiler

新建一个 compiler.ts

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-20 20:47:43
 * @LastEditTime: 2022-04-20 20:49:25
 * @LastEditors: Lin ZeFan
 * @Description:
 * @FilePath: \mini-vue3\src\compiler-core\src\compiler.ts
 *
 */

import { generate } from "./codegen";
import { baseParse } from "./parse";
import { transform } from "./transform";
import { transformElement } from "./transforms/transformElement";
import { transformExpression } from "./transforms/transformExpression";
import { transformText } from "./transforms/transformText";

// 暴露编译方法
export function baseCompile(template: string) {
  const ast = baseParse(template);
  transform(ast, {
    nodeTransforms: [transformExpression, transformElement, transformText],
  });
  const code = generate(ast);
  return {
    code,
  };
}

暴露compiler

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-20 20:49:37
 * @LastEditTime: 2022-04-20 20:49:37
 * @LastEditors: Lin ZeFan
 * @Description: 
 * @FilePath: \mini-vue3\src\compiler-core\src\index.ts
 * 
 */

// 暴露整个 compiler
export * from './compiler'

接收compiler

runtime-core 暴露接收函数

// src\runtime-core\component.ts

let compiler;
export function registerCompiler(_compiler) {
  compiler = _compiler;
}

接收 compiler

/*
 * @Author: Lin zefan
 * @Date: 2022-03-22 15:40:00
 * @LastEditTime: 2022-04-24 13:03:57
 * @LastEditors: Lin ZeFan
 * @Description: 打包入口文件
 * @FilePath: \mini-vue3\src\index.ts
 *
 */

export * from "./runtime-dom";
export * from "./reactivity";
import { baseCompile } from "./compiler-core/src";
import * as runtimeDom from "./runtime-dom";
import { registerCompiler } from "./runtime-dom";

// 创建一个 render 函数
function compileToFunction(template: string) {
  const { code } = baseCompile(template);
  const render = new Function("Vue", code)(runtimeDom);
  return render;
}

// 在这里将 compiler 传入到 component 内部中
registerCompiler(compileToFunction);

模板编译判断

新增 template 判断

src\runtime-core\component.ts


function finishComponentSetup(instance) {
  const component = instance.type;
  // 挂载实例的render函数,取当前组件实例声明得render
  if (component.render) {
    instance.render = component.render;
+  } else if (component.template && compiler) {
+    // 没有写 render,但是写了template
+    // 使用了高阶函数,暴露了 codegen 方法
+    instance.render = compiler(component.template);
+  }
}

初始化

src\runtime-core\render.ts

  function renderComponentInstance(instance) {
    const { proxy } = instance;
    return instance.render.call(proxy, proxy);
  }
  
  function setupRenderEffect(instance, container, anchor) {
    instance.runner = effect(
      () => {
        // 初始化vnode
        if (!instance.isMounted) {
          let { vnode } = instance;
          // 通过render函数,获取render返回虚拟节点,并绑定render的this,并把this传出去
+          const subTree = renderComponentInstance(instance);
          /**
           * 1. 调用组件render后把结果再次给到patch
           * 2. 再把对应的dom节点append到container
           * 3. 把当前实例传过去,让子组件可以通过parent获取父组件实例
           */
          patch(null, subTree, container, instance, anchor);
          /** 挂载当前的dom元素到$el
           * 1. 当遍历完所有Component组件后,会调用processElement
           * 2. 在processElement中,会创建dom元素,把创建的dom元素挂载到传入的vnode里面
           * 3. 当前的dom元素也就是processElement中创建的dom元素
           */
          vnode.el = subTree.el;
          // 更新初始化状态
          instance.isMounted = true;
          // 保存当前vnode
          instance.preTree = subTree;
        } else {
          const { next, vnode } = instance;
          if (next) {
            // 保存当前的dom节点,因为新vnode没有走创建流程,所以没有el
            next.el = vnode.el;
            updateComponentPreRender(instance, next);
          }
          // 通过render函数,获取render返回虚拟节点,并绑定render的this
+          const nowTree = renderComponentInstance(instance);
          // 旧vnode
          const preTree = instance.preTree;
          // 更新旧的vnode
          instance.preTree = nowTree;
          // 对比新老vnode
          patch(preTree, nowTree, container, instance, anchor);
        }
      },
      {
        scheduler() {
          // 将本次 update 加入到任务队列中
          queueJobs(instance.runner);
        },
      }
    );
  }