Vue3 source code analysis

Depend on the installation

yarn --ignore-scripts
Copy the code

Ready to debug

  • Add – sourcemap
    "dev": "node scripts/dev.js --sourcemap",
    Copy the code
  • Perform yarn dev

Entrance to the file

  • Start by running the NPM run dev command

    // "dev": "node scripts/dev.js --sourcemap"
    // find scripts/dev.js find TARGET is vue by default
    Copy the code
  • Start with the rollup package configuration file rollup.config.js

    // Package the entry file
    SRC /index.ts SRC /index.ts
    // Runtime WebPack runtime version
    const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
    
    // packages/
    const packagesDir = path.resolve(__dirname, 'packages')
    // Default packages/vue
    const packageDir = path.resolve(packagesDir, process.env.TARGET)
    const name = path.basename(packageDir)
    const resolve = p= > path.resolve(packageDir, p)
    
    // Import file configuration
    // input: resolve(entryFile),
    / / find entry files are packages/vue/SRC/index. The ts
    Copy the code
  • The SRC /index.ts file finally exports all the methods in the Runtime-dom including createApp

    export * from '@vue/runtime-dom'
    Copy the code

CreateApp method

// runtime-dom.ts
export const createApp = ((. args) = > {
   // Get a renderer first
   The createApp method is actually provided by the renderer
   constapp = ensureRenderer().createApp(... args)/ /... Some code is omitted here
   return app
}
Copy the code
  • The ensureRenderer method is called in createApp

    import {
      createRenderer,
    } from '@vue/runtime-core'
    function ensureRenderer() {
      / / the renderer
      // Singleton mode
      // Return if it exists, create if it does not exist
      return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
    }
    Copy the code
  • CreateRenderer is called again in the ensureRenderer method

    • Open @ vue/runtime – core/index. The ts

    • CreateRenderer from. / the renderer. Ts

      // @vue/runtime-core/index.ts
      export { createRenderer } from './renderer'
      Copy the code
      // ./renderer.ts
      export function createRenderer<
        HostNode = RendererNode.HostElement = RendererElement> (options: RendererOptions<HostNode, HostElement>) {
        return baseCreateRenderer<HostNode, HostElement>(options)
      }
      /* export function createRenderer(options) { return baseCreateRenderer(options) } */
      Copy the code
    • The baseCreateRenderer function is almost 2000 lines of code (the renderer’s factory function)

    • Finally, I gave up and decided to start with the return value at the bottom

    • So I’m going to skip the rest of the code

      function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
      ) :any {
      
        / /... I'm going to omit 2000 lines of code
            
        // The object returned here is the renderer
        // There are three methods
        return {
          Render (vnode, container); render(vnode, container);
          render,
          // Water injection for server rendering
          hydrate,
          // The method to create an application instance
          createApp: createAppAPI(render, hydrate)
        }
      }
      Copy the code
    • CreateApp is returned by the createAppAPI method

      • The following methods all revert back to the app instance, so they can be called chained

      • Compared to Vue2, the following methods are converted from static methods to instance methods

        // Why adjust to instance methods?
          // 1. Avoid contamination between instances
          // 2
          // 3. Tree-shake
             // Tree shaking:
                  Use ({install(){}}) initializes a plug-in
                  // But in the code, this plug-in is not actually used
                  // The plugin will not be packaged when the code is packaged
        Copy the code
        • I’m so happy to finally see so many old friends

          // import { createAppAPI, CreateAppFunction } from './apiCreateApp'
          // packages\runtime-core\src\apiCreateApp.ts
          export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
            // The method returned from the outside
            return function createApp(rootComponent, rootProps = null) {
              if(rootProps ! =null && !isObject(rootProps)) {
                rootProps = null
              }
          
              const context = createAppContext()
              const installedPlugins = new Set(a)let isMounted = false
          
              // Application instance
              const app: App = (context.app = {
                _uid: uid++,
                _component: rootComponent as ConcreteComponent,
                _props: rootProps,
                _container: null._context: context,
          
                version,
          
                get config() {
                  return context.config
                },
          
                set config(v) {/ *... * /},
                // The plugin uses the method initialization
                // The first argument passed in is the app instance
                // The first argument passed in vue2 is Vue itself (constructor)
                use(plugin: Plugin, ... options: any[]) {
             		if(plugin && isFunction(plugin.install)) { installedPlugins.add(plugin) plugin.install(app, ... options) }else if(isFunction(plugin)) { installedPlugins.add(plugin) plugin(app, ... options) }return app
                },
                // Blend method initialization
                mixin(mixin: ComponentOptions) {
                  if (__FEATURE_OPTIONS_API__) {
                    if(! context.mixins.includes(mixin)) { context.mixins.push(mixin)// global mixin with props/emits de-optimizes props/emits
                      // normalization caching.
                      if (mixin.props || mixin.emits) {
                        context.deopt = true}}}return app
                },
                // Initialize the component methodcomponent(name: string, component? : Component): any {if(! component) {return context.components[name]
                  }
                  context.components[name] = component
                  return app
                },
          	 // Initialize the instruction method
                directive(name: string, directive? : Directive) {
                  if(! directive) {return context.directives[name] as any
                  }
                  context.directives[name] = directive
                  return app
                },
                // Mount initialization goes heremount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : boolean ): any {if(! isMounted) {// Initializes the virtual DOM tree
                    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
                      
                    if (isHydrate && hydrate) {
                      hydrate(vnode as VNode<Node, Element>, rootContainer as any)
                    } else {
                      // This is not the server rendering, the client rendering goes here by default
                      render(vnode, rootContainer, isSVG)
                    }
                    isMounted = true
                    app._container = rootContainer
                    // for devtools and telemetry; (rootContaineras any).__vue_app__ = app
          
                    returnvnode.component! .proxy } },// Uninstall initialization
                unmount() {
                  if (isMounted) {
                    render(null, app._container)
                    delete app._container.__vue_app__
                  }
                },
                Dependency injection multi-level nested communication
                provide(key, value) {
                  // TypeScript doesn't allow symbols as index type
                  // https://github.com/Microsoft/TypeScript/issues/24587
                  context.provides[key as string] = value
                  return app
                }
              })
              return app
            }
          }
          Copy the code

