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'
相关推荐
独泪了无痕4 小时前
使用Fetch API 探索前后端数据交互
前端·http·交互设计
css趣多多5 小时前
别名路径的知识点
前端
靓仔建6 小时前
Vue3导入组件出错does not provide an export named ‘user_setting‘ (at index.vue:180:10)
开发语言·前端·typescript
EnoYao7 小时前
我写了一个团队体检报告 Skill,把摸鱼的同事扒出来了😅
前端·javascript
梁正雄7 小时前
Python前端-2-css练习
前端·css·python
清汤饺子7 小时前
用 Cursor 半年了,效率还是没提升?是因为你没用对这 7 个功能
前端·后端·cursor
Never_Satisfied7 小时前
在JavaScript / Node.js中,package.json文件中的依赖项自动选择最新版安装
javascript·node.js·json
蓝莓味的口香糖7 小时前
【vue3】组件的批量全局注册
前端·javascript·vue.js
wefly20177 小时前
开发者效率神器!jsontop.cn一站式工具集,覆盖开发全流程高频需求
前端·后端·python·django·flask·前端开发工具·后端开发工具
独泪了无痕8 小时前
自动导入 AutoImport:告别手动引入依赖,优化Vue3开发体验
前端·vue.js·typescript