今天开始,我将和大家一起探索vue源码,大家一起学习!
首先去git下载一份vue,我这里是v2.6.9版本的,如果要跟着一起分析的话,推荐使用同一版本!
刚下载的vue的目录结构是这样的
很多人想看源码,但是不知道从哪一块下手,所以这里我说一下我是怎么找入口的。
首先dist文件夹中有vue.js,这是已经被打包好的js文件,src中的所有js代码都合并到了这里,我们直接去看这个vue.js肯定是会懵逼的,1w多行跳来跳去的,所以我们是不是要找到打包的入口?
那么让我们进入到scripts文件夹,因为这是打包的相关配置文件夹
我们这里先看build.js
看10行左右的这里 可以看到引入了config.js中所有的配置
// 把同级目录下config的所有创建配置导入
let builds = require('./config').getAllBuilds()
27行左右开始递归打包
// 开始打包
build(builds)
既然引入了配置,那我们就要进入到config.js看配置了
看38行左右
// dist目录中各种打包方式的定义
const builds = {} //里面是各种打包方式配置
我们这里可以直接ctrl+f搜索vue.js,找到dest中为vue.js的那个打包配置
这样就来到了120行左右
// 浏览器要跑的版本
// Runtime+compiler development build (Browser)
'web-full-dev': {
// entry-runtime-with-compiler.js入口函数
entry: resolve('web/entry-runtime-with-compiler.js'),
// 最后输出为vue.js
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
可以看到输出为dist下面的vue.js,那么入口函数当然就是web/entry-runtime-with-compiler.js
显然,这个web是一个别名,我们可以通过alias.js去查找这个别名对应的目录
进入alias.js
可以在第10行看到
web: resolve('src/platforms/web'),
web别名对应着src/platforms/web
那么入口函数的路径已经出来了src/platforms/web/entry-runtime-with-compiler.js
接下来我们根据路径打开这个js文件
路径:vue-2.6.9\src\platforms\web\entry-runtime-with-compiler.js
这次分享就分析这一个js文件
将其几个代码块折叠一下,可以看到这文件其实也就3个函数:
这里是不是就遇到了我们平时见到的$mount,那我们就从这个$mount开始分析
// 扩展$mount 保存老的$mount 老的$mount也会执行以前的操作
const mount = Vue.prototype.$mount
// 进行的新的mount操作
Vue.prototype.$mount = function (
// 传入el,也就是挂载的元素节点
el?: string | Element,
// todo: 等待分析 涉及服务端渲染
hydrating?: boolean
): Component {
// 获取el节点
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 or - mount to normal elements instead.`
)
return this
}
// 获取配置的一些选项,也就是render template el那些
// 可从代码中得到一些选项的优先级:render>template>el
const options = this.$options
// resolve template/el and convert to render function
// 如果不存在render函数,就将template/el的设置转换为render函数
// render优先级非常高了,这些操作都是在没有render的情况下进行的
if (!options.render) {
// 获取template
let template = options.template
// 如果有template
if (template) {
// string "#app"这类的
if (typeof template === 'string') {
// #开头
if (template.charAt(0) === '#') {
// 将进入idToTemplate idToTemplate是接收#app这类,返回对应节点的innerHTML
template = idToTemplate(template)
/* 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) {
// 如果是DOM元素 document.querySelector()
// 获取到这段内容,也就获取到了节点
template = template.innerHTML
} else {
// 忽略
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
// 如果template不存在,获取el
} else if (el) {
//
// 调用getOuterHTML,获取包括标签的内容
template = getOuterHTML(el)
}
// 这里对拿到的template进行编译
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
// 如果是模板字符串,需要编译器去编译 也就是进入compileToFunctions这个函数
// 可以通过这个函数查看编译器的工作机制,也就是把template转换为render:todo
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// 赋值给当前选项的render
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')
}
}
}
// 执行老mount的操作 正常的挂载渲染过程
return mount.call(this, el, hydrating)
}
很多解释已经在代码里敲好了,这里总结一下:
路径:当前目录
// 根据id查询到el 并返回innerHTML
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
路径:当前目录
// 获取包括标签的内容
function getOuterHTML (el: Element): string {
// 如果存在,直接使用outerHTML
if (el.outerHTML) {
return el.outerHTML
} else {
// 不存在就创建div
const container = document.createElement('div')
// 将el深复制一份加入div
container.appendChild(el.cloneNode(true))
// 返回这个div
return container.innerHTML
}
}
路径:vue-2.6.9\src\platforms\web\util\index.js
/* @flow */
import { warn } from 'core/util/index'
export * from './attrs'
export * from './class'
export * from './element'
/**
* Query an element selector if it's not an element already.
*/
// 获取形式为“#app”的el的节点元素
export function query (el: string | Element): Element {
// 如果为字符串 “#app”
if (typeof el === 'string') {
// 通过#app 获取到元素节点
const selected = document.querySelector(el)
// 如果不存在节点
if (!selected) {
process.env.NODE_ENV !== 'production' && warn(
'Cannot find element: ' + el
)
// 返回div
return document.createElement('div')
}
// 返回获取到的节点
return selected
} else {
// 如果不是字符串 "#app"形式,直接返回el
return el
}
}
总结一下:这个入口函数,最主要的还是实现了$mount这一个函数。
下一次分享,我们将进入到vue初始化的那里去探索,当然也是通过这个入口文件去找到。怎么去找到vue初始化那里,先留个悬念在这。