46-mini-vue 实现编译 template 为 render 函数

实现编译 template 为 render 函数

  1. 目标:
    实现 compiler 模块与 runtime 模块结合起来,让我们这个 demo 跑起来, 最终把 template 编译成 render 函数,我们之前写了 parse,transform,codegen模块,但是没有一个出口,这个出口内部就是这几个模块,我们把这个出口写成 compiler 模块,
js 复制代码
// Demo
// App.js
export const App = {
  name: "App",
  template: `<div>hi,{{message}}</div>`,
  setup() {
    return {
      message: "mini-vue"
    }
  }
}

// main.js
import { createApp } from '../lib/guide-mini-vue.esm.js'
import App from './App.js'
const rootComponent = document.querySelector('#App')
createApp(App).mount(rootComponent)

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
<div id="App"></div>
<script src="./main.js" type="module"></script>
</body>
</html>

// 将 template 代码编译成 render 函数
// 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) {
  const ast: any = baseParse(template)
  transform(ast, {
    nodeTransforms: [transformExpression, transformElement, transformText],
  })
  return generate(ast)
}
// compiler-core/src/index.ts
export * from './compile'
  1. 我们将写好的 compile 文件在项目中调用,我们可以用在这里
js 复制代码
// runtime-core/component.ts
function finishComponentSetup(instance: any) { 
  const Component = instance.type
  // 之前这块我们直接把 render 函数写好
  // 但现在用户不会提供 render 函数的,需要我们手动把 template 编译为 render 函数
  //   我们在这里实现 template 编译为 render
  instance.render = Component.render
} 
  1. 官网目录依赖关系

  2. mini-vue目录梳理

js 复制代码
// 目录关系
vue ----> compiler-core
  \-----> runtime-dom ---> runtime-core ---> reactivity
  • 注意: compiler 模块不要直接引入 runtime 模块的东西,runtime 模块也不直接引入 compiler
  • 如果直接引用,就会形成强依赖的关系
  • 我们知道 vue 可以仅存在运行时的,如果运行时的代码依赖编译时的代码,这块运行时的逻辑就是有问题的
  • 我们使用 Webpack,rollup是可以提前把代码 template 编译成 render函数的,在线上运行时,我们只跑运行时逻辑就可以。
  • 如果我们真的想要在运行时使用编译时的方法,可以先把方法导出到根目录,然后再从运行时目录进行引入,这样强依赖关系就没有了
js 复制代码
// src/index.ts  
import * as runtimeDom from './runtime-dom'
import { registerRuntimeCompiler } from './runtime-core'
import { baseCompile } from './compiler-core/src'

function compileToFunction(template) { // 因为 template 转为 render 函数是一个包含 code 属性的对象
  const { code } = baseCompile(template)
  // 我们进一步转换,将该对象转为一个 render 函数
  const render = new Function("Vue", code)(runtimeDom) // Vue 是参数 code 是函数体body
  return render
}
// 我们生成了 render 函数,如何用到 component.ts 里面呢?
//  在 component.ts 里面写一个函数 registerRuntimeCompiler 将 render 函数缓存到变量里面,方便直接使用
registerRuntimeCompiler(compileToFunction)
js 复制代码
// runtime-core/component.ts
let compiler;
export function registerRuntimeCompiler(_compiler) {
  compiler = _compiler
}

function finishComponentSetup(instance: any) {
  const Component = instance.type
  // 我们在这里实现 template 编译为 render
  if(compiler && !Component.render) { // ✅
    if(Component.template) {
      Component.render = compiler(Component.template)
    }
  } 

  instance.render = Component.render
} 
// runtime-core/index.ts 
export { getCurrentInstance, registerRuntimeCompiler } from './component'
  • 解决报 message undefined 问题,这里增加 proxy 参数,可以直接传入 this
js 复制代码
function setupRenderEffect(instance, vnode, container, anchor) {
  instance.update = effect(() => {
    let { proxy } = instance
    if (!instance.isMounted) {
      const subTree = instance.subTree = instance.render.call(proxy, proxy) // ✅ 增加 proxy参数
      patch(null, subTree, container, instance, anchor)
      vnode.el = subTree.el
      instance.isMounted = true
    } else {
      const { next, vnode } = instance 
      if (next) {
        next.el = vnode.el
        updateComponentPreRender(instance, next)
      }
      const { proxy } = instance
      const subTree = instance.render.call(proxy, proxy) // ✅ 增加 proxy参数
      const prevSubTree = instance.subTree
      instance.subTree = subTree
      patch(prevSubTree, subTree, container, instance, anchor)
    }
  },{
    scheduler() {
      queueJobs(instance.update) 
    }
  })
}
  • 实现 toDisplayString 方法
js 复制代码
// shared/toDisplayString.ts
export function toDisplayString(value) {
  return String(value)
} 
// shared/index
export * from './toDisplayString'

// runtime-core/index.ts 
export { toDisplayString} from '../shared'
  • 实现 createElementVNode
js 复制代码
// runtime-core/index.ts
export { createTextVNode, createElementVNode } from './vnode'
// runtime-core/vnode.ts
export {
  createVNode as createElementVNode
}
  • 最后编译渲染出 hi,mini-vue
  • 我们将测试使用的 demo 的变量挂载到 window 上,方便控制台调用测试
js 复制代码
import { ref } from "../../lib/guide-mini-vue.esm.js"

export const App = {
  name: "App",
  template: `<div>hi,{{message}}-{{count}}</div>`,
  setup() {
    const count = window.count = ref(0)
    return {
      message: "mini-vue",
      count
    }
  }
}
  • 优化
  1. reactivity
js 复制代码
// 根据上面我们 mini-vue 的依赖图,reactivity 不能放在 src/index.ts 里面,我们调整到 runtime-core 里面
export * from '../reactivity'
相关推荐
木斯佳1 小时前
前端八股文面经大全:京东零售前端实习一面(2026-1-20)·面经深度解析
前端·状态模式·零售
YuMiao2 小时前
把 WebSocket 服务迁移到 Cloudflare Durable Objects —— 以一次协同编辑实战为例
javascript·node.js
zheshiyangyang2 小时前
前端面试基础知识整理【Day-8】
前端·面试·职场和发展
a1117762 小时前
优雅简历(html开源)
前端·开源·html
Cache技术分享2 小时前
330. Java Stream API - 处理 Optional 对象:像流一样优雅地使用 Optional
前端·后端
感性的程序员小王2 小时前
别再手撸架构图了!我写了个 AI 工具,把 Spring Boot 代码一键变成 Draw.io 流程图
前端·后端
左夕2 小时前
深度解析vue的生命周期
vue.js
猪头男2 小时前
【从零开始学习Vue|第七篇】深入组件——Props
前端
随逸1772 小时前
《彻底解决CSS冲突!模块化CSS实战指南》
vue.js·react.js