- Micro front End 01: Js isolation mechanism analysis (Snapshot sandbox, two kinds of proxy sandbox)
- Micro Front End 02: Analysis of microapplication loading process (from registration of microapplication to internal implementation of loadApp method)
- Micro front End 03: Sandbox Container Analysis of Universe (concrete application of Js sandbox mechanism after establishment)
- Microfront 04: Resource loading mechanism for The Universe (internal implementation of import-HTml-Entry)
- Micro front End 05: Implementation of loadMicroApp method and analysis of data communication mechanism
- Microfront 06: Registration mechanism for single-SPA
- Microfront 07: Analysis of single-SPA route management and microapplication state management
- Micro front 08: Reroute functions in single-SPA
As mentioned earlier in microfront 07: Analysis of single-SPA route management and microapplication state management, reroute is important because it is called either when the application is registered or when popState or hashchange events are triggered. In fact, when single-SPA loads, starts, mounts, and unmounts microapplications, the logic is mostly executed in this function. This article takes you through the Reroute function to understand how single-SPA manages microapplications at the source level. After writing this article, the relevant analysis of micro-front-end will come to an end for the time being. As for the analysis of other mainstream micro-front-end frameworks on the market, and the realization of a usable micro-front-end framework for production environment, After I complete Vue3, React18, Webpack5, Rollup, Vite and other core frameworks and tools currently on the market for in-depth analysis of the source code, and then step by step with everyone to achieve a production environment available micro front-end framework. This arrangement, because if you choose to use the front-end, actually choose a set of corresponding technical solutions, we in some mainstream core framework and tools of the source code into the in-depth analysis, follow-up to write related supporting infrastructure, everyone can be a more thorough understand why write code to be like this, rather than writing, learning also know why. This is also my desire to do source code exploration, to help you not afraid of source code, but to integrate these contents into the blood, improve work efficiency and learning efficiency, more time to invest in valuable things. In any case, source code exploration is advanced developers to temper the basic skills of the impassable threshold. Ok, now let’s officially get into the reroute function.
Before we begin this article, we willMicrofront 07: Analysis of single-SPA route management and microapplication state managementHere is the flow chart of microapplication state switch for review as you read:
The core logic of the reroute function
Let’s look at the code for this function first:
// Snippet 1
export function reroute(pendingPromises = [], eventArguments) {
// A lot of code is omitted here...
const {
appsToUnload,
appsToUnmount,
appsToLoad,
appsToMount,
} = getAppChanges();
// A lot of code is omitted here...
if (isStarted()) {
// Omit some code here...
appsThatChanged = appsToUnload.concat(
appsToLoad,
appsToUnmount,
appsToMount
);
return performAppChanges();
} else {
appsThatChanged = appsToLoad;
return loadApps();
}
// A lot of code is omitted here...
}
Copy the code
This function originally had nearly 300 lines of code, and we’ve streamlined it considerably here. As you can see from snippet 1, this function does two main things. The first is to get the microapplications registered in single-SPA using the function getAppChanges, and use four array variables to distinguish what the microapplications will do next and what state they will enter. If the single-SPA exposed start function has already been called, performAppChanged will be called to process the microapplication according to the return value of getAppChanges and change the corresponding state. If the start function is used, the loadApp function is called to perform the load operation. LoadApp is discussed in the microfront 06: Registration mechanism for single-SPA and will not be covered in this article. The following describes getAppChanges and performAppChanges respectively.
getAppChanges
First look at the code for the getAppChanges function:
// Snippet 2
export function getAppChanges() {
const appsToUnload = [],
appsToUnmount = [],
appsToLoad = [],
appsToMount = [];
// Omit some code here...
apps.forEach((app) = > {
constappShouldBeActive = app.status ! == SKIP_BECAUSE_BROKEN && shouldBeActive(app);switch (app.status) {
case LOAD_ERROR:
if (appShouldBeActive && currentTime - app.loadErrorTime >= 200) {
appsToLoad.push(app);
}
break;
case NOT_LOADED:
case LOADING_SOURCE_CODE:
if (appShouldBeActive) {
appsToLoad.push(app);
}
break;
case NOT_BOOTSTRAPPED:
case NOT_MOUNTED:
if(! appShouldBeActive && getAppUnloadInfo(toName(app))) { appsToUnload.push(app); }else if (appShouldBeActive) {
appsToMount.push(app);
}
break;
case MOUNTED:
if(! appShouldBeActive) { appsToUnmount.push(app); }break; }});return { appsToUnload, appsToUnmount, appsToLoad, appsToMount };
}
Copy the code
You can see from snippet 2 that the logic of this function is actually quite simple. Define four arrays, then deduce the state that the function is going to enter based on the different states that the microapplications are currently in, and put the microapplications that are going to enter the same state into the same array. Refer to the flow chart at the beginning of this article for state changes in microapplications. Here’s a quick look at the logic in the code to add the corresponding microapplication to the array.
Array appsToLoad
We found that microapps in the NOT_LOADED and LOADING_SOURCE_CODE states were put into the array appsToLoad. In fact, the microapps stored in the array appsToLoad were about to be loaded in the subsequent logic, and during the loading process, The state changes to LOADING_SOURCE_CODE, and after loading, the state changes to NOT_BOOTSTRAPPED, meaning that any microapps that were not loaded before will be loaded here. Don’t worry, though, because the function that executes the load has code like this:
// Snippet 3
export function toLoadPromise(app) {
return Promise.resolve().then(() = > {
if (app.loadPromise) {
return app.loadPromise;
}
if(app.status ! == NOT_LOADED && app.status ! == LOAD_ERROR) {return app;
}
// ...
return (app.loadPromise = Promise.resolve()
.then(() = > {
// ...
delete app.loadPromise;
// ...
})
.catch((err) = > {
delete app.loadPromise;
// ...
}));
});
}
Copy the code
That is, the code is cached using app.loadPromise and does not load again. I’ll cover the function toLoadPromise in more detail later in this article, where I mention instructions to answer any questions you might have when looking at the code for the first time.
Array appsToUnload
As we can also see from code snippet 2, a micro app that is NOT_BOOTSTRAPPED, NOT_MOUNTED or strapped does not need to be “loaded” and getAppUnloadInfo(toName(app)) returns true. The microapplication is added to the array appsToUnload. The getAppUnloadInfo function looks like this:
// Snippet 4
export function getAppUnloadInfo(appName) {
return appsToUnload[appName];
}
Copy the code
Object appsToUnload
Please note that appsToUnload in getAppUnloadInfo is a global object, not the appsToUnload array in getAppChanges. If getAppUnloadInfo returns true, the user has manually called unloadApplication, Because the object appsToUnload in getAppUnloadInfo is only changed during the execution of unloadApplication.
unloadApplication
Here is the official description of the unloadApplication function in the single-SPA documentation:
The purpose of unloading a registered application is to set it back to a NOT_LOADED status, which means that it will be re-bootstrapped the next time it needs to mount. The main use-case for this was to allow for the hot-reloading of entire registered applications, but unloadApplication can be useful whenever you want to re-bootstrap your application.
If you want to implement bootstrap, the life cycle function for re-executing microapplications, then calling the unloadApplicaton function is a good choice. 1. MOUNTED >UNMOUNTING >UNLOADED Micro-applications are not easy to uninstall. 2. Calling unloadApplicaton does not happen.
toUnloadPromise
In fact, the main logic that the microapplication in the array appsToUnload is going to execute is in the function toUnloadPromise, see the code:
// Snippet 5
export function toUnloadPromise(app) {
return Promise.resolve().then(() = > {
const unloadInfo = appsToUnload[toName(app)];
// The appsToUnload object has no value, indicating that the unloadApplicaton function has not been called and there is no need to continue
if(! unloadInfo) {return app;
}
// Indicates that the state is NOT_LOADED
if (app.status === NOT_LOADED) {
finishUnloadingApp(app, unloadInfo);
return app;
}
// The state that is already in the unload, waiting for the execution result, note that the promise is taken from the object appsToUnload
if (app.status === UNLOADING) {
return unloadInfo.promise.then(() = > app);
}
// The state transition of an application should follow the procedure as shown in the flowchart. Only a small application in an UNMOUNTED state can still get ->UNLOADED
if(app.status ! == NOT_MOUNTED && app.status ! == LOAD_ERROR) {return app;
}
const unloadPromise =
app.status === LOAD_ERROR
? Promise.resolve()
: reasonableTime(app, "unload");
app.status = UNLOADING;
return unloadPromise
.then(() = > {
finishUnloadingApp(app, unloadInfo);
return app;
})
.catch((err) = > {
errorUnloadingApp(app, unloadInfo, err);
return app;
});
});
}
Copy the code
The toUnloadPromise function can be considered to do three main things: first, it can not meet the execution conditions of the interception, the reason for the interception has been written in the comments of code snippe5; The reasonableTime function is used to actually perform the unloading logic. The third is to execute the function finishUnloadingApp or errorUnloadingApp to change the state of the micro-application. The logic of changing state is relatively simple and won’t be covered here, but the source implementation of the function reasonableTime is examined below.
reasonableTime
// Snippet 6
export function reasonableTime(appOrParcel, lifecycle) {
// A lot of code is omitted here...
return new Promise((resolve, reject) = > {
// A lot of code is omitted here...
appOrParcel[lifecycle](getProps(appOrParcel))
.then((val) = > {
finished = true;
resolve(val);
})
.catch((val) = > {
finished = true;
reject(val);
});
// A lot of code is omitted here...
});
}
Copy the code
This function can be understood as doing three things: it does a timeout, which I omitted in snippet 6; The second is to execute the function corresponding to the Lifecycle variable of the microapplication. In the case of the current analysis of the toUnloadPromise function, lifecycle corresponds to the unload attribute. The Unload attribute is added in the function toLoadPromise, which gives the microapplication the ability to unload during the load phase. In fact, the Unload function is retrieved from objects exposed by the microapplication after the microapplication has been loaded.
Arrays appsToMount, appsToUnmount, appsToMount
As you can see from snippets 2, a micro application that is NOT_BOOTSTRAPPED or NOT_MOUNTED will be added to the array appsToMount if the routing rules match. As for the array appsToUnmount, there are many similarities between the analysis and subsequent execution of the array appsToUnmount and the execution of the microapplication in appsToUnload that are not covered in this article.
performAppChanges
From the analysis above, we know the state of each microapplication and what logic will be executed next. But what’s the order in which these microapplications are processed, so I’m going to go into performAppChange.
The core logic
The core logic of performAppChanges is as follows:
// Snippet 7
function performAppChanges() {
return Promise.resolve().then(() = > {
// A lot of code is omitted here...
const unloadPromises = appsToUnload.map(toUnloadPromise);
const unmountUnloadPromises = appsToUnmount
.map(toUnmountPromise)
.map((unmountPromise) = > unmountPromise.then(toUnloadPromise));
const allUnmountPromises = unmountUnloadPromises.concat(unloadPromises);
const unmountAllPromise = Promise.all(allUnmountPromises);
unmountAllPromise.then(() = > {
// A lot of code is omitted here...
});
// A lot of code is omitted here...
return unmountAllPromise
.catch((err) = > {
callAllEventListeners();
throw err;
})
.then(() = > {
callAllEventListeners();
// A lot of code is omitted here...
});
});
}
Copy the code
Omitted code is also important, mainly in the dispatch of custom events, because it is too long and affects the reading experience. In general, this function does three things: first, it performs unload logic; Second, execute the related mount logic after the unmount logic. The third is to define events at different stages. The implementation logic for unload and mount is similar to toUnloadPromise, which was analyzed above, so it will not be described in this article. The following focuses on the callAllEventLiseners function and custom events in single-SPA.
callAllEventListeners
Remember we did in micro front end 07: An analysis of single-SPA routing and microapplication state management mentioned that registered hashchange and popState events are stored in an array without invoking the original listening event registration logic. Where the callAllEventListeners are called in performAppChanges is the appropriate time to trigger these events stored in the array. Because this function is called, all microapplications that need to be unloaded are completely unloaded or the application is loaded when the application is registered. Reroute is executed immediately after the hashchange/popState event is triggered. In other words, reroute is executed first after the hashchange/popState event is triggered. The subsequent registered Hashchange and popState events are executed in batches only after the uninstalled application is uninstalled. In fact, the offload here does not have to be true, because it is possible that the route changes without switching microapplications. Routing events registered with the current microapplication can then be triggered. If the current micro-application needs to be switched, triggering the registered micro-application is equivalent to emptying the events saved in the array. Similarly, when registering a microapplication, listen events stored in the array should also be triggered after the microapplication is loaded.
Custom events
Much of the event logic that defines the event, as shown below, is omitted from code fragment 7.
window.dispatchEvent(
new CustomEvent(
appsThatChanged.length === 0
? "single-spa:before-no-app-change"
: "single-spa:before-app-change",
getCustomEventDetail(true)));Copy the code
I’m not going to go into the details of each event, but I mention it here because we have an apirunAfterFirstMounted in the world, which actually listens for a custom event provided by Single-SPA. RunAfterFirstMounted then executes the function argument passed in. There are seven custom listening events in single-SPA. You can refer to the documentation of the Single-SPA API, and then go to the source code to see the corresponding logic.
Welcome to follow the wechat official account: Yang Yitao, you can get the latest news.