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