Vue2源码学习笔记之初识Vue

Vue 是前端三大 JavaScript 框架之一。它的核心是响应式原理,并被设计自底向上逐层应用的渐进式框架。 Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使

目录
  1. 源码结构
  2. 构建版本
  3. 编译入口
  4. Vue 的出生地
  5. 参考资料

Vue 是前端三大 JavaScript 框架之一。它的核心是响应式原理,并被设计自底向上逐层应用的渐进式框架。

Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

源码结构#

Vue 的源码结构非常清晰,在 src 里面的六个文件夹中。

下面是每个文件的简单介绍。

src
├── compiler        # 编译相关
├── core            # 核心代码
├── platforms       # 不同平台的支持
├── server          # 服务端渲染
├── sfc             # .vue 文件解析
├── shared          # 共享代码

构建版本#

Vue 的源码支持编译成各种各样的版本。

Vue 可以根据编译后的模块类型分成不同的版本,也可以根据是否含有编译器分为运行时版本和完整版本,还可以根据 Node 环境分为开发版本和生产版本。

Module

  • cjs:适用于 CommonJs 模块的环境,比如 Node 环境
  • esm:适用于 ES Module 的环境
  • umd:适用于浏览器运行环境,使用script标签引用

Runtime

  • runtime:只有运行时的代码,没有编译器,此时需要使用vue-loader来编译模板代码,不能编译template上的代码。
  • runtime + compiler:携带编译器,可以编译template上的代码。

ENV

  • development:开发版本,包含完整的警告和调试模式。
  • production:生产版本,删除了警告,gzip 后只有 33.3KB。

详情可以查看文档

编译入口#

如何查找编译入口呢?

首先我们查看包管理文件package.json中的构建代码的脚本命令。

"scripts": {
  // ...
  build": "node scripts/build.js"
}

再查看构建的脚本文件scripts/build.js的代码

// ...
let builds = require("./config").getAllBuilds();
 
// filter builds via command line arg
if (process.argv[2]) {
  const filters = process.argv[2].split(",");
  builds = builds.filter((b) => {
    return filters.some(
      (f) => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1
    );
  });
} else {
  // filter out weex builds by default
  builds = builds.filter((b) => {
    return b.output.file.indexOf("weex") === -1;
  });
}
 
build(builds);
// ...

里面引入了一个配置文件config.js,查看配置文件scripts/config.js,选择其中的一个编译入口。

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

entry属性是编译入口。但是resolve方法中的web是文件别名,查看scripts/alias.js文件别名的代码。

web: resolve("src/platforms/web");

最后拼接路径发现编译的入口文件路径是下面这样的。

entry: "src/platforms/web/entry-runtime-with-compiler.js";

这才是源码的入口文件,这是一个带有编译器并且会被编译成cjs模块类型的入口文件。

Vue 的出生地#

Vue 的出生地文件是项目最开始的文件,一切代码都是从这里开始。那么要如何查找呢?

打开platforms/web/entry-runtime-with-compiler.js,发现这只是 Web 平台中带编译器的源码入口,并不是 Vue 的出生地文件,继续查找,看下面的代码。

import Vue from "./runtime/index"; // ! 导入 Runtime 版本的 Vue

这里导入了 Runtime (运行时)版本的 Vue,这也不是 Vue 的出生地文件,打开这个文件,路径在platforms/web/runtime/index.js中,继续查找,看下面的代码。

import Vue from "core/index";

这里引入了 core 文件夹下的 Vue ,因为 core 也是别名,在scripts/alias.js文件中发现。

core: resolve('src/core'),

然后打开src/core/index.js文件,发现这个文件也不是出生地文件,看下面的代码。

import Vue from "./instance/index";

现在终于找到了 Vue 的出生地文件 ,路径在core/instance/index.js中。

import { initMixin } from "./init";
import { stateMixin } from "./state";
import { renderMixin } from "./render";
import { eventsMixin } from "./events";
import { lifecycleMixin } from "./lifecycle";
import { warn } from "../util/index";
 
function Vue(options) {
  if (process.env.NODE_ENV !== "production" && !(this instanceof Vue)) {
    warn("Vue is a constructor and should be called with the `new` keyword");
  }
  this._init(options);
}
 
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
 
export default Vue;

也就是说,核心代码 core 文件下的 instance 文件夹的index.js才是 Vue 的出生地文件。

总结一些查找路径。

-> 完整版本的的入口文件 entry-runtime-with-compiler.js
-> 运行时版本的入口文件 runtime/index.js
-> 核心代码的入口文件 core/index.js
-> 出生地文化 core/instance/index.js

在出生地文件中,函数Vue就是 Vue 的构造函数,一切都是从这个函数开始。

我们在使用 Vue 的时候,是通过使用new方法创建一个实例开始的。

import Vue from "vue";
 
const vm = new Vue({
  //... options
});

但是,Vue 创建实际的过程是什么样子的呢?

参考资料#