Vue 2.6.x 源码阅读-上手篇

988 阅读3分钟

前言

本文将介绍阅读 Vue 2.6.x 版本源码的涉及的一些前置知识,主要包括源码的目录结构,基本调试方式、各种版本打包时使用到的入口文件及如何给源码打断点并查看源码执行流程。

源码地址

目录结构

image.png

  • dist: 各种构建结果的打包文件,dist/README 中有详细说明。
  • examples: 使用 Vue 实现的各种示例。
  • src: 源码部分

image.png

  • src/compiler: 将模板 template 转换成 render 函数,render 函数转换成虚拟 DOM。

  • src/core: 核心代码。

image.png

  • src/core/components: vue 内置的一些组件,比如 keep-alive 等。

  • src/core/global-api: 定义 vue 中的静态方法,比如:use/extend 等方法。

  • src/core/instance: vue 实例的相关内容,如:生命周期方法等。

  • src/core/observer: 响应式相关。

  • src/core/vdom: 虚拟 dom 相关。

  • src/platforms: 不同平台的相关代码,分 web、weex 两类。

  • src/server: 服务端渲染相关的代码。

  • src/sfc:将单文件转换成 js 对象。

  • src/shared: 公共代码。

调试方法

Vue 2.6.x 使用的是 Rollup 进行打包,它与 webpack 不同的地方就是,webpack 是对所有文件资源进行处理,而 Rollup 只针对 js 文件。

  1. 使用 npm 安装依赖
npm i
  1. 给 package.json 中需要运行的 scripts 添加 sourcemap 命令行参数。
"scripts": {
    ...,
    "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
    ...
}

rollup 命令行参数说明:

  • -w: watch 模式,当源码发生变化后,会重新打包。
  • -c: 设置打包命令的配置文件。
  1. 删除 dist 文件夹,运行 npm run dev 重新打包生成带有 sourcemap 配置的 vue.js 文件。

  2. 打开项目 'examples/grid',修改示例中 index.html 文件中 script 标签的 src 路径为上一步打包后生成的 vue.js 文件。

image.png

  1. 在控制台中定位到 gird 示例中使用 new Vue 创建实例的代码,添加断点,再次刷新后便可以进行调试。

image.png

Vue 中不同的构建版本

说明文件:github.com/vuejs/vue/b…

运行时和编译器区别

运行时:可以理解为精简后的编译器版本,只包含 Vue 实例,渲染函数和虚拟 DOM。 编译器:是将 template 模板内容转换成 JavaScript 的 render 函数。

脚手架使用的构建版本

使用 vue cli 创建一个项目,在项目中使用以下命令可以生成项目中与 vue 相关的配置文件。

vue inspect > output.js

output.js 中存在以下内容:

alias: {
  '@': 'D:\\xxx\\vue-template\\src',
  vue$: 'vue/dist/vue.runtime.esm.js'
},

当我们在 main.js 中使用 import vue from vue 时,使用的是 vue.runtime.esm.js 版本的代码。

入口文件

当我们使用 npm run dev 运行项目时,会执行以下的命令:

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"

该命令设置了一个环境变量 TARGET,它的值为 web-full-dev。并指定配置文件为 scripts/config.js。

scripts/config.js

  1. builds,line 38 ~ 214。
const aliases = require('./alias')

const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

const builds = {
    ...
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
    ...
}

builds 存储的是环境变量及对应的打包配置变量,包括入口文件(entry)、目标输出文件(dest)、模块化格式(format)。其中,resolve 函数会对路径进行特殊处理,最终得到入口文件路径为:src/platforms/web/entry-runtime-with-compiler

  1. genConfig,line 216 ~ 236。以环境变量作为参数名,从 builds 中读取打包变量,并生成一个 rollup 打包配置。

  2. 设置环境变量后,会根据环境变量导出对应的打包配置。

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

template 和 render 优先级

在示例中添加以下断点,可以看到 Vue 实例对象创建后会调用 $mount 方法。

image.png

Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && query(el);

    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
       warn(
        "Do not mount Vue to <html> or <body> - mount to normal elements instead."
      );
      return this
    }

    var options = this.$options;
    // resolve template/el and convert to render function
    // 未设置 render 函数,将 template 或 el 中的内容转换成 render 函数
    if (!options.render) {
      源码较长,这里就不贴了...
    }
    // 直接使用 render 内容处理
    return mount.call(this, el, hydrating)
  };

Vue.prototype.$mount 函数的主体逻辑如下:

  1. 获取当前元素的 Dom 结点。
  2. 判断当前元素是否为 body 或 document 元素。是则函数直接返回。
  3. 读取 Vue 实例创建时传递的配置,没有设置 render 函数,则将 template 或 el 内容转换成 render 函数。否则直接返回 render 函数。

结论: 创建 Vue 实例的时候,同时设置 template 和 render 函数的情况下,render 函数的优先级更高。

总结

以上,本文对 Vue 2.6.x 源码阅读做了简略的介绍。在了解关于源码的背景知识、入口文件及断点调试后,我们阅读源码的第一步就完成了。