阅读 1509
Vue源码系列(一):Vue源码解读的正确姿势

Vue源码系列(一):Vue源码解读的正确姿势

对未来最好的馈赠,就是珍惜现在的时光,努力的付出,勇敢的面对,做一切想做的事,完成一个个眼前的目标,不辜负当下。

前言

这是一个Vue源码系列文章,建议从第一篇文章 Vue源码系列(一):Vue源码解读的正确姿势 开始阅读。

作为一名开发人员,阅读源码是非常好的学习方式,尤其Vue又是当下很受欢迎的前端框架,随着用的人越来越多,不断推动着源码地完善。在源码中有着各位大佬多年积淀下来的精华,而这些精华就非常值得我们学习。或许刚开始我们看不懂或者看着很蒙圈,但只要坚持不断的摸索,总有掌握的一天。小时候在我说读书难的时候我爸就常对我说:书读百遍其义自见。小时候感觉懂这句话又感觉不是真的懂,而现在每次学习遇到困难想退缩的时候这句话却总能激励着我,我觉得他能给我动力可能是因为看着父辈越来越老而我还一事无成吧。哎~ 扯远了!总之:对未来最好的馈赠,就是把一切献给现在。

读源码的理由

源码的阅读很多人其实是为了面试,想找一个更满意的工作。但是我们也忽略了一个重要的东西,那就是在我们不断阅读源码或者熟读某个主流框架源码的同时,其实这个过程也在潜移默化改变我们的编码能力和程序的设计能力。

下面我们就正式开始我们的Vue源码之旅吧。

Vue源码下载

首先找到Vue项目 github 地址:链接

Vue项目大体就长下面这个样子

image.png

克隆代码

把Vue项目克隆到本地

git clone https://github.com/vuejs/vue.git
复制代码

本地的目录

image.png

解读目录

首先看一下Vue项目的一级目录

└── vue
    ├── BACKERS.md
    ├── FILE_README.md
    ├── LICENSE
    ├── README.md
    ├── benchmarks
    ├── dist
    ├── examples
    ├── flow
    ├── package.json
    ├── packages
    ├── scripts
    ├── src
    ├── test
    ├── types
    └── yarn.lock
复制代码

然后对目录做一个解析

vue源码目录.png

主要说一下核心的源码文件“src"

├─ src                         // 主要源码所在位置,核心内容
│   ├─ compiler                // 模板编译相关文件,将 template 编译为 render 函数
│       ├─ codegen             // 把AST(抽象语法树)转换为Render函数
│       ├─ directives          // 生成Render函数之前需要处理的东西
│       ├─ parser              // 模板编译成AST
│   ├─ core                    // Vue核心代码,包括了内置组件、全局API封装、Vue实例化、响应式原理、vdom(虚拟DOM)、工具函数等等。
│       ├─ components          // 组件相关属性,包含抽象出来的通用组件 如:Keep-Alive
│       ├─ global-api          // Vue全局API,如Vue.use(),Vue.nextTick(),Vue.config()等,包含给Vue构造函数挂载全局方法(静态方法)或属性的代码。 链接:https://012-cn.vuejs.org/api/global-api.html
│       ├─ instance            // 实例化相关内容,生命周期、事件等,包含Vue构造函数设计相关的代码
│       ├─ observer            // 响应式核心目录,双向数据绑定相关文件。包含数据观测的核心代码
│       ├─ util                // 工具方法
│       └─ vdom                // 虚拟DOM相关的代码,包含虚拟DOM创建(creation)和打补丁(patching)的代码
│   ├─ platforms               // vue.js和平台构建有关的内容 不同平台的不同构建的入口文件也在这里 (Vue.js 是一个跨平台的MVVM框架)
│       ├── web                // web端 (渲染,编译,运行时等,包括部分服务端渲染)
│       │   ├── compiler       // web端编译相关代码,用来编译模版成render函数basic.js
│       │   ├── entry-compiler.js               // vue-template-compiler 包的入口文件
│       │   ├── entry-runtime-with-compiler.js  // 独立构建版本的入口,它在 entry-runtime 的基础上添加了模板(template)到render函数的编译器
│       │   ├── entry-runtime.js                // 运行时构建的入口,不包含模板(template)到render函数的编译器,所以不支持 `template` 选项,我们使用vue默认导出的就是这个运行时的版本。
│       │   ├── entry-server-basic-renderer.js  // 输出 packages/vue-server-renderer/basic.js 文件
│       │   ├── entry-server-renderer.js        // vue-server-renderer 包的入口文件
│       │   ├── runtime        // web端运行时相关代码,用于创建Vue实例等
│       │   ├── server         // 服务端渲染(ssr)
│       │   └── util           // 工具类相关内容
│       └─ weex                // 混合运用 weex框架 (一端开发,三端运行: Android、iOS 和 Web 应用) 2016年9月3日~4日 尤雨溪正式宣布以技术顾问的身份加盟阿里巴巴Weex团队, 做Vue和Weex的整合 让Vue的语法能跨三端
│   ├─ server                  // 服务端渲染相关内容(ssr)
│   ├─ sfc                     // 转换单文件组件(*.vue)
│   └─ shared                  // 共享代码 全局共享的方法和常量
复制代码

