春节期间在家闲来无事,于是动动小手阅读了 Vue3 的源码,并总结为文章,也是对自己的学习的总结,有任何纰漏欢迎指正~
tip: 本文阅读的 Vue3 源码对于的 commitid 为 4fe4de0
如何上手
首先需要搭建好调试阅读源码的环境。边阅读本文边对照 vue-next 源码是一个比较有效的阅读方法。
方法一:
方法一适合阅读整个 Vue 项目的运行流程,包括 compiler 的编译过程和 runtime 的运行流程。
- clone Vue3 的源码
git clone git@github.com:vuejs/vue-next.git
- 调试 demo
安装完 Vue3 的依赖后,运行 yarn dev , 然后在源码中需要的地方打 debugger,使用 vs code 的插件 live server 打开 packages/vue/examples/composition/commits.html ,打开 chrome 的 devtools,刷新后会命中断点。
tips: yarn dev 相当于运行:
npx rollup -wc --environment "COMMIT:4fe4de0,TARGET:vue,FORMATS:global,SOURCE_MAP:true"
因此我们也可以运行:
npx -n inspect rollup -wc --environment "COMMIT:4fe4de0,TARGET:vue,FORMATS:global,SOURCE_MAP:true"
来调试 Vue3 构建源码的过程。
其中 4fe4de0 是当前 Vue3 仓的 commitId,我们可以运行上面的 rollup 命令来查看 Vue3 源码的构建过程。
packages/vue/examples/composition/commits.html
<script src="../../dist/vue.global.js"></script>
<!--
vue.global.js 是 rollup 以 packages/vue/src/index.ts 为入口文件构建的,若要查看 rollup 的构建过程,可以运行:
npx -n inspect rollup -wc --environment "COMMIT:4fe4de0,TARGET:vue,FORMATS:global,SOURCE_MAP:true"
-->
<div id="demo">
<h1>Latest Vue.js Commits</h1>
<template v-for="branch in branches">
<input type="radio" :id="branch" :value="branch" name="branch" v-model="currentBranch">
<label :for="branch">{{ branch }}</label>
</template>
<p>vuejs/vue@{{ currentBranch }}</p>
<ul>
<li v-for="{ html_url, sha, author, commit } in commits">
<a :href="html_url" target="_blank" class="commit">{{ sha.slice(0, 7) }}</a>
- <span class="message">{{ truncate(commit.message) }}</span><br>
by <span class="author"><a :href="author.html_url" target="_blank">{{ commit.author.name }}</a></span>
at <span class="date">{{ formatDate(commit.author.date) }}</span>
</li>
</ul>
</div>
<script>
const { createApp, ref, watchEffect } = Vue
const API_URL = `https://api.github.com/repos/vuejs/vue-next/commits?per_page=3&sha=`
// 截断、删除
const truncate = v => {
const newline = v.indexOf('\n')
return newline > 0 ? v.slice(0, newline) : v
}
const formatDate = v => v.replace(/T|Z/g, ' ')
// createApp 调用的是 runtime-dom/src/index.ts#createApp ,其封装了 packages/runtime-core/src/apiCreateApp.ts#createApp
const app =
createApp({
setup() {
const currentBranch = ref('master')
const commits = ref(null)
watchEffect(() => {
fetch(`${API_URL}${currentBranch.value}`)
.then(res => res.json())
.then(data => {
console.log(data)
commits.value = data
})
})
return {
branches: ['master', 'sync'],
currentBranch,
commits,
truncate,
formatDate
}
}
});
debugger
// app.mount 调用的是 runtime-dom/src/index.ts#mount ,其封装了 packages/runtime-core/src/apiCreateApp.ts#mount
app.mount('#demo')
</script>
<style>
#demo {
font-family: 'Helvetica', Arial, sans-serif;
}
a {
text-decoration: none;
color: #f66;
}
li {
line-height: 1.5em;
margin-bottom: 20px;
}
.author,
.date {
font-weight: bold;
}
</style>
方法二:
方法二适合调试阅读项目具体的某个方法或某块代码。
安装 vscode 插件 jest runner,打开 Vue3 的测试文件,然后在相应的位置打断点,点击 debug 字样即可开始调试。
有时候会碰到缺少依赖包导致运行失败的情况,安装相应的 npm 包即可。
项目结构
- Vue3 的项目结构:
Vue3 的主要模块位于 packages 目录,我们 cd 到 packages 运行 tree -aI ".git*|.vscode" -C -L 1 列出所有的模块。
packages
├── compiler-core 平台无关的编译器. 它既包含可扩展的基础功能,也包含所有平台无关的插件。
├── compiler-dom 针对浏览器而写的编译器。
├── compiler-sfc
├── compiler-ssr
├── global.d.ts
├── reactivity 数据响应式系统,这是一个单独的系统,可以与任何框架配合使用
├── runtime-core 与平台无关的运行时。其实现的功能有虚拟 DOM 渲染器、Vue 组件和 Vue 的各种API,我们可以利用这个 runtime 实现针对某个具体平台的高阶 runtime,比如自定义渲染器。
├── runtime-dom 针对浏览器的 runtime。其功能包括处理原生 DOM API、DOM 事件和 DOM 属性等。
├── runtime-test 一个专门为了测试而写的轻量级 runtime。由于这个 rumtime 「渲染」出的 DOM 树其实是一个 JS 对象,所以这个 runtime 可以用在所有 JS 环境里。你可以用它来测试渲染是否正确。它还可以用于序列化 DOM、触发 DOM 事件,以及记录某次更新中的 DOM 操作。
├── server-renderer 用于 SSR
├── sfc-playground
├── shared 没有暴露任何 API,主要包含了一些平台无关的内部帮助方法。
├── size-check
├── template-explorer
└── vue 用于构建「完整构建」版本,引用了上面提到的 runtime 和 compiler。
其中最核心的模块可以分为两大部分:编译系统(compiler)和运行时(runtime,包括 reactivity),模块间的继承关系如下:
+---------------------+
| |
| @vue/compiler-sfc |
| |
+-----+--------+------+
| |
v v
+---------------------+ +----------------------+
| | | |
+------------>| @vue/compiler-dom +--->| @vue/compiler-core |
| | | | |
+----+----+ +---------------------+ +----------------------+
| |
| vue |
| |
+----+----+ +---------------------+ +----------------------+ +-------------------+
| | | | | | |
+------------>| @vue/runtime-dom +--->| @vue/runtime-core +--->| @vue/reactivity |
| | | | | |
+---------------------+ +----------------------+ +-------------------+
- Vue3源码的构建产物
Vue3 源码的构建产物位于packages/vue/dist目录,包含:
├── vue.cjs.js 适用于 commonjs 方式引入的 vue3 包,包括编译时compile和运行时runtime
├── vue.cjs.js.map
├── vue.cjs.prod.js 生产模式的包
├── vue.cjs.prod.js.map
├── vue.esm-browser.js 适用于 esmodule 方式引入的 vue3 包,适用于浏览器环境,包括编译时和运行时
├── vue.esm-browser.js.map
├── vue.esm-browser.prod.js
├── vue.esm-browser.prod.js.map
├── vue.esm-bundler.js 适用于 esmodule 方式引入的 vue3 包,适用于node环境,包括编译时和运行时
├── vue.esm-bundler.js.map
├── vue.global.js 既适用于浏览器环境又适用于node环境的包
├── vue.global.js.map
├── vue.global.prod.js
├── vue.global.prod.js.map
├── vue.runtime.esm-browser.js 只包含运行时的包
├── vue.runtime.esm-browser.js.map
├── vue.runtime.esm-browser.prod.js
├── vue.runtime.esm-browser.prod.js.map
├── vue.runtime.esm-bundler.js
├── vue.runtime.esm-bundler.js.map
├── vue.runtime.global.js
├── vue.runtime.global.js.map
├── vue.runtime.global.prod.js
└── vue.runtime.global.prod.js.map
阅读建议
- 如果你只需要了解 Vue3 最核心的特性,可以直接看编译系统或响应式系统的部分。
- 如果你想了解一个 Vue 应用从初始化到挂载、diff、卸载的整个过程,可以前往后阅读本系列文章。
- 如果你工作中不需要了解 Vue3 运行工程中的某些细节,直接跳过即可,一般不会影响你阅读后面的内容。
运行时(Runtime)+ 编译器(Compiler) vs. 只包含运行时(Runtime-only)
如果你需要动态编译模版(比如:将字符串模版传递给 template 选项,或者通过提供一个挂载元素的方式编写 html 模版),你将需要编译器,因此需要一个完整的构建包。
当你使用 vue-loader 或者 vueify 时,*.vue 文件中的模版在构建时会被编译为 JavaScript 的渲染函数。因此你不需要包含编译器的全量包,只需使用只包含运行时的包即可。
对比 vue.global.js 和 vue.runtime.global.js 可知,371581 / 579987 = 0.64,只包含运行时的包体积仅为全量包的体积的 64%,因此我们应该尽量使用只包含运行时的包。
各个包的大小, 单位 bit:
vue.cjs.js 2875
vue.cjs.js.map 5196
vue.cjs.prod.js 2268
vue.cjs.prod.js.map 4027
vue.esm-browser.js 530764
vue.esm-browser.js.map 1111379
vue.esm-browser.prod.js 112250
vue.esm-browser.prod.js.map 874911
vue.esm-bundler.js 2828
vue.esm-bundler.js.map 5140
vue.global.js 579987
vue.global.js.map 1124410
vue.global.prod.js 109903
vue.global.prod.js.map 868571
vue.runtime.esm-browser.js 350861
vue.runtime.esm-browser.js.map 725244
vue.runtime.esm-browser.prod.js 72336
vue.runtime.esm-browser.prod.js.map 596109
vue.runtime.esm-bundler.js 875
vue.runtime.esm-bundler.js.map 1797
vue.runtime.global.js 371581
vue.runtime.global.js.map 724952
vue.runtime.global.prod.js 71782
vue.runtime.global.prod.js.map 589807
我们会按照代码的执行顺序,包括 Vue 应用的初始化、挂载、模板编译、patch 等过程,完整的学习一下 Vue3 应用从初始化到渲染完成的整个过程。
Vue3 源码解读
- Vue3 源码解读(1)—— 环境搭建
- Vue3 源码解读(2)—— 初始化过程
- Vue3 源码解读(3)—— 挂载根节点
- Vue3 源码解读(4)—— 模板编译
- Vue3 源码解读(5)—— transform与generate
参考文献: