In my previous article “Debug, Learn to Read Open-source Project Code efficiently”, I used the Vue3 source code debugging configuration as an example to introduce the basic configuration of debugging and highlight its importance in the process of looking at source code. This article uses debugging techniques to get a quick overview of the Vue3 execution process without having to go into detail about the full code implementation.

The simplest code

As we all know, calling vue.createApp method creates a Vue3 application, this article starts from this method, step-by-step analysis of the method in the source code implementation, so that you can understand the overall Vue3 implementation process.

First of all, in the source directory to create packages/vue/examples/hello. HTML file:

<script src=".. /dist/vue.global.js"></script>

<div id="demo">{{text}}</div>

<script>
  debugger
  Vue.createApp({
    data: () = > ({
      text: 'hello world'
    })
  }).mount('#demo')
</script>
Copy the code

The code’s function is simple: Print the Hello World string in the page. Here the debugger code is inserted before executing vue.createApp, causing execution to pause at the debugger.

Debugging start

For a more detailed understanding of debugging principles and procedures, please visit the author’s previous article: “Debug, you learn to read open-source project code efficiently”.

{
  "version": "0.2.0"."configurations": [{"type": "chrome"."request": "launch"."name": "Launch hello"."url": "http://localhost:8080"."webRoot": "${workspaceFolder}"."file": "${workspaceFolder}/packages/vue/examples/hello.html"}}]Copy the code

Click the debug button. The program pauses in the debugger and then steps into the createApp method in Vue. CreateApp.

createApp

Let’s directly look at the source code of createApp method, here is a part of the code cut, mainly for the dev environment of some methods to achieve, does not affect the main process, the same below.

// packages/runtime-dom/src/index.ts
export const createApp = ((. args) = > {
  constapp = ensureRenderer().createApp(... args)const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
    const container = normalizeContainer(containerOrSelector)
    if(! container)return

    const component = app._component
    if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML }// clear content before mounting
    container.innerHTML = ' '
    const proxy = mount(container, false, container instanceof SVGElement)
    if (container instanceof Element) {
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app'.' ')}return proxy
  }

  return app
}) as CreateAppFunction<Element>
Copy the code

Const app = ensureRenderer().createApp(… Args), we will step through the internal implementation of this line of code. In the debug panel on the left, you can see the value of the APP as follows:

So we can reasonably guess that — ensureRenderer().createApp(… Args), which uses the arguments passed in to initialize the properties and methods, and is mounted in the app variable to return.

After returning the app variable, take out the original mount method, then mount a new method implementation to the app’s mount property, which is the actual implementation of the.mount(‘#demo’) code block in the HTML file, and finally return the app variable.

ensureRenderer().createApp(... args)

Step to const App = ensureRenderer().createApp(… This line of code looks like this:

There are two methods involved: ensureRenderer and createApp, and we’ll look at them one by one.

ensureRenderer

// packages/runtime-dom/src/index.ts
function ensureRenderer() {
  return (
    renderer ||
    (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  )
}
Copy the code

The core of this method is createRenderer. Instead of expanding the source code for this method, let’s look at the definition of the value returned by this method:

// packages/runtime-core/src/renderer.ts
export interface Renderer<HostElement = RendererElement> {
  render: RootRenderFunction<HostElement>
  createApp: CreateAppFunction<HostElement>
}
Copy the code

CreateRenderer returns an object with two properties: render and createApp. Render is the render function referred to in mount below. The createApp method is the return value of createAppAPI. The source code is as follows:

export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    if(rootProps ! =null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }
    const context = createAppContext()
    const installedPlugins = new Set(a)let isMounted = false
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null._context: context,
      _instance: null,

      version,

      get config() {},set config(v) {},use(plugin: Plugin, ... options:any[]){},mixin(mixin: ComponentOptions) {
      },

      component(name: string, component? : Component):any{},directive(name: string, directive? : Directive){ }, mount( rootContainer: HostElement, isHydrate? :boolean, isSVG? :boolean) :any{},unmount(){},provide(key, value){}})return app
  }
}
Copy the code

The source code confirms our guess: createApp mounts several properties and methods in the app variable and returns app.

mount

Const proxy = mount(container, false, Container instanceof SVGElement) const proxy = mount(Container, false, Container instanceof SVGElement);

// packages/runtime-core/src/apiCreateApp.tsmount( rootContainer: HostElement, isHydrate? :boolean, isSVG? :boolean) :any {
  // Use a variable to control that mount is executed only once
  if(! isMounted) {// Create a virtual node
    const vnode = createVNode(
      rootComponent as ConcreteComponent,
      rootProps
    )
    // store app context on the root VNode.
    // this will be set on the root instance on initial mount.
    vnode.appContext = context

    // HMR root reload
    if (__DEV__) {
      context.reload = () = > {
        render(cloneVNode(vnode), rootContainer, isSVG)
      }
    }

    if (isHydrate && hydrate) {
      hydrate(vnode as VNode<Node, Element>, rootContainer as any)}else {
      render(vnode, rootContainer, isSVG)
    }
    isMounted = true
    app._container = rootContainer
    // for devtools and telemetry; (rootContaineras any).__vue_app__ = app

    returnvnode.component! .proxy } }Copy the code

As you can see, when we step past render(vnode, rootContainer, isSvg), the Hello World string is displayed in the browser, that is, the mount method mounts the Vue component to the browser. And render is the key rendering method.

One of the highlights is that mount calls the createVNode method to create the virtual node, and render receives the virtual node parameter for rendering.

conclusion

This article for the author serial Vue3 source reading the first, does not involve the interpretation of various principles, the main purpose is to do not need to read the premise of detailed source code, quickly understand the implementation process of Vue3 components.

I hope that readers can read Vue3 source code efficiently through debugging after reading this article. If you think this article can help you, click a like and then go