One, foreword
In the previous article “Micro Front End Exploration”, we analyzed some source code and principle of single-SPA and Qiankun. In this paper, we made a simple analysis of icestark, another micro front end framework in the community, to help students who want to explore the micro front end have a sensory understanding.
Second, source directory
Icestark’s main source directory structure is shown below, including packages and SRC. We will not go through the code line by line. If you are interested in a particular area, you can clone the code base on Github. The source code is still very simple to understand.
3. Sorting out important codes
1. Sub-application state management
When we learned single-SPA, we concluded that single-SPA is a state machine, which is responsible for managing the state of each sub-application. Therefore, icestark must have state management for this sub-application. The relevant implementation is in the SRC /apps.ts file, which includes the following main processes/methods:
- RegisterMicroApp: a method to register child applications and add them to a global microApps array variable;
- CreateMicroApp: the method of creating a subapplication, including loading the resources of the subapplication.
- MountMicroApp: a method for mounting sub-applications to containers.
- UnmountMicroApp: unmounts child applications from containers.
- UnloadMicroApp: Unload subapplications, including removing subapplications’ resources.
- RemoveMicroApp: Method of removing a child application from the global microApps array.
The implementation idea of the above method is relatively simple. Define a global microApps array variable to save sub-applications, and then each sub-application has a corresponding status variable to represent the state of the sub-application. Then, the next operation will be carried out according to the current state. Let’s take a look at the most “complex” methods of createMicroApp. We’ve annotated the code, which is relatively simple, so WE won’t go into details.
export async function createMicroApp(app: string | AppConfig, appLifecyle? : AppLifecylceOptions) {
const appConfig = getAppConfigForLoad(app, appLifecyle);
const appName = appConfig && appConfig.name;
if (appConfig && appName) {
if (appConfig.status === NOT_LOADED || appConfig.status === LOAD_ERROR ) {
// If the current state of the child application is not loaded or failed to load, execute the following logic
if (appConfig.title) document.title = appConfig.title;
// Update the status of the child application
updateAppConfig(appName, { status: LOADING_ASSETS });
let lifeCycle: ModuleLifeCycle = {};
try {
// Load subapplication resources
lifeCycle = await loadAppModule(appConfig);
// in case of app status modified by unload event
if (getAppStatus(appName) === LOADING_ASSETS) {
// Update the subapplication configurationupdateAppConfig(appName, { ... lifeCycle,status: NOT_MOUNTED }); }}catch (err){
// Error, update subapplication configuration
updateAppConfig(appName, { status: LOAD_ERROR });
}
if (lifeCycle.mount) {
// Mount subapplications
awaitmountMicroApp(appConfig.name); }}else if (appConfig.status === UNMOUNTED) {
// Perform the following logic if the current child application is uninstalled
if(! appConfig.cached) {// Load js/ CSS resources
await loadAndAppendCssAssets(appConfig.appAssets || { cssList: [].jsList: []});
}
// Mount it
await mountMicroApp(appConfig.name);
} else if (appConfig.status === NOT_MOUNTED) {
// If the current child application is not mounted, mount it
await mountMicroApp(appConfig.name);
} else {
console.info(`[icestark] current status of app ${appName} is ${appConfig.status}`);
}
// Returns information about the created child application
return getAppConfig(appName);
} else {
console.error(`[icestark] fail to get app config of ${appName}`);
}
return null;
}
Copy the code
2. Route hijacking
We all know the react, Vue, Angular, and other single-application route hijacking implementations: The history route listens for popState events, the hash route listens for hashchange routes, and the icestark route hijacking does the same thing. The location of the code is in the SRC /start.js file, and the relevant source code is shown below
const hijackHistory = (): void= > {
// Listen for the corresponding route event, urlChange is the event callback function
window.addEventListener('popstate', urlChange, false);
window.addEventListener('hashchange', urlChange, false);
};
Copy the code
3. Sandbox isolation
In the micro-front-end container, there is a situation where several sub-applications share a window object. If there is no isolation, there may be mutual influence among the sub-applications. Icestark enables a sandbox environment for each child application based on Proxy. The code is located in the Packages/ICestark-sandbox/SRC /index.js file. The code implementation is shown below
createProxySandbox(injection? : object) {
const { propertyAdded, originalValues, multiMode } = this;
const proxyWindow = Object.create(null) as Window;
const originalWindow = window;
const originalAddEventListener = window.addEventListener;
const originalRemoveEventListener = window.removeEventListener;
const originalSetInerval = window.setInterval;
const originalSetTimeout = window.setTimeout;
// hijack addEventListener
proxyWindow.addEventListener = (eventName, fn, ... rest) = > {
const listeners = this.eventListeners[eventName] || [];
listeners.push(fn);
return originalAddEventListener.apply(originalWindow, [eventName, fn, ...rest]);
};
// hijack removeEventListener
proxyWindow.removeEventListener = (eventName, fn, ... rest) = > {
const listeners = this.eventListeners[eventName] || [];
if (listeners.includes(fn)) {
listeners.splice(listeners.indexOf(fn), 1);
}
return originalRemoveEventListener.apply(originalWindow, [eventName, fn, ...rest]);
};
// hijack setTimeout
proxyWindow.setTimeout = (. args) = > {
consttimerId = originalSetTimeout(... args);this.timeoutIds.push(timerId);
return timerId;
};
// hijack setInterval
proxyWindow.setInterval = (. args) = > {
constintervalId = originalSetInerval(... args);this.intervalIds.push(intervalId);
return intervalId;
};
const sandbox = new Proxy(proxyWindow, {
set(target: Window, p: PropertyKey, value: any): boolean {
target[p] = value;
},
get(target: Window, p: PropertyKey): any {
const targetValue = target[p];
if (targetValue) {
// case of addEventListener, removeEventListener, setTimeout, setInterval setted in sandbox
return targetValue;
}
},
has(target: Window, p: PropertyKey): boolean {
return p in target || p inoriginalWindow; }});this.sandbox = sandbox;
}
Copy the code
4, communication
Icestark provides event/ Store communication. It implements a simple EventEmit instance in the packages/ ICestark-data/SRC /event.js file. We’ve made some code comments that I won’t go over.
class Event implements Hooks {
eventEmitter: object;
constructor() {
this.eventEmitter = {};
}
// The event is triggered
emit(key: string, ... args) {
const keyEmitter = this.eventEmitter[key];
// Execute the callback method for event registration
keyEmitter.forEach(cb= >{ cb(... args); }); }// Event listener
on(key: string, callback: (value: any) => void) {
if (!this.eventEmitter[key]) {
this.eventEmitter[key] = [];
}
// Put the event callback method into the array
this.eventEmitter[key].push(callback);
}
// Cancel registration
off(key: string, callback? : (value: any) =>void) {
if (callback === undefined) {
this.eventEmitter[key] = undefined;
return;
}
this.eventEmitter[key] = this.eventEmitter[key].filter(cb= >cb ! == callback); }has(key: string) {
const keyEmitter = this.eventEmitter[key];
return isArray(keyEmitter) && keyEmitter.length > 0; }}Copy the code
Four,
After the above code sorting and analysis, we can see that icestark does the same thing/principle with single-SPA and Qiankun. Icestark realizes the state management of sub-applications by itself, and then realizes the auxiliary functions such as sandbox and communication.
Hard to sort out for a long time, but also hope to manually praise encouragement ~ blog github address: github.com/fengshi123/… , a summary of all the author’s blog, welcome to follow and star ~