熟悉了大概的一个目录信息,我们可以把Vue项目启动起来,为之后的源码之旅做好充足的准备

安装依赖

npm i
or
yarn add all 
复制代码

启动项目

启动项目之前先说一个配置,在 package.json 文件中,咱们需要在 scripts 配置中给 dev 加上 --sourcemap。 加上以后在运行项目(npm run dev)的时候会生成带编译器版本的 vue.js 和其对应的 .map 文件,在源码解读时就可以引用dist目录下的 vue.js 文件,从而通过 vue.js.map 在浏览器中调试,就能关联到对应的源码文件。

// package.json
  "scripts": {
    "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap",
    ......
  },
复制代码

启动成功以后并生成带编译器版本的 vue.js 和其对应的 .map 文件,如下图所示

image.png

查看案例

启动以后随便在 examples 文件夹中写一个案例测试一下

image.png

加 --sourcemap 的原因

这里咱们在测试案例中打一个 debugger 说一下加 --sourcemap 的原因,这是咱们解读源码的重要一步。

案例代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- 首先引入运行项目生成的 dis/vue.js -->
  <script src="../../dist/vue.js"></script>
</head>
<body>
  <!-- 然后编写测试代码 -->
  <div id="app">
    {{ message }}
  </div>
  <script>
    debugger
    new Vue({
      el: '#app',
      data () {
        return {
          message: '测试一下'
        }
      }
    })
  </script>
</body>
</html>
复制代码

在 new Vue() 之前加上 debugger,查看运行情况

1、不加 --sourcemap

没有加 --sourcemap 的情况,就不会生成 vue.js 对应的 .map 文件,也就关联不到对应的源码文件

image.png

image.png

2、加上 --sourcemap

加上 --sourcemap 就会生成 vue.js 对应的 .map 文件,同时也就能关联到对应的源码文件

image.png

image.png

补充说明 - dist 目录

在Vue项目中,dist 目录中其实有很多文件,这些文件在 dist/README.md 或者 github-dist 中其实都已经有解释了,但说的比较简洁并且还是英文文安,这里统一解释一下吧。

生成文件的解释

UMDCommonJSES Module
Fullvue.jsvue.common.jsvue.esm.js
Runtime-onlyvue.runtime.jsvue.runtime.common.jsvue.runtime.esm.js
Full (production)vue.min.js
Runtime-only (production)vue.runtime.min.js

UMD、CommonJS、ES Module

简单的普及一下吧,AMD、CMD、CommonJS、UMD和ES Module是js模块化规范的几个名词。出现这些规范是因为早期的js语言是没有模块化体系的,开发者为了方便代码组织,就逐渐形成AMD和CMD规范,后来出现了主要在nodejs中使用的CommonJS规范,但为了代码复用,又提出了UMD规范来统一兼容AMD、CMD、CommonJS。最后呢随着js语言的不断发展,模块化也越来越重要,所以就又增加了ES Module。

CommonJS

这里的 CommonJS 主要是用来配合老的打包工具比如 webpack 1 或者 Browserify。这些打包工具的默认文件 (pkg.main) 是只包含运行时的 CommonJS 版本 (vue.runtime.common.js)。

ES Module

