在平时开发中,我们都会写template
,在vue
底层,会将template
编译成render
函数。vue
的编译原理相对复杂,本文的主要目标是找到编译主入口。
我们以debugger
的方式找到编译文件的调试入口,感受debugger
调试源码的乐趣。
ts
// 本例包含:div,注释,事件,插槽,v-if,组件等内容
<body>
// #app容器
<div id="app"></div>
<script>
// 定义父组件
const App = {
// template解析后执行,所有其中的响应式数据可以访问到
template: `<div class="myApp">
<!-- 这是注释文案 -->
<h3>编译原理</h3>
<button @click="flag = !flag">控制显示隐藏</button>
<div v-if="flag">
<p>{{ first + second }}</p>
</div>
<ChildComp v-else></ChildComp>
</div>`,
// setup函数先执行,返回的数据供模版编译时使用
setup() {
// 定义响应式数据
const first = Vue.ref('hi');
const second = Vue.ref(' bqb');
const flag = Vue.ref(true);
// 返回数据
return {
first,
second,
flag,
};
},
};
// 定义子组件
const ChildComp = {
template: `<div>子组件</div>`,
};
// 创建app实例
const app = Vue.createApp(App);
// 注册子组件,供模版编译时使用
app.component("ChildComp", ChildComp);
// 挂载app实例
debugger;
app.mount("#app");
</script>
</body>
例子中通过const app = Vue.createApp(App)
创建app
实例,然后通过app.mount("#app")
将app
实例挂载到#app
容器上。在app.mount
方法中,会调用mountComponent
方法。
1、mountComponent
方法

以上是在渲染函数的mountComponent
中找到了setupComponent
副作用函数,这是组件渲染中组件实例
、执行副作用函数
和执行渲染函数
中的第二步。
接着我们继续寻找编译入口。
2、finishComponentSetup
方法

以上在finishComponentSetup
中找到了compile$1
(即 compileToFunction)方法,这就是编译的入口。
3、compileToFunction
方法

const { code } = compile(template, opts)
中将template
和opts
作为参数传入compile
方法中,执行返回code
。
再通过const render = new Function(code)()
的方式,将code
转成render
函数。
4、baseCompile
方法
到这里,我们就找到了编译时重要的三个流程:
- 生成 ast 树
- 优化树
- 生成 code
至此,我们就通过debugger
的方式找到了编译的入口,并且找到了编译时重要的三个流程。
我们需要注意的是,每个截图右侧的Call Stack
,可以让我们知道当前执行的方法的调用栈,方便我们找到当前执行的方法的调用者,以及各个函数之间的调用关系。