[关闭]
@wy 2020-07-08T17:24:27.000000Z 字数 2421 阅读 476

vue源码一:文件构建&目录设计

vuejs


在看源码时,无论有多少个目录,多么复杂的文件引用,是有规律可循的,有些经验要内化成常识。

先找入口文件

当打开项目时,先扫一眼目录,大致有个印象,然后从 package.json 开始,找到 scripts 位置,这是给 npm run 执行的命令。

找到一般名称为 build 的字段,要进行构建。从 build 后面要执行的命令,找到构建执行文件,Vue项目的如下所示:

  1. scripts: {
  2. ... 其余省略
  3. "build": "node scripts/build.js",
  4. }

实际上 scripts 中有许多待执行的命令,那些可以忽略不看。找到一条线慢慢展开,不要多条线齐头并进,这样会更凌乱。如果有测试文件,可以看 test 类命令,作者会对关键点写测试用例,通过测试用例能够清晰的看到程序运行的过程,可以在源码中进行打印调试信息,这在看 Vue3 源码时,目录组织非常清晰,源码和测试用例在一个目录下。

Vue2 源码中测试用例不多,在浏览器中运行,用断点调试。

我们选择的线是从打包成最终的文件入手。

打开 scripts 文件下的 build.js ,用的是 rollup 构建工具,这类构建工具包括 webpack,需要设置相关配置项,按这个思路,一路找到配置信息,就能找到需要打包的入口文件。

配置信息在 scripts/config.js 中,配置有很多个入口。

这些入口打包出的文件,提供给不同平台使用:浏览器、node,Weex;打包出的模块规范也不相同: CommonJS、ES modules、UMD 模块;还区分构建为生产环境和开发环境的文件。

总之满足了不同平台、不同模块规范、不同环境。

主要分析在 web 平台下的代码, 找到相关入口:

  1. const builds = {
  2. 'web-runtime-prod': {
  3. entry: resolve('web/entry-runtime.js'),
  4. },
  5. 'web-full-prod': {
  6. entry: resolve('web/entry-runtime-with-compiler.js'),
  7. },
  8. 'web-compiler-browser': {
  9. entry: resolve('web/entry-compiler.js'),
  10. }
  11. }

打包成了三个文件:

  1. compiler 是编译器文件,用来解析模板字符串,并编译成可运行的 javascript 函数代码。
  2. runtime 运行时文件, 用来创建 vue 实例,处理虚拟 DOM,渲染到平台等。除了编译器以外所有代码。
  3. 完整文件,包含了 compilerruntime 的代码。

预编译模板

在写 Vue 项目时,在模板中写渲染数据的标识 {{}}, 或者 Vue 提供的各种指令,这都不被浏览器所理解,就需要有一个编译器将模板进行解析,这就是 compiler 擅长干的事。利用完整版提供的 Vue.compile 来理解:

  1. // 解析模板,返回包含 render函数项
  2. const { render } = Vue.compile(`<div>{{message}}</div>`);
  3. console.log(render.toString());

打印的 render 函数为

  1. function anonymous() {
  2. with(this){return _c('div',[_v(_s(message))])}
  3. }

可以看到 render 是一个函数,在 with 作用域中,都指向函数作用域中的 this,从 this 上找 _c_v_s 函数,包括 message 属性。

有了这个 render 函数,在需要渲染到浏览器时,可以将其执行。

  1. const vm = new Vue({
  2. el: "#app",
  3. data: {
  4. message: "hello"
  5. },
  6. render() {
  7. console.log(render.call(this));
  8. return render.call(this)
  9. }
  10. });
  11. console.log(vm)

最终生成在页面中就是一个 div 元素,子级文本为 hello

调用 render 传入的是当前组件的实例对象,可以从这个对象上找到以上需要的 _c_v_s 方法,用来创建虚拟 DOM,并从实例对象上找到所需要的属性,触发内部的依赖收集程序。所以当你打印出一个组件实例时,可以看到写在 data 中的数据,挂载在了实例对象上:

  1. {
  2. ... 其他属性省略,可以自己打印一下
  3. _c: function () {},
  4. message: 'hello'
  5. }

这个过程目前看来有些跳跃,之后会详细分析。

调用 render 后生成的是 VNode

内部拿到虚拟 DOM 后,根据渲染器提供的方法,生成真实的 DOM,渲染到页面中。

当然,如果提供了针对不同平台的渲染器,则生成对应平台的元素,例如 提供了生成 Weex 平台的渲染器,则调用 Weex 平台生成元素的 API,这个感兴趣的可以自己去探索。我们只针对 web 平台的渲染器来讨论,内部实际调用的就是 DOM 提供的方法。

写项目常用的是 vue-cli 创建的单文件组件(sfc),会在打包构建阶段预先编译了模板,生成 render 函数,这样在浏览器端免去编译模板的步骤,可以只引入 runtime 文件,体积会小很多,极大的提高了加载文件和运行速度。

目录设计

先来看下源码的目录设计,主要集中在 src 目录下,每个文件夹的作用:

  1. src
  2. ├── compiler # 编译相关- 模板解析为ast,优化ast,代码生成等功能
  3. ├── core # 核心代码- 提供Vue构造函数、全局API、内置组件、虚拟DOM、观察者等
  4. ├── platforms # 不同平台的支持- 提供 web 平台 和 Weex 平台入口
  5. ├── server # 服务端渲染- 运行在node,把组件解析为填充数据后的模板字符串
  6. ├── sfc # .vue文件解析- 会把单文件组件解析为一个 Javascript 对象
  7. ├── shared # 共享代码- 浏览器端和node共享的代码

主要会分析 web 平台下构建的代码。

目录地址:https://github.com/vuejs/vue/tree/dev/src

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注