持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
前言
Vue2 的源码相信很多人都读过,而Vue3的项目结构发生了很大的变化,各个功能模块被分别放入到 packages
目录下,职责更加清晰,通过目录名就可以一目了然。
我们这篇就从查找入口文件开始说起。
入口文件
从package.json
中的‘dev’脚本可以知道,打包是通过执行 node scripts/dev.js
来开始的,所以我们可以先定位到文件scripts/dev.js
中。
// scripts/dev.js
/** 省略 */
const target = args._[0] || 'vue'
build({
entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
})
/** 省略 */
我们省略其他代码只看上面这几句,可以知道 entryPoints
属性就是我们的入口文件,所以我们可以知道,整个Vue3的项目入口是 /packages/vue/src/index.ts
,打开这个文件。
// packages/vue/src/index.ts
import { registerRuntimeCompiler } from '@vue/runtime-dom'
function compileToFunction(
template: string | HTMLElement,
options?: CompilerOptions
): RenderFunction {
/** 暂时省略 */
}
registerRuntimeCompiler(compileToFunction)
export { compileToFunction as compile }
export * from '@vue/runtime-dom'
我们可以看到,这个文件定义了一个方法 compileToFunction
, 这个方法是用于编译用的,我们暂时先不看。还直接运行了方法registerRuntimeCompiler
并把 compileToFunction
当作参数传入。
我们顺着registerRuntimeCompiler
这个方法,去看看他的实现。
// packages/runtime-core/src/component.ts
type CompileFunction = (
template: string | object,
options?: CompilerOptions
) => InternalRenderFunction
let compile: CompileFunction | undefined
export function registerRuntimeCompiler(_compile) {
compile = _compile;
}
这个方法做的事情很简单,只是把参数 compileToFunction
赋值给全局变量 compile。上面这些过程我们可以简单画个流程图。
createApp
我们回顾一下我们在开发时是怎么调用Vue3的呢,我们可以在 /packages/vue/examples
中创建一个demo。
<!-- packages/vue/examples/test.html -->
<script src="../../dist/vue.global.js"></script>
<div id="demo">
<h1 @click="changeName">age:{{a}}</h1>
</div>
<script>
const { createApp, ref, watchEffect, reactive, effect, computed } = Vue
createApp({
setup() {
const dad = ref(1)
const a = computed(() => {
return dad
})
const changeName = () => {
dad.value++
}
return {
dad,
a,
changeName
}
}
}).mount('#demo')
</script>
由上面的例子我们可以知道,我们是通过执行 Vue 中的 createApp
方法,然后再调用 mount
方法来挂载到dom节点上。
createApp
的位置在 /packages/runtime-dom/src/index.ts
// packages/runtime-dom/src/index.ts
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {}
})
我们可以看到,在createApp
中,首先运行了ensureRenderer
方法,然后再调用ensureRenderer
里面的createApp
方法,来生成 app,
先拿出app里面的挂载方法mount
,然后再重新为 app 赋值了一个新的 mount
方法。
ensureRenderer
里面只做了一件事,就是调用了createRenderer
来创建渲染器。整体到这里还是比较简单明了的。
小结
这节我们分析了入口文件,找到了registerRuntimeCompiler
和 createApp
,createApp
里面进行了一系列的操作,生成了 app 属性,
并对 mount 方法进行了重构,因此,我们在使用 Vue3 时先执行 createApp()
后才能继续调用 mount
方法。下一篇我们会讲 createApp
的整个逻辑和createRenderer
的作用。