已被阅读 793 次 | 文章分类:日常随笔 | 2022-04-08 00:57
阅读源码基本步骤是找到入口文件,然后一步步往前追踪代码,看每个模块的导入导出文件;所以本文也按照这个逻辑记录;在记录过程中会尽量注释源码。 注:本节就先阅读如何找到Vue对象的导出及挂载函数的定义
1 入口脚本
目的:能找到源码中在哪里导出vue类;及如何定义$mount挂载函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
<script src="./vue.min.js"></script>
</script>
<script>
const app = new Vue({
render: (h) => {
return h('h', 'hello render')
}
}).$mount('#app')
</script>
</body>
</html>
找到第一条脚本:
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
// scripts/config.js:是入口文件;
// TARGET:web-full-dev"是目标 命令;
我们找到scripts文件夹下的config.js;查看他最终导出的对象是什么
如果target存在,导出对应的模块配置参数;如果不存在全部导出;
// 如果命令中 target参数有值
if (process.env.TARGET) {
// 根据目标名称导出模块
module.exports = genConfig(process.env.TARGET)
} else {
exports.getBuild = genConfig
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}
genConfig函数:从builds根据name获取,最后返回config
function genConfig (name) {
const opts = builds[name]
const config = {
input: opts.entry,
external: opts.external,
plugins: [
flow(),
alias(Object.assign({}, aliases, opts.alias))
].concat(opts.plugins || []),
output: {
file: opts.dest,
format: opts.format,
banner: opts.banner,
name: opts.moduleName || 'Vue'
},
onwarn: (msg, warn) => {
if (!/Circular/.test(msg)) {
warn(msg)
}
}
}
....省略
return config
}
builds对象:根据name获取打包的参数配置;dev传入的target值是"web-full-dev";
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+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
}
....省略
}
从上面可以看出返回的是roolup打包的必要参数;如入口文件、打包目标目录、format等等; 所以执行 yarn dev命令会在dist目录下生成一个vue.js的文件;
2 入口文件
上面了解了入口脚本怎么获取打包命令的参数;其中有个入口文件并没看如何获取;
上面的入口文件是通过下面的方式获取
resolve('web/entry-runtime-with-compiler.js')
第一步 看一下resolve函数怎么定义
// 获取别名模块
const aliases = require('./alias')
const resolve = p => {
const base = p.split('/')[0]
if (aliases[base]) {
// 返回别名模块路径
return path.resolve(aliases[base], p.slice(base.length + 1))
} else {
return path.resolve(__dirname, '../', p)
}
}
看到resovle函数就是根据传入的别名路径,找到别名对应的具体模块;别名和模块的对应关系就存在alias这个类中;
第二步 看一下alias类;仍然在scripts文件夹下
const path = require('path')
const resolve = p => path.resolve(__dirname, '../', p)
module.exports = {
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler: resolve('src/compiler'),
core: resolve('src/core'),
shared: resolve('src/shared'),
web: resolve('src/platforms/web'),
weex: resolve('src/platforms/weex'),
server: resolve('src/server'),
sfc: resolve('src/sfc')
}
可看到记录了详细的别名和模块的对应关系,那么很明显web/entry-runtime-with-compiler.js别名对应的就是src/platforms/web目录下的entry-runtime-with-compiler.js模块;我们继续跟踪
第三步 打开entry-runtime-with-compiler.js文件,看到export default Vue;终于看见了熟悉的Vue对象;
也就是说我们的Vue对象在该模块中定义并导出;所以该模块必须是第一个被引入的;后面的模块需要在该模块后面添加其他的vue内容;
3 定义挂载函数
查看entry-runtime-with-compiler.js,主要的一个作用定义了mount挂载函数,挂载函数内部主要获取了el元素,并定义了render渲染函数;代码如下
// 定义挂载函数
Vue.prototype.$mount = function (){
...省略
return mount.call(this, el, hydrating)
}
// 获取el模板并转成渲染函数
// 1 优先看有没有render函数,如果有直接用
if (!options.render) {
let template = options.template
// 2 如果没有render函数就看有没有template模板
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
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) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// 3 如果都没有就直接获取el的outerHTML作为渲染模板
template = getOuterHTML(el)
}
// 如果template存在;用 template 生成 render 函数
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
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')
}
}
// 综上直接传render编译效率会高一些
}
到这里el的获取,vue挂载函数的定义就结束了;
注:在定义render函数时,可以看到如果传入render函数,那么后面通过模板或者直接获取el内容的代码就不用执行;所以由此可见在创建vue对象时,传入render函数比template编译效率要高。
QQ:3410192267 | 技术支持 微信:popstarqqsmall
Copyright ©2017 xiaobaigis.com . 版权所有 鲁ICP备17027716号