The entry functioncreateApp

Vue2’s entry function is a constructor Vue and must be instantiated to use it. By vue3 the entry function becomes a separate function createApp and we can break into the entry function to see inside

(File in: ThuE-Next3.2 / Packages/Runtime-dom/SRC /index.ts)It turns out that the internal call is another onecreateApp, it is byensureRendererThe function call returns the result of the point call, so it can be inferredensureRendererThe renderer returned is an object, and there must be onecreateAppMethod,

Jump toensureRendererLook inside ensureRendereThe function ensures globalrendererThere is, there is direct return, there is no callcreateRendererCreate a new onerenderer

whilecreateRendererThe interior is quite large (2018 lines), so the code is not included here. It is mainly divided into three parts, 1. Renderer can be customized for diif. Renderer can be customized for diif.patch,patchProps,patchElementTo update the DOM mount:mountChildren,mountComponent,updateComponentBut in the endpatchandrenderMethod, the lastrendererLet’s see what’s inside

Render: render function

CreateApp: the function generated after the execution of createAppAPI

Hydrate: Used for server-side rendering

Application instance generation

Vue2 instance starts with a lot of $methods, but in Vue3, these methods are not directly attached to the instance, but mounted to the rendering context of the component instance. Vue3 instance is a Proxy object, so to say, vue3 relies heavily on Proxy. All data proxies are handled by Proxy.

When vue3 creates the root component instance, it also proxies all methods and attributes that start with $.

Vue3 has no static methods. Instead of static methods, vue3 has instance methods, app.xxx. This makes good use of tree shaking optimization without dead code, and the filter and other attributes in VUe3 have been removed, such as: _component Users write configuration items. _container components mount containers. _context: application context

Initialize the

When createApp is created by createAppAPI, an instance of the app is returned. Each root instance has a mount on it, but the first mount is not on the instance. First save the original function, then extend the mount, and the mount inside the mount calls the mount on the instance

