Skip to content

vue2 项目结构

1. 源码目录结构

1.1 整个源码工程目录结构

  • .circleci 持续集成
  • benchmarks 性能评测
  • dist 输出目录
  • examples 案例
  • flow flow 声明文件
  • packages vue 中的包
  • scripts 工程化
  • src 源码目录
  • test 测试相关
  • types ts 声明文件

1.2 src源码目录结构

src是写源代码的目录,单独以树结构的方式来呈现更直观

sh
├─compiler       # 编译的相关逻辑
  ├─codegen
  ├─directives
  └─parser
├─core           # vue核心代码
  ├─components  # vue中的内置组件 keep-alive ...
  ├─global-api  # vue中的全局api
  ├─instance    # vue中的核心逻辑
  ├─observer    # vue中的响应式原理
  ├─util
  └─vdom        # vue中的虚拟dom模块
├─platforms      # 平台代码
  ├─web      # web逻辑 - vue
  ├─compiler
  ├─runtime
  ├─server
  └─util
  └─weex        # weex逻辑 - app
      ├─compiler
      ├─runtime
      └─util
├─server         # 服务端渲染模块
├─sfc            # 用于编译.vue文件
└─shared         # 共享的方法和常量

到这里可能只能看的懂文件夹的组成,还无法知道代码是如何运行的,所以需要通过package.json来查找代码是如何被打包的!

2. 打包流程

一般看一个项目,要找到项目入口文件,都会从package.json文件入手,packahe.josn文件里面有一个main字段和一个module字段,若都没有,会找index.js作为入口问价。但是在src下没有index.js文件、若是commonjs规范,会首先找main字段作为入口,webpack则会找module作为入口,但是vue里面这两个字段都是指向打包后的文件,所以得先分析打包命令。

sh
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",

可以看到,三个打包命令,核心其实是通过node来执行 scripts/build.js来打包,然后通过传入不同的参数实现不同的打包结果。这里的 -- 代表后面的内容是参数。

1.build.js文件

先在build.js里面找一下打包的入口点。

js
// 1.获取不同的打包的配置
let builds = require("./config").getAllBuilds();

// 2.根据执行打包时的参数进行过滤
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 {
  // 默认不打包weex相关代码
  builds = builds.filter((b) => {
    return b.output.file.indexOf("weex") === -1;
  });
}
// 3.进行打包
build(builds);

1. 不同的打包配置指的是

  • web/weex 不同的平台:这里先不关注 weex,web 指代的就是我们常用的 vue
  • Runtime only/Runtime + compiler 是否带编译模块:带有 compiler 的会将模板转化成 render 函数
  • CommonJS/es/umd 打包出不同模块规范:umd模块是整合了CommonJSAMD两个模块定义规范的方法,当不支持时两种模块时会将其添加到全局变量中

2. 打包入口

sh
src/platforms/web/entry-runtime.js
src/platforms/web/entry-runtime-with-compiler.js

我们可以通过打包的配置找到我们需要的入口,这两个区别在于是否涵盖compiler逻辑,我们在开发时一般使用的是entry-runtime,可以减小vue的体积,但是同样在开发时也不能再使用template,.vue文件中的template是通过vue-loader来进行编译的,和我们所说的compiler无关,compiler是在比如说,在html文件内使用vm时候的字符串模板时候才需要的东西。 例如:

js
new Vue({
  template: `<div>第一个模板</div>`,
});

上面这样的template必须要使用带compiler的入口才能进行模板的解析.

3. 入口分析

此处,为了分析vue完整代码,选择了带有compiler的文件进行分析 src/platforms/web/entry-runtime.js文件(不带编译器compiler

js
import Vue from "./runtime/index"; // 引入运行时代码

export default Vue;

src/platforms/web/entry-runtime-with-compiler.js文件(带compiler

js
import Vue from "./runtime/index"; //
const mount = Vue.prototype.$mount; //  2.获取runtime中的$mount方法
Vue.prototype.$mount = function (el, hydrating) {
  // 3. 重写$mount方法
  el = el && query(el);
  const options = this.$options;
  if (!options.render) {
    // 4.没有render方法就进行编译操作
    let template = options.template;
    if (template) {
      // 5.将模板编译成函数
      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          outputSourceRange: process.env.NODE_ENV !== "production",
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments,
        },
        this
      );
      options.render = render; // 6.将render函数放到options中
    }
    // todo...
  }
  return mount.call(this, el, hydrating); // 7.进行挂载操作
};
export default Vue;

观察这两个入口的文件不难发现他们都引入了 runtime/index.js

带有compiler的文件仅仅是对$mount方法进行了重写,增添了将template变成render函数的功能

4. Vue 的构造函数

image.png

  • instance/index.js:真正的Vue的构造函数,并在Vue的原型上扩展方法
  • core/index.js:增加全局API方法
  • runtime/index.js:扩展$mount方法及平台对应的代码