实现编译 template 为 render 函数
- 目标:
实现 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'
- 我们将写好的 compile 文件在项目中调用,我们可以用在这里
js
// runtime-core/component.ts
function finishComponentSetup(instance: any) {
const Component = instance.type
// 之前这块我们直接把 render 函数写好
// 但现在用户不会提供 render 函数的,需要我们手动把 template 编译为 render 函数
// 我们在这里实现 template 编译为 render
instance.render = Component.render
}
-
官网目录依赖关系

-
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
}
}
}
- 优化
- reactivity
js
// 根据上面我们 mini-vue 的依赖图,reactivity 不能放在 src/index.ts 里面,我们调整到 runtime-core 里面
export * from '../reactivity'