QuerySelector () is recommended to use the ID selector (usually #app). The next step is to get the template. Users may unmount the template directly. After that, we need to clear the contents of the template. After that, we need to mount an empty container.

Execute mount on the instance, taking three arguments: rootContainer: container isHybrate: whether the server renders isSVG in SVG. The function is divided into two main steps: generate vNodes from the component and render vNodes in the container

createVNode

The VNode of a vue is created by createVNode, but is actually created by calling _createNode, in this function: The main task is to make the user passed to process some of the properties, such as class, style, mainly on the props of the processing and to mark for the first time, the component itself shapeFlag, then passed to the createBaseVNode, let it be according to the properties to create, CreateBaseVNode initializes a public VNode and then modifies it with attributes passed in from the outside. Some of the public attributes of a VNode are initialized in createBaseVNode. These tokens are important for subsequent compilation.

  • appContext: Application context
  • shapeFlagMark: theVNodeFor example: native node, component (functional or state component), text child node, etc
  • patchFlagWere recorded:VNodeThose are dynamic,
  • dynamicProps: What familiar changes need to be monitored
  • dynamicChildren: Which child nodes need to be monitored
  • el: Record currentVNodeThe actual real node of

.

Then there’s the processing of the child nodes, and the tracking of the black tree, which is one of the reasons that vue Diff speeds up,

  if (needFullChildrenNormalization) {
  // Standard child node
    normalizeChildren(vnode, children)
    // normalize suspense children
    if(__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ; (type as typeof SuspenseImpl).normalize(vnode)
    }
  } else if (children) {
    // compiled element vnode - if children is passed, only possible types are
    // string or Array.
    // Indicates that a child can only be a text child or an array child
    vnode.shapeFlag |= isString(children)
      ? ShapeFlags.TEXT_CHILDREN
      : ShapeFlags.ARRAY_CHILDREN
  }

// track vnode for block tree
  if (
    isBlockTreeEnabled > 0 &&
    // avoid a block node from tracking itself! isBlockNode &&// has current parent block
    currentBlock &&
    // presence of a patch flag indicates this node needs patching on updates.
    // component nodes also should always be patched, because even if the
    // component doesn't need to update, it needs to persist the instance on to
    // the next vnode so that it can be properly unmounted later.
    (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    // the EVENTS flag is only for hydration and if it is the only flag, the
    // vnode should not be considered dynamic due to handler caching.vnode.patchFlag ! == PatchFlags.HYDRATE_EVENTS ) { currentBlock.push(vnode) }Copy the code

Finally, the created VNode is returned (the last processing is compatible with vue2, not to be repeated here). Compared with the VNode of vue2, a series of tag and other attributes are removed, but a type attribute is added, which records the template of VNode and some configurations. However, the time nodes generated by the two vnodes are different. Vue3 is created directly, while VUe2 is generated after compilation. The vnodes that are finally handed over to Patch for rendering are generated by the generated render function.

Mount the parsing

Renderer. ts, which is used to render the VNode into a container, patch it, and store the latest VNode for the next comparison. Have a deal with the container. _vnode | | null, this is the first time to compatible initialization rendering without old vnode,

The path function takes many parameters, but the most important parameters are n1, n2, and container, which are the old virtual DOM, the new virtual DOM, and the mount container respectively. Inside the patch function, the virtual DOM passed in will be processed first, and then the template parsing will be performed to generate the render function.

The core logic

The types of nodes handled are: Text, comments, static nodes, Fargment(a series of nodes without root nodes, arranged side by side), Element, Component, teleport, suspense, type is the configuration object of the root component when you first enter patch. So the parse component logic (if(shapeFlag & ShapeFlags.component)) is executed first, which means processComponent is executed first

The first time n1 must be null, it will execute if logic, the second if is the built-in keepAlive cache component, initialization is obviously not, else, execute mountComponent,

Rendering function generation

Mounting components are divided into the following steps

  1. Creating a component instance

Instance is a component instance. Instead of some $XXX methods, there are attributes. Some of these attributes are familiar to those who have seen vue2 source code. For example, BC = beforeCreate, BM = beforeMount, bu = beforeUpdate, bum = beforeUnmount, the final CTX property is the $XXX method, and there are some other important properties. Examples are type, vNode, slots, and props

  1. Installation of components

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR

  const { props, children } = instance.vnode
  const isStateful = isStatefulComponent(instance)
  // Initialize properties and slots
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)

  const setupResult = isStateful
  // Install stateful components
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}
Copy the code

In this function, the main job is to initialize properties and slots, and install the stateful component. At this point, the props is initialized before setup is executed, indicating that the props data takes precedence over the component data, and then call setupStatefulComponent

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  // Component is the configuration of the current Component
  const Component = instance.type as ComponentOptions

  if (__DEV__) {
    if (Component.name) {
      validateComponentName(Component.name, instance.appContext.config)
    }
    if (Component.components) {
      const names = Object.keys(Component.components)
      for (let i = 0; i < names.length; i++) {
        validateComponentName(names[i], instance.appContext.config)
      }
    }
    if (Component.directives) {
      const names = Object.keys(Component.directives)
      for (let i = 0; i < names.length; i++) {
        validateDirectiveName(names[i])
      }
    }
    if (Component.compilerOptions && isRuntimeOnly()) {
      warn(
        `"compilerOptions" is only supported when using a build of Vue that ` +
          `includes the runtime compiler. Since you are using a runtime-only ` +
          `build, the options should be passed via your build tool config instead.`)}}// 0. create render proxy property access cache
  instance.accessCache = Object.create(null)
  // 1. create public instance / render proxy
  // also mark it raw so it's never observed
  // The context does the proxy
  instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
  if (__DEV__) {
    exposePropsOnRenderContext(instance)
  }
  // 2. call setup()
  // 
  const { setup } = Component
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    setCurrentInstance(instance)
    pauseTracking()
    // setup exists and executes setup and passes some parameters to it. Pass props and CTX
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    unsetCurrentInstance()

    if (isPromise(setupResult)) {
      setupResult.then(unsetCurrentInstance, unsetCurrentInstance)

      if (isSSR) {
        // return the promise so server-renderer can wait on it
        return setupResult
          .then((resolvedResult: unknown) = > {
            handleSetupResult(instance, resolvedResult, isSSR)
          })
          .catch(e= > {
            handleError(e, instance, ErrorCodes.SETUP_FUNCTION)
          })
      } else if (__FEATURE_SUSPENSE__) {
        // async setup returned Promise.
        // bail here and wait for re-entry.
        instance.asyncDep = setupResult
      } else if (__DEV__) {
        warn(
          `setup() returned a Promise, but the version of Vue you are using ` +
            `does not support it yet.`)}}else {
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    // Handle things like options
    finishComponentSetup(instance, isSSR)
  }
}
Copy the code

