Skip to content

源码笔记(二):源码结构及调试介绍

项目准备

拉取 vue 代码到本地仓库,并切换版本到 v2.6.11

bash
git clone https://github.com/vuejs/vue.git
git checkout v2.6.11

进入 vue 文件夹根目录下安装依赖:

bash
yarn

目录结构

忽略掉 .babelrc.js,.editorconfig 等常规项目文件,只介绍 vue 相关。

js
├─ benchmarks                 // 基准数据,一些测试性能的 demo,用于与竞品框架对比
├─ dist                       // 构建后输出的不同版本的 Vue 文件
├─ examples                   // 用 Vue 写的一些小 demo
├─ flow                       // flow 静态类型声明文件
├─ packages                   // 构建后输出的服务端渲染、模板编译器、weex 相关的NPM包
├─ scripts                    // 存放 npm scripts 执行的各种 script,用于项目编译、测试、构建。
├─ src                        // 核心
│   ├─ compiler               // 编译器相关,用于解析模版,template->render()
│       ├─ codegen            // AST 转换为 render()
│       ├─ directives         // 生成 render() 之前需要处理的指令
│       ├─ parser             // template 解析为 AST
│       ├─ codeframe.js       // 导出 generateCodeFrame,用于格式化 console template
│       ├─ create-compiler.js // 导出 createCompilerCreator,用于返回 createCompiler
│       ├─ error-detector.js  // 检查 AST 的错误
│       ├─ helpers.js         // 一些编译的帮助方法
│       ├─ index.js           // 导出 createCompiler 方法,用于返回 compile 和 compileToFunctions
│       ├─ optimizer.js       // 标记静态节点,用于重建优化
│       └─ tofunction.js      // 导出 createCompileToFunctionFn,用于返回 compileToFunctions
│   ├─ core                   // 核心代码,包括内置组件,全局API封装,Vue 实例化,观察者,虚拟DOM, 工具函数等等。
│       ├─ components         // 内置组件定义,目前包含 Keep-Alive
│       ├─ global-api         // 全局 api 定义,如 Vue.component,Vue.use,Vue.extend,Vue.mixin等
│       ├─ instance           // 实例化相关内容,生命周期定义、事件等
│       ├─ observer           // 数据监听,双向数据绑定,订阅中心设置等
│       ├─ util               // 工具方法
│       ├─ vdom               // 虚拟DOM相关
│       ├─ config.js          // 基础配置
│       └─ index.js           // 导出 Vue
│   ├─ platforms              // 跨平台相关
│       ├─ web                // web端
│           ├─ compiler       // 创建 createCompiler 所需参数 baseOptions,导出compile,compileToFunctions
│           ├─ runtime        // 在Vue上挂载了一些新的directives,components,config,__patch__,$mount(重写)等
│           ├─ server         // 服务端渲染
│           ├─ util           // 工具方法
│           └─ xxx.js         // 5个入口js,不同构建调用不用的方法
│       └─ weex               // weex相关
│   ├─ server                 // 服务端渲染(ssr)
│   ├─ sfc                    // 单文件组件解析(*.vue)
│   └─ shared                 // 全局共享的常量,方法
├─ test                       // 测试用例
├─ types                      // typescript 类型声明文件

针对目录的一些解释

dist

dist 下有 10 几种不同版本的 vue 文件,他们是根据 不同规范(包括 CommonJS 规范,ES Module,UMD)是否包含编译器不同环境 构建出的不同版本。

具体查阅 文档

flow

类似 TypeScriptFlowfacebook 出品的 JavaScript 静态类型检查工具。Vue.js 的源码利用了 Flow 做了静态类型检查。

packages

通过执行 npm script 对应的命令可编译出以下 npm 包。

vue-server-renderer

Vue.js 服务器端渲染(SSR) 所用。

vue-template-compiler

vue-template-compiler 通常与 vue-loader 连用,用于将单文件组件 (SFCs)预编译为渲染函数。

vue-template-compiler 作为 compilervue-loader/lib/index.jsparse 方法里传入,(其中会调用 compiler.parseComponent)得到 descriptor,根据 descriptor 可以生成不同模块的 import 请求,然后通过 pitcher 将原 import 请求转化为新的 import 请求,然后根据新的 import 请求执行 js,转化不同模块,其中转化 template 执行 templateLoader.js 时会去依赖 vue-template-compiler 提供 compiler.compile 方法解析。

vue-loader 解析

weex-template-compiler

weex 相关(略过)

weex-vue-framework

weex 相关(略过)

weex

Weex 是使用流行的 Web 开发体验来开发高性能原生应用的框架,集成的是运行时版本的 Vue

加载流程参考:Weex 加载流程浅说 Weex 工作原理

sfc

sfc 即单文件组件(Single File Components),执行 sfc 下的 parse.js 里导出的 parseComponent 方法得到 SFCDescriptor 对象。该文件最终会打包到 vue-template-compiler 里。

test

包含 karmajasmine 两种测试工具。

开始调试

vue 采用 rollup 作为构建工具。

修改 npm scripts 里的 dev 字段对应的命令,在命令后面增加 --sourcemap,然后执行:

bash
npm run dev

此时会在 dist 生成 vue.jsvue.js.map,然后在在任意处新增 html 文件,然后 script 标签引入刚生成的 vue.js,然后书写 vue 代码在浏览器打开即可断点到源代码而不是打包后的代码。

程序入口

dev 命令对应的 TARGETweb-full-dev,即对应的平台入口为 platforms/web/entry-runtime-with-compiler.js,该文件里面引入:

js
import Vue from './runtime/index';

该文件会从 core/index(他又会从 ./instance/index 引入Vue,该文件里会初始化 Vue 的各项,通过传入 Vue 作为参数的方式(Mixin)扩展原型) 里引入 Vue,并进行平台化相关的定制,如扩展了 Vue.options.directives、Vue.options.components。 初始化了 Vue.prototype.__patch__patch 方法,初始化了 Vue.prototype.$mount,该 $mount 会调用 core/instance/lifecycle 里的 mountComponent

回到 entry-runtime-with-compiler.js 分析,继续引入:

js
import { compileToFunctions } from './compiler/index';

该文件里执行:

js
const { compile, compileToFunctions } = createCompiler(baseOptions);

其中 createCompilercompiler/index 的导出,用于创建编译器。这里传入平台相关的参数 baseOptions 得到对应的 compile,compileToFunctionscompileToFunctions 用于将 template 转化为 render 函数。

回到 entry-runtime-with-compiler.js 继续分析,重写了 Vue.prototype.$mount,并在方法里末尾执行了原 $mount,然后将 compileToFunctions 赋给了 Vue.compile,最后导出 Vue

demo 分析使用编译后的 vue

因为 demo 里会涉及到组件引入和其他模块,所以采用 webpack 打包,将 vue 作为 npm 包引入,所以意味着断点将会打到编译后的 vuejs 里,后续分析将会在那个文件里进行。