Version: 3.0.11

1. Preparation – source code structure

1.1 Directory Structure

Before officially learning the source code, first download the source code on Vue official Github website, download the decompression directory is about like this.

  • compiler-core

  • compiler-dom

  • compiler-sfc

  • compiler-ssr

  • reactivity

  • Run-time core Specifies the runtime core

So let’s just show you what’s inside run-time Core.

Vue’s main core API is in the runtime core, and Vue3 uses TypeScript as its development language.

In Run-time Core, the main modules vnode, H, Components, apiCreateApp and apiLifecycle are introduced first, because this module is the core of Vue3 compositionApi functions

  • runtime-dom

  • server-render

  • sfc-playground

  • shared

  • size-check
  • template-explorer
  • vue

1.2 Compiled source code looks like

It looks something like this! It is not difficult to find that each function module has three JS files and one TS. First of all, js files are called with ESM-Bundler version when dev mode is used in normal development, and prod version is called when the project is packaged and built. It is not clear when the remaining version will be called. Ts file inside the definition of the interface type, not the real sense of the running code, aid in the development of data type inference.

2 What does createApp do

2.1 createApp from main.js

First let’s take a look at the Vue3 initialization code. The Vue3 initialization is created by executing the createApp method, whereas in vue2 the vue instance is created using new vue (). So let’s see what createApp does.

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
Copy the code

When we see the createApp method inside the browser devTool breakpoint, execute the following code.

The renderer is created, the app instance is created, and the mount function is overridden. The Renderer is created with the ensureRenderer method, then the App instance is created with createApp, and finally the app.mount method is overridden to return the Proxy proxy component instance

const createApp = ((. args) = > {
    constapp = ensureRenderer().createApp(... args);if((process.env.NODE_ENV ! = ='production')) {
        injectNativeTagCheck(app);
        injectCustomElementCheck(app);
    }
    const { mount } = app;
    app.mount = (containerOrSelector) = > {
        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;
    };
    return app;
});
Copy the code

2.2 create the renderer

EnsureRender () is one of the first implemented methods, and the lazy creation of the renderer is ensureRender(). The reason for this is that when the user only introduces reactivity through Vue, it ensures that tree-shaking is available if the renderer has already been created it is returned directly, otherwise a new renderer is created through createRenderer.


const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps);
// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
let renderer;
let enabledHydration = false;
function ensureRenderer() {
    return renderer || (renderer = createRenderer(rendererOptions));
}
Copy the code

The createRenderer method takes a rendererOption argument, where the method encapsulated in nodeOps is a DOM node operation that is called when the template data changes.

PatchProp updates DOM properties, including style inline styles, class class styles, and patchEvent event bindings

Look at the createRenderer method, which executes the baseCreateRenderer method and passes in the option argument.


function createRenderer(options) {
    console.log('% C calls baseCreateRenderer >>>'.'color:red')
    return baseCreateRenderer(options);
}

Copy the code

The baseCreateRenderer method returns an object with three elements: Render, Hydrate, and createApp.

var count = 0
function baseCreateRenderer(options, createHydrationFns) {
    

    // The code is too long to omit N lines...
   
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    };
}
Copy the code

2.3 Creating an APP Instance

CreateAppAPI = createAppAPI; createApp = createAppAPI; createAppAPI = createAppAPI; createAppAPI = createAppAPI

function createAppContext() {
    return {
        app: null.config: {
            isNativeTag: NO,
            performance: false.globalProperties: {},
            optionMergeStrategies: {},
            isCustomElement: NO,
            errorHandler: undefined.warnHandler: undefined
        },
        mixins: [].components: {},
        directives: {},
        provides: Object.create(null)}; }let uid = 0;
