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 ~