The purpose of this article is to take a closer look at the Vue3 application creation process, starting with createApp.

Create an

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

  <div id="root">
    <h1>{{text}}</h1>
  </div>

  <script>
    const app = Vue.createApp({
      data() {
        return {
          text: 'Hello Vue'
        }
      }
    })

    app.mount('#root')
  </script>
</body>
Copy the code

With this code we can create the simplest VUE application that displays the Hello Vue H1 tag in a browser window. The above process does two things:

  1. throughcreateAppFunction creates an app object
  2. throughapp.mountMethod to mount the content toidrootThe label

Generation of app objects

App is the Vue application ontology, which is generated by Vue’s global API createApp. Let’s take a look at the internal implementation of createApp until the app is created.

The createApp function is located in Vue -next/ Packages/Runtime-dom/SRC /index.ts.

export type CreateAppFunction<HostElement> = (rootComponent: Component, rootProps? : Data |null
) = > App<HostElement>
Copy the code

The createApp function accepts the rootComponent as an argument and the props of the rootComponent as an optional argument. Concrete implementation is as follows:

export const createApp = ((. args) = > {
  constapp = ensureRenderer().createApp(... args)const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
    / /... Logical omission here
  }

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

CreateApp internal process is as follows:

  1. usingensureRenderedcreaterenderedRenderer, and userenderedOn thecreateAppMethod to createappObject.
  2. rewriteappOn the objectmountmethods
  3. returnappObject (that is, application instance)

Create the renderer

The createApp function has a rendered renderer to build the application instance, so we need to build the renderer using the createRenderer function. That is:

// vue-next/packages/runtime-dom/src/index.ts
let renderer: Renderer<Element> | HydrationRenderer

function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
Copy the code

One that may have the same doubts as I have about implementations of ensureRenderer, Why ensureRenderer function not directly return createRenderer function call but as return the renderer | | (the renderer = createRenderer < Node, Element>(rendererOptions)) to delay creating renderers? The reason for this is that Vue3 implements tree-shaking of the global API, and has latently built Rendered logic to be tree-shakable. (We know this from the following notes)

// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
Copy the code

Rendered contains a call to createBaseRendered to createRendered:

// vue-next/packages/runtime-core/src/renderer.ts
export function createRenderer<
  HostNode = RendererNode.HostElement = RendererElement> (options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}
Copy the code

CreateBaseRendered is the low-level function to create Rendered support, and the logic of this function is complicated, so I will go through the process later. At this point we just need to focus on the return value of the function:

// vue-next/packages/runtime-core/src/renderer.ts
function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
    / /... Logical omission here
    return {
      render,
      hydrate,
      createApp: createAppAPI(render, hydrate)
    }
}
Copy the code

EnsureRenderer ().createApp(… Source of createApp in args. The value of this attribute is the result of calling createAppAPI.

The app instance comes from createAppAPI

The internal implementation of createAppAPI is as follows:

// vue-next/packages/runtime-core/src/apiCreateApp.ts
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
    }
		
    // Create global context
    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() {
        return context.config
      },

      set config(v) {
        if (__DEV__) {
          warn(
            `app.config cannot be replaced. Modify individual options instead.`)}},use(plugin: Plugin, ... options:any[]) {
        / /... pluggable
        return app
      },

      mixin(mixin: ComponentOptions) {
        / /... Global mixin
        return app
      },

      component(name: string, component? : Component):any {
        / /... Global component registration
        return app
      },

      directive(name: string, directive? : Directive) {
       / /... Global instruction registration
        returnapp }, mount( rootContainer: HostElement, isHydrate? :boolean, isSVG? :boolean) :any {
       	/ /... Application of mount
      },

      unmount() {
        / /... Application of unloading
      },

      provide(key, value) {
        / /... Dependency injection
        return app
      }
    })

    if (__COMPAT__) {
      installAppCompatProperties(app, context, render)
    }

    return app
  }
}
Copy the code

We can see that createAppAPI returns createApp, and when the createApp function is executed, we get an app instance, where the app object performs a series of initialization operations. CreateAppContext () gives you the application global context, which can be accessed by all subsequent application components.

rewriteappOn the objectmountmethods

After analyzing the renderer creation process, we move on to the next step in the internal process of global API createApp: overriding the mount method on the app object.

// 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) // Support both strings and DOM objects
    if(! container)return
    const component = app._component
    // If the root component is not a function object and the render and template attributes are not set, use the innerHTML of the container as the template content
    if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML } container.innerHTML =' ' // Empty container contents before mounting
    const proxy = mount(container) // Perform the mount operation
    if (container instanceof Element) {
      container.removeAttribute('v-cloak') // Avoid Mustache tags during page rendering if the network is bad or you load too much data
      container.setAttribute('data-v-app'.' ')}return proxy
  }

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

Inside the app.mount method, when the information about the root component is set, the original mount method of the app object is called to perform the mount operation. So why override the app.mount method? The reason is that in order to support cross-platform, the app.mount methods defined in the Runtime-DOM package are Web platform-specific methods.

conclusion

The internal implementation of Vue3 is very complex, but the separation of functional modules is very clear, and the API exposed to developers is simple and elegant. So we can create an app instance with const app = createApp({}).