function createAppAPI(render, hydrate) {
    return function createApp(rootComponent, rootProps = null) {
        if(rootProps ! =null&&! isObject(rootProps)) { (process.env.NODE_ENV ! = ='production') && warn(`root props passed to app.mount() must be an object.`);
            rootProps = null;
        }
        const context = createAppContext();
        const installedPlugins = new Set(a);let isMounted = false;
        const app = (context.app = {
            _uid: uid++,
            _component: rootComponent,
            _props: rootProps,
            _container: null._context: context,
            version,
            get config() {
                return context.config;
            },
            set config(v) {
                if((process.env.NODE_ENV ! = ='production')) {
                    warn(`app.config cannot be replaced. Modify individual options instead.`); }},use(plugin, ... options) {
                if(installedPlugins.has(plugin)) { (process.env.NODE_ENV ! = ='production') && 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); }else if((process.env.NODE_ENV ! = ='production')) {
                    warn(`A plugin must either be a function or an object with an "install" ` +
                        `function.`);
                }
                return app;
            },
            mixin(mixin) {
                if (__VUE_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; }}else if((process.env.NODE_ENV ! = ='production')) {
                        warn('Mixin has already been applied to target app' +
                            (mixin.name ? ` :${mixin.name}` : ' ')); }}else if((process.env.NODE_ENV ! = ='production')) {
                    warn('Mixins are only available in builds supporting Options API');
                }
                return app;
            },
            component(name, component) {
                if((process.env.NODE_ENV ! = ='production')) {
                    validateComponentName(name, context.config);
                }
                if(! component) {return context.components[name];
                }
                if((process.env.NODE_ENV ! = ='production') && context.components[name]) {
                    warn(`Component "${name}" has already been registered in target app.`);
                }
                context.components[name] = component;
                return app;
            },
            directive(name, directive) {
                if((process.env.NODE_ENV ! = ='production')) {
                    validateDirectiveName(name);
                }
                if(! directive) {return context.directives[name];
                }
                if((process.env.NODE_ENV ! = ='production') && context.directives[name]) {
                    warn(`Directive "${name}" has already been registered in target app.`);
                }
                context.directives[name] = directive;
                return app;
            },
            mount(rootContainer, isHydrate, isSVG) {
                if(! isMounted) {const vnode = createVNode(rootComponent, rootProps);
                    // store app context on the root VNode.
                    // this will be set on the root instance on initial mount.
                    vnode.appContext = context;
                    // HMR root reload
                    if((process.env.NODE_ENV ! = ='production')) {
                        context.reload = () = > {
                            render(cloneVNode(vnode), rootContainer, isSVG);
                        };
                    }
                    if (isHydrate && hydrate) {
                        hydrate(vnode, rootContainer);
                    }
                    else {
                        render(vnode, rootContainer, isSVG);
                    }
                    isMounted = true;
                    app._container = rootContainer;
                    rootContainer.__vue_app__ = app;
                    if((process.env.NODE_ENV ! = ='production') || __VUE_PROD_DEVTOOLS__) {
                        devtoolsInitApp(app, version);
                    }
                    return vnode.component.proxy;
                }
                else if((process.env.NODE_ENV ! = ='production')) {
                    warn(`App has already been mounted.\n` +
                        `If you want to remount the same app, move your app creation logic ` +
                        `into a factory function and create fresh app instances for each ` +
                        `mount - e.g. \`const createMyApp = () => createApp(App)\``); }},unmount() {
                if (isMounted) {
                    render(null, app._container);
                    if((process.env.NODE_ENV ! = ='production') || __VUE_PROD_DEVTOOLS__) {
                        devtoolsUnmountApp(app);
                    }
                    delete app._container.__vue_app__;
                }
                else if((process.env.NODE_ENV ! = ='production')) {
                    warn(`Cannot unmount an app that is not mounted.`); }},provide(key, value) {
                if((process.env.NODE_ENV ! = ='production') && key in context.provides) {
                    warn(`App already provides property with key "The ${String(key)}". ` +
                        `It will be overwritten with the new value.`);
                }
                // TypeScript doesn't allow symbols as index type
                // https://github.com/Microsoft/TypeScript/issues/24587
                context.provides[key] = value;
                returnapp; }});return app;
    };
}

Copy the code

The core of createAppAPI is to call createAppContext to return a baseline object containing APP, config, mixins, Components, Directives and provides parameters, and then create context.app. Directives The vue instance object, which contains internal parameters and version numbers, exposes the use, Component, mount, mixin, directive, unmount, and provide apis for us to use.

Author: Jia Wenli, Freely Big front End R&D Center