The setup is removed from the configuration. The execution with error handling passes some parameters, props and CTX. If a Promise is returned, the server render is returned so that the server renderer can wait for it. If it is an asynchronous server rendering, store it and wait for it to return

There are two cases where an object is returned or a function is returned. In the case of an object (data, and assuming you can get the render function from the render template), proxy it directly and set it on the component instance, while the function, plain render or server-side ssrRender, is stored on the component instance. The returned object is propped like ref, and the option API is handled with a call to finishComponentSetup

In V3, the rendering function of V2 has been standardized as a functional component. The specific compatibility process can be seen in the convertLegacyRenderFn function in renderer.ts. If render function does not exist, all render functions will be NOOP functions to inherit from mixins/extend. Generate the render function by template compilation in the case of client rendering and no render function on the instance.

In order to be compatible with v2 inline templates, there is no need to directly look for the entire template. If the template is not found, the template will not be compiled. Before compiling the template, the compiled configuration will be collected (v2 and V3 will be collected), get the final configuration options, and execute complie(actually execute baseComplie) for compilation. Make a compilation trilogy

Compilation trilogy

This parameter is used to prevent the use of with(this) when in strict mode (when the vue mode is set to module)

The compilation trilogy is divided into: 1. Convert templates to AST syntax trees. 2. Generating the render function

Convert the template toASTThe syntax tree

Converting a template to an AST syntax tree (vue3 compilation is deep-search first, and all children of a node are compiled before the next node is compiled) calls baseParse, but its core is in parseChildren, and in parseChildren (in VUe2, Vue3 uses a large number of regular table to match,vue3 uses a functional way), the code is relatively large, here part by part to see, before introducing several variables,

Ancestors: Are stored in order of the parent node array, and the parent hope to get is when I compile position closest parent element, convenient check behind is compiled on the child nodes in a node, the HTML templates are double label, is divided into the start and end, and compiled the first to get the start tag, behind the compilation to the ending tag or tell program, This node is finished compiling

Ns: indicates the current node type

Nodes: stores compiled nodes.

delimiters:

Mainly divided into interpolation, text, label, the first judgment is not interpolation, through the use ofparseInterpolationAnalytical,

Then there is the content of the < tag that starts with the < tag, which is parseElement if you use the re match to

If neither method is found, it is plain text and parseText is used

Finally, v2 blank processing, according to the processing results, return,

The final AST syntax tree looks like this

conversionASTThe syntax tree

Simply rely on ast syntax tree cannot generate render, need to get some methods and attributes, such as function cache, static nodes, as well as instruction conversion, node conversion and other tool functions, generated by the template ast to convert

generaterenderfunction

Once you get the final AST syntax tree, you can pass it into generate function to generate render. The function will be optimized according to some tags in the AST syntax tree, such as static node promotion, static attribute promotion, function cache, inline template, etc

