查找入口文件

What

什么是入口文件呢?可以理解为就是 Vue 源码的出发点,从这个出发点,可以查找 Vue 其他的所有内容。

Why

找入口文件是为了方便我们从源头开始理解和学习 Vue 的源码。

How

接下来我们就开始找 Vue 的入口文件。 现在的项目,一般情况下从 git 上 clone 下来的项目就会有一个 README 文件,而这个文件呢就告诉我们如何对项目进行打包,如何是项目运行起来,那么我们看一下 Vue 这个项目的 READM 中的 NPM scripts 脚本。

# watch and auto re-build dist/vue.js
$ npm run dev

# watch and auto re-run unit tests in Chrome
$ npm run dev:test

# build all dist files,including npm packages
$ npm run build
1
2
3
4
5
6
7
8

通过以上呢我们知道 Vue 的打包命令是 npm run dev,那么我们就具体看一下 Vue 中的 packages.json 文件的 scripts 脚本

"scripts": {
    "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
  },
1
2
3

通过上面的脚本呢,知道 Vue 使用的是 Rollup 进行打包的,并且打包的配置文件在 scripts/config.js 文件中,此时请记住TARGET:web-full-dev,下面会有用到。代码如下:

if (process.env.TARGET) {
  // npm run dev的时候process.env.TARGET=web-full-dev
  module.exports = genConfig(process.env.TARGET);
} else {
  exports.getBuild = genConfig;
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig);
}
1
2
3
4
5
6
7

TIP

直接看文件的最后呢是因为module.exports通常都是在最后,并且通常导出的内容就是供其他地方使用的,方便找到主要的函数。
process 是 Node 的环境变量,process.env返回一个包含用户环境信息的对象。若想进一步学习,请移步Node Process

上面代码是在文件scripts/config.js的最后,用了ifprocess.env.TARGET进行了判断,我们也知道npm run dev中指明了TARGET:web-full-dev,因此process.env.TARGET==web-full-dev。接下来就是执行genConfig('web-full-dev')(我已经将行参变成了实参)。看一下genConfig()方法(这个方法就在scripts/config.js内):

function genConfig(name) {
  // 此时name===web-full-dev

  const opts = builds[name];
  // builds[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
  // },
  const config = {
    input: opts.entry, //这里就是入口文件 opts.entry===resolve("web/entry-runtime-with-compiler.js") 调用了resolve方法
    external: opts.external,
    plugins: [
      replace({
        __WEEX__: !!opts.weex,
        __WEEX_VERSION__: weexVersion,
        __VERSION__: version
      }),
      flow(),
      buble(),
      alias(Object.assign({}, aliases, opts.alias))
    ].concat(opts.plugins || []),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || "Vue"
    }
  };

  if (opts.env) {
    config.plugins.push(
      replace({
        "process.env.NODE_ENV": JSON.stringify(opts.env)
      })
    );
  }

  Object.defineProperty(config, "_name", {
    enumerable: false,
    value: name
  });

  return config;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

有一些说明已经在代码中注释了,并且都将行参变成了实参,那么我们就进一步解释一下: const opts = builds[name];就是定义了一个常量opts,这个常量的值是builds[name],那么 builds 是什么呢?看代码(builds 依然在scripts/config.js):

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
    },
    ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13

因此就找到了opts这个常量的具体的值是什么。由此我们知道了input:opts.entry===resolve("web/entry-runtime-with-compiler.js")。此时呢,又调用了resolve()方法:

TIP

由于本章节只是为了找入口文件,因此我们只需要关注 const config={}中的input:opts.entry

//resolve方法在scripts/config中
// resolve("web/entry-runtime-with-compiler.js")
const resolve = p => {
  // base='web'
  const base = p.split("/")[0];
  if (aliases[base]) {
    // aliases是在文件头部引入的
    // const alias = require("rollup-plugin-alias");
    // alias文件在scripts/alias.js
    // alias = {
    //   vue: resolve("src/platforms/web/entry-runtime-with-compiler"),
    //   compiler: resolve("src/compiler"),
    //   core: resolve("src/core"),
    //   shared: resolve("src/shared"),
    //   web: resolve("src/platforms/web"),
    //   weex: resolve("src/platforms/weex"),
    //   server: resolve("src/server"),
    //   entries: resolve("src/entries"),
    //   sfc: resolve("src/sfc")
    // }
    // aliases[base]===aliases['web']===src/platforms/web
    return path.resolve(aliases[base], p.slice(base.length + 1));
  } else {
    return path.resolve(__dirname, "../", p);
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

通过以上文件的逐步解析,最终opts.entry===src/platforms/web/entry-runtime-with-compiler.js,因此找到了打包的开始文件。
到这里呢,还不算真正的完成,因为找入口文件就是为了要看new Vue()的时候这个Vue到底是个什么,因此呢进入src/platforms/web/entry-runtime-with-compiler.js文件,会看到:

...
import Vue from "./runtime/index";
...
1
2
3

我们就跟着这个 import 一路寻找,进入src/platforms/web/runtime/index.js:

import Vue from "core/index";
...
1
2

进入src/core/index.js:

import Vue from "./instance/index";
...
1
2

进入 src/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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

到这里,终于看到了 Vue 的定义,因此也就找到了 Vue 的根源。Vue 就是一个构造函数。接下来就分析一下 Vue 的初始化。

总结

分析了半天呢,其实就是要去解析一下文件的路径,一切以找到 Vue 最初的定义为线索,从而跟着这条线去找,这样才不会走偏。在我们阅读源码的时候呢,不是一开始就要把所有的内容都看的,而是要有一个主线的去找,不必要的内容呢我们先放在一边,等到需要的时候再去抽丝剥茧,这样就会容易很多啦。

支付宝
微信