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
-