Nowadays, the three front-end frameworks Vue, React and nodeJS are the necessary skills for front-end development. With the release of Vue3 in September 2020, Vue has attracted the attention of more developers, and learning source code should also be put on the agenda. This column will detail the main flow of Vue3 source code and the underlying implementation of usage.

Create a Vue instance

const Vue = createApp(/* options */).mount('#app')
Copy the code

As you can see, Vue3’s entry function calls createApp, a function exposed by Vue3. Take a look at its internal implementation

export const createApp = ((. args) = > {
  constapp = ensureRenderer().createApp(... args)// ...
  const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any= > {}
  
  return app
}) as CreateAppFunction<Element>
Copy the code

The createApp function creates an app object, deconstructs the mount method of the app, redefines the mount method of the app, and then returns the app object. Let’s take a look at how the App object is generated.

App object

constapp = ensureRenderer().createApp(... args)Copy the code

First we call the ensureRenderer() method

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

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

function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
  // ...
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    forcePatchProp: hostForcePatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options
  
  // ...
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}
Copy the code

One can see that the ensureRenderer method ultimately implements baseCreateRenderer, creating options objects with properties defined that are methods of DOM operations, such as addition, deletion, and shift of nodes, etc. These methods are called later when the VNode object is parsed to generate the real DOM. The ensureRenderer method ultimately returns an object, and the return value of the createAppAPI method is called when one calls the createApp property returned with ensureRenderer

export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    // ...
    const context = createAppContext()
    const installedPlugins = new Set(a)let isMounted = false
    const app: App = (context.app = {
      // ...
      _component: rootComponent as ConcreteComponent,
      / /...
    })
    return app
  }
}
Copy the code

As you can see, the createAppAPI method returns a createApp function, so you end up calling the createApp method, which creates an app object, defines property methods such as mount, unmount, and so on, and returns the object.

The mount mount

When the App object is generated, looking back to when we created a Vue instance, the mount method is executed after calling the createApp method, which is the app.mount method. What does the new app.mount method do

/ / normalizeContainer method
function normalizeContainer(
  container: Element | ShadowRoot | string
) :Element | null {
  if (isString(container)) {
    const res = document.querySelector(container)
    // ...
    return res
  }
  // ...
  return container as any
}

// Redefine the app.mount method
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
}
Copy the code

This method first calls normalizeContainer and finds the DOM node with the ID app (hereafter called root) via document.querySelector(‘#app’). Then get the _component object for app, which is the options parameter object (with data, methods, computed properties, and so on) passed in when the createApp method is called.

if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML }Copy the code

The internal HTML string of the root node is assigned to the template property of the options parameter

container.innerHTML = ' '
Copy the code

The internal HTML string is left blank. The root node is now the only one on the page, and its internal child nodes are all cleared. The template string will be template-compiled and the child nodes will be added.

// Container is the root node
const proxy = mount(container, false, container instanceof SVGElement)

container.removeAttribute('v-cloak')
container.setAttribute('data-v-app'.' ')
Copy the code

At the last step, mount (app.mount), remove the V-cloak property from the root node, and add the data-v-app property.

conclusion

Start with Vue3’s method of creating an instance and find its entry function. The ensureRenderer method is implemented to define functions needed to operate on DOM nodes, methods to parse virtual DOM objects into real DOM, and so on. The createApp method is then executed, which defines an app object, creates properties such as _component, which holds the options argument passed in, and defines methods such as mount, unmount, etc. The app object is formed. Mount (‘#app’) is the same as mount(‘#app’). The new app.mount method looks for the root node, stores the HTML string inside the root node in the template attribute (in preparation for subsequent template compilation), and then empties the root node. Finally, mount the vm using the original mount method