Vue源码解析(一)
所有的源码分析都应该是package.json开始
package.json解析
"scripts": {
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
"dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
"dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
"dev:test": "karma start test/unit/karma.dev.config.js",
"dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
"dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ",
"dev:weex": "rollup -w -c scripts/config.js --environment TARGET:weex-framework",
"dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory",
"dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ",
...
}
我们可以看到频繁的出现一个关键词 scripts/config.js
scripts/config.js
根据传入的不同的 --environment Target: 获取不同的配置,简化的配置如下所示
const builds = {
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
'web-runtime-cjs-dev': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.dev.js'),
format: 'cjs',
env: 'development',
banner
},
...
// Runtime only ES modules build (for bundlers)
'web-runtime-esm': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.esm.js'),
format: 'es',
banner
},
// Runtime+compiler ES modules build (for bundlers)
'web-full-esm': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.esm.js'),
format: 'es',
alias: { he: './entity-decoder' },
banner
},
...
// 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
},
...
// Web server renderer (CommonJS).
'web-server-renderer-dev': {
entry: resolve('web/entry-server-renderer.js'),
dest: resolve('packages/vue-server-renderer/build.dev.js'),
format: 'cjs',
env: 'development',
external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
},
...
// Weex runtime framework (CommonJS).
'weex-framework': {
weex: true,
entry: resolve('weex/entry-framework.js'),
dest: resolve('packages/weex-vue-framework/index.js'),
format: 'cjs'
},
}
我们可以看到几个特征
- Vue支持CJS(CommonJS), ESM(ECMAScript Modedule), UMD 如果这三个特性不清楚了,可以单独去了解下
- Vue提供Weex的版本
- Vue提供
runtime,compile,full三种类型的包,full = runtime + compile,compile顾名思义支持提前编译,也就是把我们平时写的<template></template>的vue文件进行拆分编译。
web/entry-runtime-with-compiler.js
对应的文件在src/platform/web下面
$mount函数
getOuterHTML的作用
Vue挂载的时候可以传入一个Element对象,如果没有template信息,这时候会把Element对象的innerHTML当做template。
[TODO:实际场景很少用到,不知道是不是内部用的比较多]
const { render, staticRenderFns } = compileToFunctions
把template中的代码,编译成render函数,设计到编译原来,词法分析等,暂时不深入理解
[TODO: staticRenderFns 的作用]
整个web/entry-runtime-with-compiler.js文件的Vue对象来自于web/runtime/index.js我们继续深入了解
web/runtime/index.js
我们发现此文件中也有$mount方法,什么意思呢?
回头看上一个文件,发现
//先保留核心的mount
const mount = Vue.prototype.$mount;
//在调用核心mount
return mount.call(this, el, hydrating);
这其实是一个装饰器模式,目的让entry-runtime-with-compiler.js的$mount支持compile模式。
而在此文件中
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
核心逻辑是mountComponent
mountComponent
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
//没有则返回一个空的VNode,VNode会在后面具体讲
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
//调用生命周期
callHook(vm, 'beforeMount')
//跟新Dom节点,这个内部逻辑比较多,可以先放一放
let updateComponent
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
//...调试信息忽略
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// Watcher 后续作为一个重点去看,大家可以先把这个理解为对Data的双向监听
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// 调用挂载函数,结束挂载
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
[TODO:transition 原理]
extend, noop, toObject函数
我们学一些里面用到的小工具。
//添加对象属性
export function extend (to: Object, _from: ?Object): Object {
for (const key in _from) {
to[key] = _from[key]
}
return to
}
//把数据属性合并到一个对象上
export function toObject (arr: Array<any>): Object {
const res = {}
for (let i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i])
}
}
return res
}
//空函数写法
export function noop (a?: any, b?: any, c?: any) {}
总结
到此位置,我们已经完成不同环境,不同平台的源码解剖,去除了Runtime,Compile,Weex等额外的干扰因素,下面我们将进入 Core 内部的学习。