export function generate(ast: RootNode, options: CodegenOptions & { onContextCreated? : (context: CodegenContext) =>void
  } = {}
) :CodegenResult {
// Generate context
  const context = createCodegenContext(ast, options)
  if (options.onContextCreated) options.onContextCreated(context)
  const {
    mode,
    push,
    prefixIdentifiers,
    indent,
    deindent,
    newline,
    scopeId,
    ssr
  } = context

  const hasHelpers = ast.helpers.length > 0
  // Can use with(this)
  constuseWithBlock = ! prefixIdentifiers && mode ! = ='module'
  constgenScopeId = ! __BROWSER__ && scopeId ! =null && mode === 'module'
  constisSetupInlined = ! __BROWSER__ && !! options.inline// preambles
  // in setup() inline mode, the preamble is generated in a sub context
  // and returned separately.
  const preambleContext = isSetupInlined
    ? createCodegenContext(ast, options)
    : context
  if(! __BROWSER__ && mode ==='module') {
    genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
  } else {
    // Static promotion
    genFunctionPreamble(ast, preambleContext)
  }
  // enter render function
  const functionName = ssr ? `ssrRender` : `render`
  // Server-side render and client-side render
  const args = ssr ? ['_ctx'.'_push'.'_parent'.'_attrs'] : ['_ctx'.'_cache']
  if(! __BROWSER__ && options.bindingMetadata && ! options.inline) {// binding optimization args
    args.push('$props'.'$setup'.'$data'.'$options')}constsignature = ! __BROWSER__ && options.isTS ? args.map(arg= > `${arg}: any`).join(', ')
      : args.join(', ')

  if (isSetupInlined) {
    push(` (${signature}) = > {`)}else {
    push(`function ${functionName}(${signature}) {`)
  }
  indent()

  if (useWithBlock) {
    push(`with (_ctx) {`)
    indent()
    // function mode const declarations should be inside with block
    // also they should be renamed to avoid collision with user properties
    // Rename some methods of the create function
    if (hasHelpers) {
      push(
        `const { ${ast.helpers
          .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
          .join(', ')} } = _Vue`
      )
      push(`\n`)
      newline()
    }
  }

  // Generate asset resolution statements Generate a list of configured assets for future use
  if (ast.components.length) {
    genAssets(ast.components, 'component', context)
    if (ast.directives.length || ast.temps > 0) {
      newline()
    }
  }
  if (ast.directives.length) {
    genAssets(ast.directives, 'directive', context)
    if (ast.temps > 0) {
      newline()
    }
  }
  if (__COMPAT__ && ast.filters && ast.filters.length) {
    newline()
    genAssets(ast.filters, 'filter', context)
    newline()
  }

   // Pull out the methods in vUE
  if (ast.temps > 0) {
    push(`let `)
    for (let i = 0; i < ast.temps; i++) {
      push(`${i > 0 ? `, ` : ` `}_temp${i}`)}}if (ast.components.length || ast.directives.length || ast.temps) {
    push(`\n`)
    newline()
  }

  // generate the VNode tree expression
  if(! ssr) { push(`return `)}if (ast.codegenNode) {
    genNode(ast.codegenNode, context)
  } else {
    push(`null`)}if (useWithBlock) {
    deindent()
    push(`} `)
  }

  deindent()
  push(`} `)

  return {
    ast,
    code: context.code,
    preamble: isSetupInlined ? preambleContext.code : ` `.// SourceMapGenerator does have toJSON() method but it's not in the types
    map: context.map ? (context.map as any).toJSON() : undefined}}Copy the code

At the end, the generated function is still a string, which needs to be converted into a function later. At this point, the whole compilation process is complete

Mount the start

Installation of components

Go back to the stack of finishComponentSetup and start processing the configuration items for V2 by calling applyOptions(instance).

dataOption handling

computedOption to deal withAnd then there’s some option processing, and then after the option processing is done, it’s the lifecycle function, the constraint is exposed, and then it’s done, and then it goes backmountComponentExecute the stack to start installing render dependenciessetupRenderEffect

SetupRenderEffect internally defines a function componentUpdateFn, which serves to update and mount two projects. Internally, there are two ways to call patch, the difference is whether there is an old VNode.

// Initialize render
patch(
    null,
    subTree,
    container,
    anchor,
    instance,
    parentSuspense,
    isSVG
)

// Update render
patch(
  prevTree,
  nextTree,
  // parent may have changed if it's in a teleporthostParentNode(prevTree.el!) ! .// anchor may have changed if it's in a fragment
  getNextHostNode(prevTree),
  instance,
  parentSuspense,
  isSVG
)
Copy the code

RenderComponentRoot (renderComponentRoot, renderComponentRoot, renderComponentRoot, renderComponentRoot, renderComponentRoot, renderComponentRoot, renderComponentRoot, renderComponentRoot, renderComponentRoot) Attr, slots, emit will be passed in if it is a function component.

Install dependencies

Before and after the mount update, the lifecycle function of V2 and v3 will be executed respectively. SetupRenderEffect is equivalent to the mountComponent in V2. In V2, reactive dependency is used to render watcher, while in V3, effect, SetupRenderEffect performs an effect internally. Effect creates a dependency mapping between the incoming FN and the reactive data it calls internally. After v3.2, this was changed to instantiate a ReactiveEffect object to create a dependency mapping. In the end in the generated update function default execution, internal will call componentUpdateFn, mount, vue3 initialization process ends

The mounting process is as follows: Mount () => processComponent() => mountComponent() => setupComponent() then SetupComponent () calls setupStatefulComponent() to install the stateful component setupRenderEffect() dependency collection

Finally: welcome guidance and comments