我决定从scripts/config.js中的web-full-dev入手开始读
'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
}
其实我以前尝试过很多次,最后我发现,不应该直接读源码,而是应该读源码编译好的vue.js文件。为什么这样呢?因为模块化给出了维护的便利的同时,因为文件太多,有点分散。
先读吧,读不下去就换别的方式,条条大路通罗马嘛!!
找到入口文件web/entry-runtime-with-compiler.js,查看scripts/alias.js中知道web其实是src/platforms/web,最终入口就是src/platforms/web/entry-runtime-with-compiler.js
/* @flow */
import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'
import Vue from './runtime/index' // 原来万物始祖的Vue来自runtime/index
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'
// 给定一个id,在页面找到对应的dom节点,取其中的内容(innerHTML)作为模板字符串
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
// 为什么非得重写$mount?
// 当然是有原因的呀,对比entry-runtime.js就能知道
// 原始的$mount只负责挂载,根本不负责编译字符串模板为render函数
// 因为这个是entry-runtime-with-compiler
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el) // 找到挂载点
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) { // 没有明确指定render函数,就根据字符串模板生成render函数
let template = options.template
if (template) { // 设置中指定了template
if (typeof template === 'string') {
if (template.charAt(0) === '#') { // 认为是dom的id
template = idToTemplate(template) // 根据id去查找dom元素,取出里面的内容
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) { // 去el中的内容作为字符串模板
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
// 从字符串模板编译成render函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
* 如果拿不到el的outerHTML就把el包裹在一个div内,取div的innerHTML
* 怎么那么聪明呢
*/
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
Vue.compile = compileToFunctions
export default Vue
感觉自己走偏了,从compileToFunctions开始,用了一个多小时,就再也没出来过,放弃吧,先创建一个空文章占位
看不下去编译器相关的,就从Vue开始吧,runtime/index开始咯
以上2020-03-23下午
突然觉得自己不应该从entry-runtime-with-compiler开始,我看不懂compiler,我应该直接从entry-runtime.js开始,只看运行时的就够了。能这块看懂了再看entry-runtime-with-compiler.js。做事情就要循序渐进,突然感觉自己长大了:)
import Vue from './runtime/index'
export default Vue
这个文件都是简单,言简意赅,直接把Vue放出了。跟entry-runtime-with-compiler.js对比一下就知道多的那些就是跟compiler相关的呗。
entry-runtime.js -> runtime/index
src/platform/web/runtime/index.js
/* @flow */
/**
* 看到platform/web/runtine/index.js,让我想到了Vue的大致封装思路
* core是平台无关的,封装虚拟DOM相关的内容。真正平台相关的就放到了platform中
* 想在哪个平台运行,就开发哪个平台的移植就行了,只要能满足预先定义好的接口就行
*
* 这个思路让我想到了webkit的架构,它本身也是不涉及到具体的绘图逻辑,而是使用移植
* 提供给它的接口,真是妙!!!
*
* 思路的开阔让我对Vue源码更感兴趣了:)
*/
import Vue from 'core/index' // 这就是平台无关的Vue
import config from 'core/config'
// 导入extend和noop函数,前者是简单的把对象b的属性拷贝到a上;后者是一个空函数
import { extend, noop } from 'shared/util'
//
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'
// 导入几个函数
import {
query, // 查询dom,接收string或Element,底层使用document.querySelector
mustUseProp,
isReservedTag, // 判断是否是保留的标签
isReservedAttr, // 判断是否是保留的属性
getTagNamespace, // 获取标签的命名空间,只有svg和math两个
isUnknownElement // 判断标签是否是未知元素
} from 'web/util/index'
import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'
// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// install platform runtime directives & components
// 把平台相关的指令和组件合并到Vue.options中
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
setTimeout(() => {
if (config.devtools) {
if (devtools) {
devtools.emit('init', Vue)
} else if (
process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test'
) {
console[console.info ? 'info' : 'log'](
'Download the Vue Devtools extension for a better development experience:\n' +
'https://github.com/vuejs/vue-devtools'
)
}
}
if (process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test' &&
config.productionTip !== false &&
typeof console !== 'undefined'
) {
console[console.info ? 'info' : 'log'](
`You are running Vue in development mode.\n` +
`Make sure to turn on production mode when deploying for production.\n` +
`See more tips at https://vuejs.org/guide/deployment.html`
)
}
}, 0)
}
export default Vue
把platforms/web中的结构大致过了一遍,跟platforms/weex做了对比,对vue的设计还是深感佩服的。也有的深入的了解。vue对于跨平台的支持还是下了功夫的。
现在来看看src目录下的结构吧
- compiler 编译器相关的内容,看着头疼
- core vue的核心功能,平台无关的部分
- platforms 各个平台的移植
- server 服务器端的内容,应该是服务器端渲染的
- sfc 单文件组件的内容single file component
- shared 全局的帮助方法,平台无关的,都是js的相关内容
接下来主攻core目录,后面再看compiler和server吧。
觉得好的给我点赞哦:)