从 2.6 开始 Vue 提供两个 ES Modules (ESM) 构建文件:

1、一个是为打包工具提供的 ESM:比如 webpack 2 或 Rollup 提供的现代打包工具。ESM 格式被设计为可以被静态分析,所以打包工具可以利用这一点来进行 “tree-shaking” 并将用不到的代码排除出最终的包。为这些打包工具提供的默认文件 (pkg.module) 是只有运行时的 ES Module 构建 (vue.runtime.esm.js)。

2、另一个是为浏览器提供的 ESM (2.6+):用于在现代浏览器中通过

注:具体的前端工程化这边有介绍:👉 前端工程化浅谈和模块化方案深耕👈

Full

包含编译器(Compiler)和运行时(Runtime),是一个完整的构建。可以认为是 Compiler-Runtime

编译器(Compiler)

负责将模板字符串编译成为 JavaScript 渲染函数的代码。

运行时(Runtime)

负责创建 Vue 实例、渲染和修补虚拟 DOM 等的代码。

Runtime-only

Full 不同的是 Runtime-only 只包含运行时(Runtime)的构建。

Full (production)

就是生产环境的 Full

Runtime-only (production)

就是生产环境的 Runtime-only

补充说明:Compiler-Runtime(Full)和 Runtime-only 的区别

简单概述就是:

  • 如果你需要在客户端编译模板,就将需要加上编译器,即完整版(Compiler-Runtime), 举个例子:比如需要传入一个字符串给template 或者 挂载到一个元素上,并以其 DOM 内部的 HTML 作为模板
  • 如果你使用的是.vue文件开发,那么就选择 Runtime-only,只需要运行时代码

如果还不明白,这么可以理解为: 一个组件渲染到页面中的过程包括这几个步骤 Template ——> AST(抽象语法树) ——> Render ——> VDom(虚拟Dom) ——> 真实的Dom ——> 最终页面

1、 Compiler-Runtime 需要编译器(Compiler)把Vue中的模板最终渲染成真实DOM, 也就是上面的整个流程:Template ——> AST ——> Render ——> VDom ——> 真实的Dom ——> 最终页面

new Vue({
    el: '#app',
    components: { App },
    template: '<App/>.'
})
复制代码

2、Runtime-only 指定render函数,通常借助webpack的vue-loader工具,在构建时进行了预编译(将.vue文件编译为js),所以只包含运行时的 Vue.js 代码。

这里普及一下:webpack打包时已经将 template 编译为 render 函数。具体一点就是 template 会通过 vue-template-compiler 转换为 render 函数, 所以不需要在客户端进行编译。

因此只需要运行时(Runtime)就可以把Vue中的模板最终渲染成真实DOM

具体流程: Render ——> VDom ——> 真实的Dom ——> 最终页面

new Vue({
    el: '#app',
    render: h => h(App)
})
复制代码

运行时版本和完整版的对比

最终打好的包里实际上是不需要编译器的,只用运行时版本即可,因为当使用 vue-loader 或 vueify 的时候,*.vue 文件内部的模板会在构建时预编译成 JavaScript。

还有运行时版本相比完整版体积要小 30% 左右,所以尽可能使用这个版本就可以。但如果仍然想使用完整版,则需要在打包工具里做一下配置,具体配置如下:

webpack

module.exports = {
  // ...
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // 用 webpack 1 时需用 'vue/dist/vue.common.js'
    }
  }
}
复制代码

Rollup

const alias = require('rollup-plugin-alias')

rollup({
  // ...
  plugins: [
    alias({
      'vue': require.resolve('vue/dist/vue.esm.js')
    })
  ]
})
复制代码

Browserify

{
  // 在项目的 `package.json`中添加:
  "browser": {
    "vue": "vue/dist/vue.common.js"
  }
}
复制代码

Parcel

{
  // 在项目的 `package.json`中添加:
  "alias": {
    "vue" : "./node_modules/vue/dist/vue.common.js"
  }
}
复制代码

结语

到此 “Vue源码解读的正确姿势” 就准备完毕了,下一篇将开始真正的源码解读:Vue源码系列(二):Vue初始化都做了什么?

文章完成再把链接贴上来,希望大家多多支持,欢迎点赞加关注🙏

文章分类
前端
文章标签