To mount the disk, mount the disk

  • The render function is finally executed inside mount

    // Mount internal implementation
    // This is not the server rendering, the client rendering goes here by default
    render(vnode, rootContainer, isSVG)
    Copy the code
  • Patch is executed in the render function

    // Initialization goes here, which is similar to vue2
    // Update if parameter 1 exists
    // If parameter 1 does not exist, go to the mount process
    const render: RootRenderFunction = (vnode, container, isSVG) = > {
        / /... Omit some code
        patch(container._vnode || null, vnode, container, null.null.null, isSVG)
        / /... Omit some code
    }
    
    // Pacth internally distinguishes text comment static nodes (nodes that do not change)
    // Fragment Abstract virtual node (parent container). Vue2 can have only one node. Vue3 can have multiple nodes
    // Element node components, etc
    
    case Text:
            processText(n1, n2, container, anchor)
    case Comment:
            processCommentNode(n1, n2, container, anchor)
    / /... Omit some code
    default:
    	/ /... Omit some code
    	else if (shapeFlag & ShapeFlags.COMPONENT) {
            // Initialization goes here
            // Because the following is done when initializing the mount
            Const vNode = createVNode(rootComponent as ConcreteComponent,) const vNode = createVNode(rootComponent as ConcreteComponent,); // Render (vnode, rootContainer, isSVG) */
            processComponent(
                n1,
                n2,
                container,
                anchor,
                parentComponent,
                parentSuspense,
                isSVG,
                slotScopeIds,
                optimized
            )
        }
    	/ /... Omit some code
    Copy the code
  • Then initialize the line processComponent

    • The mountComponent method is executed because n1(the old vNode) does not exist
    • In vue2 mountComponent declares the updateComponent function new and a render Watcher does the dependency collection for component granularity
      • And Vnode patch operation vm._update(vm._render(), hydrating), which generates the real node and finally performs the root node replacement operation, nodeops.insertbefore to the page
      • And callHook(VM, ‘beforeMount ‘) and callHook(VM, ‘mounted’)
    • This is obviously what Vue3 does, converting vNodes into real DOM and updating setupComponent installation
    const processComponent = (
        n1: VNode | null./ / the old vnode
        n2: VNode,  / / new vnode
        container: RendererElement,
        anchor: RendererNode | null,
        parentComponent: ComponentInternalInstance | null,
        parentSuspense: SuspenseBoundary | null,
        isSVG: boolean,
        slotScopeIds: string[] | null,
        optimized: boolean
      ) = > {
        n2.slotScopeIds = slotScopeIds
        // Initialize container to null because container is the id passed in when #app calls mount
        // There are no old vNodes
        if (n1 == null) {
           // Whether to cache components for keep-alive
           // when initialized n2 is a vNode non-cached component converted from createApp options
          if(n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ; (parentComponent! .ctxas KeepAliveContext).activate(
              n2,
              container,
              anchor,
              isSVG,
              optimized
            )
          } else {
            // So the initialization goes here, i.e. the initial mount is handled here
            mountComponent(
              n2, / / new vnode
              container, // #app
              anchor,
              parentComponent,
              parentSuspense,
              isSVG,
              optimized
            )
          }
        } else {
          // Verify that the old vNode is updated and the mountComponent is not mounted
          updateComponent(n1, n2, optimized)
        }
      }
    Copy the code
  • Then initialize the mountComponent above

    • Creating a component instance
    • Added render function side effects
    const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
      // 1. Create a component instance
      const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
    
      // inject renderer internals for keepAlive
      if(isKeepAlive(initialVNode)) { ; (instance.ctxas KeepAliveContext).renderer = internals
    }
    
      // setupComponent setup: similar to vue2's new Vue() initializes this._init(options) in the vue2 constructor
      // Recall what you did with new Vue in VUe2
      // 1. User configuration options and system configuration options are combined
    $parent $root $children $refs
      // 3. Listen for your own custom events
      // 4. Parse your own slot
      // 5. Also, it performs responsive processing on some internal data, such as props(properties) MethoSDS (methods) Data computed watch
        
    // This is exactly what we're doing here
      setupComponent(instance)
    
      // setup() is async. This component relies on async logic to be resolved
      // before proceeding
      if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
        parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
    
        // Give it a placeholder if this is not hydration
        // TODO handle self-defined fallback
        if(! initialVNode.el) {const placeholder = (instance.subTree = createVNode(Comment))
          processCommentNode(null, placeholder, container! , anchor) }return
      }
      
      // Add render function side effects
      // The render function is used to retrieve the virtual DOM
      // The current component is updated and then re-patched
      setupRenderEffect(
        instance,
        initialVNode,
        container,
        anchor,
        parentSuspense,
        isSVG,
        optimized
      )
    }
    Copy the code
    • Follow the process above to install setupComponent

      export function setupComponent(
        instance: ComponentInternalInstance,
        isSSR = false
      ) {
        isInSSRComponentSetup = isSSR
      
        const { props, children } = instance.vnode
        const isStateful = isStatefulComponent(instance)
        / / processing props
        initProps(instance, props, isStateful, isSSR)
        // Process the slot
        initSlots(instance, children)
      
        const setupResult = isStateful
          // Data responsive processing
          ? setupStatefulComponent(instance, isSSR)
          : undefined
        isInSSRComponentSetup = false
        return setupResult
      }
      Copy the code
      • SetupStatefulComponent Data reactive processing

        function setupStatefulComponent(
          instance: ComponentInternalInstance,
          isSSR: boolean
        ) {
          // Since the initialization is passed in a vnode configured with obj conversion,
          // So type is the root component configuration object
          // createApp({
          // data() {
          // return { aa: 'haha' }
          / /},
              
          // The setup option and data(){return {}} can exist together, but setup takes precedence
          // setup() {
          // const aa = ref('haha')
          // return { aa }
          / /}
          // })
          const Component = instance.type as ComponentOptions
        
          // 0. create render proxy property access cache
          instance.accessCache = Object.create(null)
        
          // The root component configuration object does the proxy operation i.e. rendering function context, data responsive proxy
          // All future reactive data in the render function is retrieved from the proxy
          // const instance = getCurrentInstance(); You can get it in setup(){}
          // const { ctx, proxy } = instance
          / / PublicInstanceProxyHandlers will first to find attributes from the setup, if there is no will to find in the data
          instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
        
          // call setup()
          // Handle the setup option
          // The setup option and data(){return {}} can exist together, but setup takes precedence
          / / PublicInstanceProxyHandlers will first to find attributes from the setup, if there is no will to find in the data
          const { setup } = Component
          if (setup) {
            const setupContext = (instance.setupContext =
              setup.length > 1 ? createSetupContext(instance) : null)
        
            currentInstance = instance
            pauseTracking()
            const setupResult = callWithErrorHandling(
              setup,
              instance,
              ErrorCodes.SETUP_FUNCTION,
              [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
            )
            resetTracking()
            currentInstance = null
        
            if (isPromise(setupResult)) {
              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 {
              // The handleSetupResult method is finally called internally
              // finishComponentSetup(instance, isSSR)  
              handleSetupResult(instance, setupResult, isSSR)
            }
          } else {
           If setup is not present in createApp, go here
            finishComponentSetup(instance, isSSR)
          }
        }
        Copy the code
      • Then look at the finishComponentSetup function

        • ApplyOptions (Instance, Component) is called internally, which is compatible with VUe2.0 processing

          const {
              // composition
              mixins,
              extends: extendsOptions,
              // state
              data: dataOptions,
              computed: computedOptions,
              methods,
              watch: watchOptions,
              provide: provideOptions,
              inject: injectOptions,
              // assets
              components,
              directives,
              // lifecycle
              beforeMount,
              mounted,
              beforeUpdate,
              updated,
              activated,
              deactivated,
              beforeDestroy,
              beforeUnmount,
              destroyed,
              unmounted,
              render,
              renderTracked,
              renderTriggered,
              errorCaptured,
              // public API
              expose
            } = options
          Copy the code
    • When the components are installed, go to setupRenderEffect

      // If the data inside the function changes, the function is re-executed
      // There is no watcher here compared to VUe2
      instance.update = effect(function componentEffect() {
        // Get the vNode of the current root component
        // Execute the render function to get the dom
        const subTree = (instance.subTree = renderComponentRoot(instance))
        // Initialize the patch
        // Since initialization is a Fragment
        // After the patch is removed, the nodes of and type will be processed internally and searched recursively
        patch(
          null,
          subTree, // Initialize with a Fragment
          container,
          anchor,
          instance,
          parentSuspense,
          isSVG
        )
      })
      
      // patch -> processFragment
      
      const processFragment = (
        n1: VNode | null,
        n2: VNode,
        container: RendererElement,
        anchor: RendererNode | null,
        parentComponent: ComponentInternalInstance | null,
        parentSuspense: SuspenseBoundary | null,
        isSVG: boolean,
        slotScopeIds: string[] | null,
        optimized: boolean
      ) = > {
        // The old vnode passed a null during initialization
        // n2 new vNode sends subTree
        if (n1 == null) {
          hostInsert(fragmentStartAnchor, container, anchor)
          hostInsert(fragmentEndAnchor, container, anchor)
          // Mount the child node
          mountChildren(
            n2.children as VNodeArrayChildren,
            container,
            fragmentEndAnchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        }
      }
      
      // mountChildren Mounts the child node
      const mountChildren: MountChildrenFn = (
        children,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized,
        slotScopeIds,
        start = 0
      ) = > {
        // Perform patch processing on recursive child nodes
        // Initialization is complete
        // Patch will be processed according to the section type
        // Text tags, comments, etc
        for (let i = start; i < children.length; i++) {
          const child = (children[i] = optimized
            ? cloneIfMounted(children[i] as VNode)
            : normalizeVNode(children[i]))
          patch(
            null,
            child,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized,
            slotScopeIds
          )
        }
      }
      Copy the code

Register global methods

  • Stereotypes are available in Vue2, but not in Vue3

    • Provide/inject Dependency injection (officially recommended)

      • Vue3js. Cn/docs/useful/API…

        // provide and inject enable dependency injection. Both can only be called during setup() using the current active instance.
        import { ref, provide } from 'vue'
        setup() {
            let title = ref('The value to be passed')
            // provide the first is the name, and the second value is the parameter that needs to be passed
            provide('title', title); 
            let setTitle = () = > {
                 // There will be a response after clicking!
                title.value = 'When I click, the title will be this.';
            }
            return {
                title,
                setTitle
            }
        }
        
        / / child component
        import { inject } from 'vue'
        setup() {
            // Inject takes the name of the provide
        	let title = inject('title'); 
        }
        
        Copy the code
    • App. Config. GlobalProperties in app mount before use

      const app = createApp(App)
      app.config.globalProperties.http = () = > {}
      app.mount('#app')
      
      import { ref, computed, watch, getCurrentInstance, onMounted } from "vue";
      export default {
        components: {
        TestComp
        },
        setup( ) {
          // Get the context instance, CTX equals this of vue2
          // But CTX is not responsive
          // Proxy is reactive
          const { ctx, proxy } = getCurrentInstance(); 
      
          onMounted(() = > {
            // This works in the local environment, but the online environment will report an error
            console.log(ctx, "ctx")
            ctx.http()
            
            // This is the only way to use it
            constinstance = getCurrentInstance() instance.appContext.config.globalProperties.http() }); }};Copy the code

xxxx

xxxx