Source code analysis vue3 what does createapp do

What happens when we use createApp to create vue app instances like this?

const { createApp } = Vue
createApp({
    setup() {
      return {
      }
    }
})
.mount('#app')

Let's have a look.

First, enter the createApp function exported by vue, which combines all parameters into args, calls the ensurerender function and createApp on its return data, and then scatters args.

It can be seen here that the ensurerender calls the createRenderer method exported from the runtime core, and the createRenderer receives rendererOptions,

The purpose of doing this is to use renderer to do single instance cache to avoid creating multiple renderers. Second, vue3 considering better support for multi terminal rendering, there is no strong coupling of browser dom operations, but to expose the specific implementation of some operations to developers. The rendererOptions passed in here is the specific implementation of browser dom environment

import {
  createRenderer
} from '@vue/runtime-core'

const rendererOptions = extend({ patchProp }, nodeOps) // Some configurations related to rendering, such as the method of updating attributes and the method of operating DOM

let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer

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

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args) //

  //...
})

baseCreateRenderer is called inside the createRenderer method

export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}

baseCreateRenderer has a lot of internal content. The options here are the platform related operation details passed in from the outside, rendererOptions, and then take the insert, remove and other methods out of the options. It is invoked in a series of rendering related methods below, because the content is too long, which is omitted here. Further down, at the end of the function, two more important things are exported, the render method and the createApp method. Then, the createApp method is created through the createApp API method, which accepts render and hydrate. This is also an external function. You can call ensurerender() Reason for createApp (... Args)

import { createAppAPI } from './apiCreateApp'

function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {


  const { 
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options  
  
  const patch: PatchFn = () => {/*.... */ }
  const processText: ProcessTextOrCommentFn = = () => {/*.... */ }
  const processCommentNode: ProcessTextOrCommentFn = = () => {/*.... */ }
  const mountStaticNode = = () => {/*.... */ }
  const patchStaticNode = () => {/*.... */ }
  const moveStaticNode = () => {/*.... */ }
  const removeStaticNode = () => {/*.... */ }
  const processElement = () => {/*.... */ }
  const mountElement = () => {/*.... */ }
  const setScopeId = () => {/*.... */ }
  const mountChildren: MountChildrenFn = () => {/*.... */ }
  const patchElement = () => {/*.... */ }
  // The fast path for blocks.
  const patchBlockChildren: PatchBlockChildrenFn = () => {/*.... */ }
  const patchProps = () => {/*.... */ }
  const processFragment = () => {/*.... */ }
  const processComponent = () => {/*.... */ }
  const mountComponent: MountComponentFn = () => {/*.... */ }
  const updateComponent = () => {/*.... */ }
  const setupRenderEffect: SetupRenderEffectFn = () => {/*.... */ }
  const updateComponentPreRender = () => {/*.... */ }
  const patchUnkeyedChildren = () => {/*.... */ }
  const patchKeyedChildren = () => {/*.... */ }
  const move: MoveFn = () => {/*.... */ }
  const unmount: UnmountFn = () => {/*.... */ }
  const remove: RemoveFn = () => {/*.... */ }
  const removeFragment = () => {/*.... */ }
  const unmountComponent = () => {/*.... */ }
  const unmountChildren: UnmountChildrenFn = () => {/*.... */ }
  const getNextHostNode: NextFn = () => {/*.... */ }
  const render: RootRenderFunction = () => {/*.... */ }
  const internals: RendererInternals = () => {/*.... */ }


  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

The createApp API internally returned the createApp function. This function mainly creates the basic data structure of APP object, which has UUID, use (registered plug-in), component (registered sub component), mount and other properties and methods. It should be noted here that the mount method internally calls the render renderer received by the createApp API. At this time, it exists in the form of closure, and finally returns the app object

export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    const context = createAppContext()
    const installedPlugins = new Set() 

    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
      },

      use(plugin: Plugin, ...options: any[]) { 
        if (installedPlugins.has(plugin)) {
          __DEV__ && warn(`Plugin has already been applied to target app.`)
        } else if (plugin && isFunction(plugin.install)) { 
          installedPlugins.add(plugin)
          plugin.install(app, ...options) 
        } else if (isFunction(plugin)) { 
          installedPlugins.add(plugin)
          plugin(app, ...options)
        }
        return app
      },

      mixin(mixin: ComponentOptions) {
        if (__FEATURE_OPTIONS_API__) {
          if (!context.mixins.includes(mixin)) {
            context.mixins.push(mixin)
          }
        }
        return app
      },
      // Register components
      component(name: string, component?: Component): any {

        if (!component) { 
          return context.components[name]
        }

        context.components[name] = component
        return app
      },

      directive(name: string, directive?: Directive) {
        if (!directive) {
          return context.directives[name] as any
        }
        context.directives[name] = directive
        return app
      },

      mount(
        rootContainer: HostElement,
        isHydrate?: boolean, 
        isSVG?: boolean
      ): any {
        if (!isMounted) { 
          const vnode = createVNode( 
            rootComponent as ConcreteComponent,
            rootProps
          )
          vnode.appContext = context

          render(vnode, rootContainer, isSVG)
          isMounted = true
          app._container = rootContainer 
          ;(rootContainer as any).__vue_app__ = app  // Hang app to DOM__ vue_ app__  Attribute
          return vnode.component!.proxy
        }
      },

      unmount() {
        if (isMounted) {
          render(null, app._container)
          delete app._container.__vue_app__
        }
      },

      provide(key, value) {
        context.provides[key as string] = value
        return app
      }
    })
    return app
  }
}

After returning the app, you will return to the createApp function at the beginning, store the returned app instance in the app variable, take out the mount method, and redefine an app Mount method, the original mount method is packaged. The purpose of packaging is the same as that of renderer. The specific implementation of the platform is separated and implemented by the developer,
Here is app The containerOrSelector parameter received by the mount method is the #app selector passed in externally, and then normalize the selector with normalizeContainer. The actual dom is returned. If the actual dom is not found, it will be returned directly. If it is found that the component does not provide template and render, the innerHTML of the dom will be taken as the template, Before the mount method is called to mount, the innerHTML of the actual dom is cleared. After the mount, the attributes such as v-cloak are removed and the app is returned

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)

  const { mount } = app // Remove the original mount 
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    const container = normalizeContainer(containerOrSelector)
    if (!container) return

    const component = app._component // Take out the object data of the component
    if (!isFunction(component) && !component.render && !component.template) { 
      component.template = container.innerHTML 
    }
    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>

When the mount method is called externally, app Mount is called naturally

createApp().mount('#app')

This brings you to the initial mount defined in createApp. You create vNode internally, call the render method to render, and modify isMounted

  mount( 
    rootContainer: HostElement,
    isHydrate?: boolean, 
    isSVG?: boolean
  ): any {
    if (!isMounted) {
      const vnode = createVNode(
        rootComponent as ConcreteComponent,
        rootProps
      )
      vnode.appContext = context

      render(vnode, rootContainer, isSVG)

      isMounted = true
      app._container = rootContainer 
          ;(rootContainer as any).__vue_app__ = app  
      return vnode.component!.proxy
    } 
  }

OK, so the whole process is over

The whole calling process is as follows:

createApp - > ensurerender - > basecreaterenderer - > createappapi (receive render method) - > createApp create and return app object - > return to createApp and wrap app Mount - > external call Mount ('#app') - > trigger app Mount - > mount - > call render method to render

Keywords: Front-end

Added by andrew_ww on Fri, 04 Feb 2022 14:03:54 +0200