Vue3 源码解析系列之初始化流程(二)

449 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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。上面这些过程我们可以简单画个流程图。

image.png

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 来创建渲染器。整体到这里还是比较简单明了的。

小结

这节我们分析了入口文件,找到了registerRuntimeCompilercreateAppcreateApp里面进行了一系列的操作,生成了 app 属性, 并对 mount 方法进行了重构,因此,我们在使用 Vue3 时先执行 createApp() 后才能继续调用 mount 方法。下一篇我们会讲 createApp 的整个逻辑和createRenderer的作用。