主题
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 指代的就是我们常用的 vueRuntime only
/Runtime + compiler
是否带编译模块:带有 compiler 的会将模板转化成 render 函数CommonJS
/es
/umd
打包出不同模块规范:umd
模块是整合了CommonJS
和AMD
两个模块定义规范的方法,当不支持时两种模块时会将其添加到全局变量中
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 的构造函数
instance/index.js
:真正的Vue
的构造函数,并在Vue
的原型上扩展方法core/index.js
:增加全局API
方法runtime/index.js
:扩展$mount
方法及平台对应的代码