共计 7349 个字符,预计需要花费 19 分钟才能阅读完成。
参考:
Vue 技术解密
面试官:Vue 实例挂载的过程
vue 中 Runtime-Compiler 和 Runtime-only 的区别
对不同构建版本的解释
如有谬误,欢送指出~
更多学习笔记请戳:https://github.com/6fa/WebKno…
本文目录:
1. 源码外围目录
2. 源码构建
3.Runtime-Only 和 Runtime-Compiler
4. 入口(runtime+compiler 模式)
1. 源码外围目录
Vue2 源码可查看官网 github:https://github.com/vuejs/vue
源码次要放在 src 目录:
src
├── compiler # 编译相干
├── core # 外围代码
├── platforms # 不同平台的反对
├── server # 服务端渲染
├── sfc # .vue 文件解析
├── shared # 共享工具代码
-
compiler
- 寄存编译相干代码,包含
- 将模板解析为 AST 树(形象语法树)、AST 树优化、代码生成
-
core
- 外围代码,包含
- 内置组件、全局 API 封装、工具函数
- Vue 实例化、响应式、虚构 DOM
-
platforms
- vue.js 的入口
- 2 个目录代表 2 个次要入口,别离打包成运行在 web 上和 weex 上的 Vue.js
-
server
- 服务端渲染相干
-
sfc
- 把.vue 文件解析成一个 js 对象
-
shared
- 一些工具,会被浏览器端 Vue.js 和服务端 Vue.js 共享
2. 源码构建
在源码的 package.json 文件中能够看到,构建代码的入口是 scripts/build.js:
{
//...
"scripts": {
//...
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
//...
}
}
scripts/build.js 的重点代码:
- 先从配置文件(config)读取配置,再通过命令行参数对构建配置(builds)做过滤,以构建出不同用处的 Vue.js
- build() 外面通过 rollup(打包工具)来构建
//scripts/build.js
let builds = require('./config').getAllBuilds()
// filter builds via command line arg
// 通过比对命令行参数对 builds 过滤
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 {
// filter out weex builds by default
// 默认状况下过滤掉 weex 构建
builds = builds.filter(b => {return b.output.file.indexOf('weex') === -1
})
}
build(builds)
再来看一下 config 配置文件:
// script/config.js
// 配置是遵循 Rollup 的构建规定的
const builds = {// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
'web-runtime-cjs': {entry: resolve('web/entry-runtime.js'), // 构建入口
dest: resolve('dist/vue.runtime.common.js'), // 构建后的文件地位
format: 'cjs', // 构建格局,cjs 示意遵循 CommonJS,es 则 ES Module,umd 则 UMD 标准
banner
},
// Runtime+compiler CommonJS build (CommonJS)
'web-full-cjs': {entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.js'),
format: 'cjs',
alias: {he: './entity-decoder'},
banner
},
//...
}
3.Runtime-Only 和 Runtime-Compiler
编译器(compiler):
用来将模板字符串编译成为 JavaScript 渲染函数的代码
运行时(runtime):
用来创立 Vue 实例、渲染并解决虚构 DOM 等的代码。基本上就是除去编译器的其它所有
runtime + compiler(完整版):
如果写了 template,那么须要在运行时将 template 编译成 render 函数(生成虚构 DOM)。因为在 Vue2 中,最终渲染都是通过 render 函数,如果写 template 属性,则须要编译成 render 函数, 则须要 compiler。
// 须要编译器
new Vue({template: '<div>{{ hi}}</div>'
})
// 不须要编译器
new Vue({render (h) {return h('div', this.hi)
}
})
runtime only(只有运行时):
须要借助 webpack 的 vue-loader 将.vue 文件编译成 js 文件:vue 文件外部的模板会在构建时预编译成 JavaScript。(此时 main.js 里的 new Vue 选项仍然不能够写 template 属性,只有.vue 文件里能够写)
因为不须要编译器 compiler 了,所以体积缩小了约 30%,应该尽可能应用 runtime only 版本。
4. 入口(runtime+compiler 模式)
runtime+compiler 模式入口文件地位:src/platforms/web/entry-runtime-with-compiler.js
代码的重点是:
- 引入了 Vue(vue 的入口为./runtime/idex)
- 在 Vue 的原型上定义了 $mount 办法
- 导出 Vue
import config from 'core/config'
import {warn, cached} from 'core/util/index'
import {mark, measure} from 'core/util/perf'
import Vue from './runtime/index'
import {query} from './util/index'
import {compileToFunctions} from './compiler/index'
import {shouldDecodeNewlines, shouldDecodeNewlinesForHref} from './util/compat'
//...
// 定义 $mount 办法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (){......}
//...
Vue.compile = compileToFunctions
export default Vue
4.1 Vue 入口
下面引入 Vue 的地位为:src/platforms/web/runtime/index.js
代码重点:
- 从 core/index 引入 Vue
- 对 Vue 对象进行一些拓展,比方定义了 Vue 原型上的根底 $mount 办法
-
从 core/index.js 又能够看到
- 从 core/instance/index 导入了 Vue 对象,因而真正的 Vue 实例定义在 core/instance/index
- 初始化全局 API:initGlobalAPI(Vue)
// src/platforms/web/runtime/index.js
import Vue from 'core/index'
import config from 'core/config'
import {extend, noop} from 'shared/util'
import {mountComponent} from 'core/instance/lifecycle'
import {devtools, inBrowser} from 'core/util/index'
import {
query,
//......
} from 'web/util/index'
//......
// install platform specific utils
Vue.config.mustUseProp = mustUseProp
//......
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
//......
export default Vue
// core/index
import Vue from './instance/index'
import {initGlobalAPI} from './global-api/index'
import {isServerRendering} from 'core/util/env'
import {FunctionalRenderContext} from 'core/vdom/create-functional-component'
initGlobalAPI(Vue)
//......
export default Vue
4.1.1 Vue 的定义
地位 core/instance/index,重点:
- 定义了 Vue 构造函数(为什么不必 class,是因为前面将 Vue 当参数传递,次要是给 vue 的原型拓展办法,这些拓展办法扩散在各个模块,如果是 class 则难以实现)
- xxMixin 都是在 Vue 的 prototype 上拓展一些办法,比方_init 办法就是在 initMixin 里增加的
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
4.1.2 initGlobalAPI
下面的是给 Vue.prototype 拓展办法,则 initGlobalAPI 则是给 Vue 自身增加静态方法。
地位:src/core/global-api/index.js
//......
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {configDef.set = () => {
warn('Do not replace the Vue.config object, set individual fields instead.')
}
}
Object.defineProperty(Vue, 'config', configDef)
// Vue.util 裸露的办法最好不要依赖,因为它可能常常会发生变化,是不稳固的
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
4.2 挂载
$mount 办法重点:
- 如果定义了 template 或 el,则将 template 或 el 的 innerHTML 转换为 render 函数
- 转换是通过 compileToFunctions 实现的
- 最初调用 Vue 原型上的共享 $mount 办法挂载(这么做是为了复用,runtime 模式间接用,省却了下面转换成 render 函数的步骤。地位在 src/platform/web/runtime/index.js)
- $mount 办法理论会去调用 mountComponent 办法(定义在 src/core/instance/lifecycle.js)
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {el = el && query(el)
//...
const options = this.$options
// 将 template 或者 el 的 outerHTML 转换为 render 函数
if (!options.render) {
let template = options.template
if (template) {if (typeof template === 'string') { //template 为字符串
if (template.charAt(0) === '#') {template = idToTemplate(template) //idToTemplate 是通过 id 来获得元素的 innerHTML
//...
}
} else if (template.nodeType) { //template 为节点
template = template.innerHTML
} else { // 有效 template
if (process.env.NODE_ENV !== 'production') {warn('invalid template option:' + template, this)
}
return this
}
} else if (el) { // 如果不存在 template 但有 el
template = getOuterHTML(el)
}
if (template) {
//...
// compileToFunctions 将 template 转为 render 函数
const {render, staticRenderFns} = compileToFunctions(template, {//... 传递的选项}, this)
options.render = render
options.staticRenderFns = staticRenderFns
//...
}
}
return mount.call(this, el, hydrating)
}
// src/platform/web/runtime/index.js
// 可复用的共享 $mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
4.3 总结
runtime+compiler 模式入口(src/platforms/web/entry-runtime-with-compiler.js)
-
从 runtime/index 引入 vue(src/platforms/web/runtime/index.js)
-
从 core/index 引入 Vue
-
从 core/instance/index 导入了 Vue 对象,因而真正的 Vue 实例定义在 core/instance/index
- 定义了 Vue 构造函数
- 拓展 Vue 的 prototype 上一些办法
- 初始化全局 API:initGlobalAPI(Vue)
-
- 对 Vue 对象进行一些拓展,比方定义了 Vue prototype 上的根底 $mount 办法
-
-
在 vue 原型上定义 $mount(留神和共享的 $mount 办法不同)
- 将 template 转换为 render 函数,转换是通过 compileToFunctions 实现的
- 调用根底 $mount 办法(src/platform/web/runtime/index.js)
- 根底 $mount 办法理论会调用 mountComponent 办法(src/core/instance/lifecycle.js)