The preface

Sandbox this word presumably everyone should not be strange, even if strange, after reading this article is not so strange

Sandboxie, also known as a sandbox, is a virtual system program that allows you to run a browser or other program in a sandbox environment, so that changes made by the run can be removed later. It creates a sandbox-like environment in which programs run without permanent impact on the hard disk. In network security, a sandbox is a tool used to test behavior, such as untrusted files or applications, in an isolated environment

Today’s sandbox comes from the implementation of Qiankun, which is to solve the isolation problem in the micro front end solution. Currently, Qiankun can say that it is the best micro front end implementation solution. It is based on the secondary encapsulation of single-SPA, which solves many problems left by single-SPA. Runtime sandboxes are one of them

Why do we need it

Single-spa is great, but there are some issues that need to be addressed at the framework level that are not addressed, such as providing a clean, independent operating environment for each microapplication

JS global object pollution is A very common phenomenon, for example: microapplication A added A unique property on the global object window.A, this time switch to microapplication B, this time how to ensure that the window object is clean? The answer is the runtime sandbox implemented by Qiankun

conclusion

To summarize, the runtime sandbox is divided into JS sandbox and style sandbox

JS sandbox

JS sandbox, through proxy window object, record the window object on the property of the add, delete, change and check

  • The singleton pattern

    It directly represents the native Window object, records the addition, deletion, modification and check of the native Window object. When the window object is activated, the window object is restored to the state when it is about to be deactivated last time, and when it is deactivated, the window object is restored to the initial state

  • Many cases of pattern

    It represents a new object that is part of the non-configurable properties of the copied Window object, and all changes are based on the fakeWindow object to ensure that the properties of multiple instances do not affect each other

This is how the JS sandbox works, using the proxy as the global object of the microapplication. All operations are performed on the proxy object

Style sandbox

Create the element and hijack the creation action of script, link, and style tags by enhancing the createElement method in the multi-instance mode

AppendChild and insertBefore methods are enhanced that add elements, hijack the script, link, and style tags, determine whether the tags are inserted into the main or micro application depending on whether the main application is called, and pass the proxy object to the micro application. As its global object, to achieve the purpose of JS isolation

After initialization, return a free function, which is called when the microapplication is uninstalled. It is responsible for clearing the patch and caching the dynamically added styles (because all relevant DOM elements will be deleted after the microapplication is uninstalled).

The free function returns the rebuild function, which is called when the microapplication is remounted. It adds the cached dynamic style to the microapplication

Strictly speaking, this style sandbox is a bit of a failure to live up to its name. The real style isolation is provided by the strict style isolation mode and the Scoped CSS mode. Of course, if scoped CSS is enabled, dynamically added styles in the style sandbox will also be scoped

Back to the topic, what the style sandbox actually does is very simple. The script, link and style elements that are dynamically added are inserted into the main application, and the elements that belong to the micro application are inserted into the corresponding micro application, so that the micro application can be deleted together when it is unloaded.

Of course the style sandbox does two additional things:

  • Cache dynamically added styles before unmounting and then insert them into the microapplication when the microapplication is remounted
  • Pass the proxy object to the execScripts function to set it as the execution context for the microapplication

This is a summary of the run-time sandbox. For more details, read the source code analysis section below

Source code analysis

The next step is to get into the head-numbing source code analysis section. To be honest, the sandbox code is a bit difficult to run. I read the source code of Qiankun over and over again several times, github

Entry location – createSandbox

/** * generate the runtime sandbox, which is actually made up of two parts => JS sandbox (execution context), style sandbox **@param AppName Micro application name *@param The elementGetter, the getter function, <div id="__qiankun_microapp_wrapper_for_${appInstanceId}__" data-name="${appName}">${template}</div> *@param Singular is a singleton pattern *@param scopedCSS
 * @param ExcludeAssetFilter specifies part of the special dynamically loaded microapplication resources (CSS/JS) are not being held back */
export function createSandbox(
  appName: string,
  elementGetter: () => HTMLElement | ShadowRoot,
  singular: boolean,
  scopedCSS: boolean, excludeAssetFilter? : (url:string) = >boolean.) {
  /** * JS: /** * JS: /** * JS: /** * JS: /** * JS * The singleton mode directly represents the native Window object, records the addition, deletion, modification and query of the native Window object, and when the window object is activated, restores the window object to the state when it was last deactivated. * The multi-instance pattern represents a completely new object that is part of the non-configurable properties of the copied Window object. All changes are made based on the fakeWindow object. Sandbox.proxy is the global object of the microapplication. All operations are performed on this object. This is how JS sandbox.proxy works */
  let sandbox: SandBox;
  if (window.Proxy) {
    sandbox = singular ? new LegacySandbox(appName) : new ProxySandbox(appName);
  } else {
    // The browser does not support proxy, and implements the sandbox in diff mode
    sandbox = new SnapshotSandbox(appName);
  }

  /** * style sandbox ** enhances the createElement method in multiple cases, which creates elements and hijacks the script, link, and style tags * enhances the appendChild and insertBefore methods, which add elements, And hijack the add action of script, link and style tags to do some special processing => * Determine whether the tag is inserted into the main application or micro application according to whether it is called by the main application, and pass the proxy object to the micro application as its global object. To achieve the purpose of JS isolation * Free function is returned after initialization, which will be called when the microapplication is uninstalled. It is responsible for clearing the Patch and caching the dynamically added styles (because all relevant DOM elements will be deleted after the microapplication is uninstalled) * Free function is returned after execution. When the microapplication is remounted, it is called to add the cached dynamic style to the microapplication. * * Technically this style sandbox is a bit of a misname. The real style isolation is provided by the strict style isolation mode and scoped CSS mode. * Styles added dynamically in the style sandbox will also be scoped CSS; Back to the point, what the style sandbox actually does is very simple. The script, link and style * that are dynamically added are inserted into the main application, and the elements that belong to the micro application are inserted into the corresponding micro application, so that the micro application can be deleted together when the micro application is unloaded. * Of course, the style sandbox does two additional things: first, it caches the dynamic addition of styles before uninstallation, and then inserts them into the microapplication when the microapplication is remounted. Second, it passes the proxy object to the execScripts function and sets it as the microapplication's execution context */
  const bootstrappingFreers = patchAtBootstrapping(
    appName,
    elementGetter,
    sandbox,
    singular,
    scopedCSS,
    excludeAssetFilter,
  );
  // mounting freers are one-off and should be re-init at every mounting time
  Mounting Freers is one-time and should be reinitialized each time a mount is mounted
  let mountingFreers: Freer[] = [];

  let sideEffectsRebuilders: Rebuilder[] = [];

  return {
    proxy: sandbox.proxy,

    /** * The sandbox is mounted from the bootstrap state. /** * The sandbox is mounted from the bootstrap state. These are things that microapplications do when they are unmounted and want to be remounted, such as reconstructing the dynamic style of the cache */
    async mount() {
      / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- because there are context dependent (window), the following code execution order cannot be changed -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /

      / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 1. Start/resume sandbox -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
      sandbox.active();

      const sideEffectsRebuildersAtBootstrapping = sideEffectsRebuilders.slice(0, bootstrappingFreers.length);
      const sideEffectsRebuildersAtMounting = sideEffectsRebuilders.slice(bootstrappingFreers.length);

      // must rebuild the side effects which added at bootstrapping firstly to recovery to nature state
      if (sideEffectsRebuildersAtBootstrapping.length) {
        // When the microapplication mounts again, it reconstructs the cached dynamic style
        sideEffectsRebuildersAtBootstrapping.forEach(rebuild= > rebuild());
      }

      / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 2. Open the global variable patch -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
      // The render sandbox starts to hijack all global listeners. Try not to have side effects such as event listeners/timers during the application initialization phase
      mountingFreers = patchAtMounting(appName, elementGetter, sandbox, singular, scopedCSS, excludeAssetFilter);

      / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 3. The side effects of some reset initialization time -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
      // The presence of a rebuilder indicates that some side effects need to be rebuilt
      // Now only the patchHistoryListener for UMI has the rebuild action
      if (sideEffectsRebuildersAtMounting.length) {
        sideEffectsRebuildersAtMounting.forEach(rebuild= > rebuild());
      }

      // Clean up rebuilders, which will be filled back when uninstalled
      sideEffectsRebuilders = [];
    },

    /** * restore the global state to the state before the application was loaded */
    // Undo the patch made during initialization and mount; Cache some of the things the microapplication wants to do when it is mounted again (rebuild), such as rebuilding dynamic stylesheets; Deactivate microapplications
    async unmount() {
      // record the rebuilders of window side effects (event listeners or timers)
      // note that the frees of mounting phase are one-off as it will be re-init at next mounting
      // When unmounting, execute the free function, release the patch made during initialization and mounting, store all rebuild functions, and rebuild what was done through patch when the microapplication is mounted again (side effect)
      sideEffectsRebuilders = [...bootstrappingFreers, ...mountingFreers].map(free= >free()); sandbox.inactive(); }}; }Copy the code

JS sandbox

SingularProxySandbox JS sandbox

/** * Sandbox based on the singleton mode implemented by Proxy, directly operate the native Window object, and record the addition, deletion, change and check of the window object, initialize the window object when each microapplication switch; * When activated: restores the window object to the state it was in when it was last deactivated * When deactivated: restores the window object to its original state * *TODO:For compatibility use the same sandbox in Singular mode and wait until the new sandbox is stable before switching */
export default class SingularProxySandbox implements SandBox {
  // Global variables added during sandbosting
  private addedPropsMapInSandbox = new Map<PropertyKey, any> ();// Global variables updated during sandbox.key is the updated attribute and value is the updated value
  private modifiedPropsOriginalValueMapInSandbox = new Map<PropertyKey, any> ();// Continuously record the map of updated (new and modified) global variables for snapshot at any time
  private currentUpdatedPropsValueMap = new Map<PropertyKey, any> (); name:string;

  proxy: WindowProxy;

  type: SandBoxType;

  sandboxRunning = true;

  // Activate the sandbox
  active() {
    // If the sandbox is activated by deactivation ->, restore the Window object to the state it was in when it was last deactivated
    if (!this.sandboxRunning) {
      this.currentUpdatedPropsValueMap.forEach((v, p) = > setWindowProp(p, v));
    }

    // Switch the sandbox status to active
    this.sandboxRunning = true;
  }

  // Deactivate the sandbox
  inactive() {
    // Development environment, print the global properties that were changed
    if (process.env.NODE_ENV === 'development') {
      console.info(`[qiankun:sandbox] The ${this.name}modified global properties restore... `, [
        ...this.addedPropsMapInSandbox.keys(),
        ...this.modifiedPropsOriginalValueMapInSandbox.keys(),
      ]);
    }

    // restore global props to initial snapshot
    // Change the global properties that were changed back
    this.modifiedPropsOriginalValueMapInSandbox.forEach((v, p) = > setWindowProp(p, v));
    // Delete the new attribute
    this.addedPropsMapInSandbox.forEach((_, p) = > setWindowProp(p, undefined.true));

    // Switch the state of the sandbox to inactive
    this.sandboxRunning = false;
  }

  constructor(name: string) {
    this.name = name;
    this.type = SandBoxType.LegacyProxy;
    const { addedPropsMapInSandbox, modifiedPropsOriginalValueMapInSandbox, currentUpdatedPropsValueMap } = this;

    const self = this;
    const rawWindow = window;
    const fakeWindow = Object.create(null) as Window;

    const proxy = new Proxy(fakeWindow, {
      set(_: Window, p: PropertyKey, value: any) :boolean {
        if (self.sandboxRunning) {
          if(! rawWindow.hasOwnProperty(p)) {// If the attribute does not exist, add it
            addedPropsMapInSandbox.set(p, value);
          } else if(! modifiedPropsOriginalValueMapInSandbox.has(p)) {// If this property exists in the current Window object and is not recorded in the Record Map, record the initial value of this property, indicating that the existing property is changed
            const originalValue = (rawWindow as any)[p];
            modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
          }

          currentUpdatedPropsValueMap.set(p, value);
          // Set the native Window object directly, since it is a singleton, there is no other effect
          // eslint-disable-next-line no-param-reassign
          (rawWindow as any)[p] = value;

          return true;
        }

        if (process.env.NODE_ENV === 'development') {
          console.warn(`[qiankun] Set window.${p.toString()} while sandbox destroyed or inactive in ${name}! `);
        }

        // In strict-mode, the Proxy's handler.set returns false and raises TypeError, which should be ignored in the case of sandbox uninstallation
        return true;
      },

      get(_: Window, p: PropertyKey): any {
        // avoid who using window.window or window.self to escape the sandbox environment to touch the really window
        // or use window.top to check if an iframe context
        // see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13
        if (p === 'top' || p === 'parent' || p === 'window' || p === 'self') {
          return proxy;
        }

        // Get data directly from the native Window object
        const value = (rawWindow as any)[p];
        return getTargetValue(rawWindow, value);
      },

      // trap in operator
      // see https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/constants.js#L12
      has(_: Window, p: string | number | symbol): boolean {
        return p inrawWindow; }});this.proxy = proxy; }}/** * Set the key value on the window object or delete the specified property (key) *@param prop key
 * @param value value
 * @param ToDelete whether toDelete */
function setWindowProp(prop: PropertyKey, value: any, toDelete? :boolean) {
  if (value === undefined && toDelete) {
    / / remove the window [key]
    delete (window as any)[prop];
  } else if (isPropConfigurable(window, prop) && typeofprop ! = ='symbol') {
    // window[key] = value
    Object.defineProperty(window, prop, { writable: true.configurable: true });
    (window as any)[prop] = value; }}Copy the code

ProxySandbox Multiple examples of JS sandbox

// Record the number of activated sandboxes
let activeSandboxCount = 0;

The fakeWindow object is represented by the Proxy. All changes are made to the fakeWindow object, which is different from singleton (which is important). * this ensures that the properties of each ProxySandbox instance do not affect each other */
export default class ProxySandbox implements SandBox {
  /** Change record of window value */
  private updatedValueSet = new Set<PropertyKey>();

  name: string;

  type: SandBoxType;

  proxy: WindowProxy;

  sandboxRunning = true;

  / / activation
  active() {
    // Number of active sandboxes + 1
    if (!this.sandboxRunning) activeSandboxCount++;
    this.sandboxRunning = true;
  }

  / / the deactivation
  inactive() {
    if (process.env.NODE_ENV === 'development') {
      console.info(`[qiankun:sandbox] The ${this.name}modified global properties restore... `, [
        ...this.updatedValueSet.keys(),
      ]);
    }

    // The number of activated sandboxes is -1
    clearSystemJsProps(this.proxy, --activeSandboxCount === 0);

    this.sandboxRunning = false;
  }

  constructor(name: string) {
    this.name = name;
    this.type = SandBoxType.Proxy;
    const { updatedValueSet } = this;

    const self = this;
    const rawWindow = window;
    // All non-configurable properties on the global object are in fakeWindow, and properties that have getter properties also exist in propertesWithGetter Map, with value set to true
    const { fakeWindow, propertiesWithGetter } = createFakeWindow(rawWindow);

    const descriptorTargetMap = new Map<PropertyKey, SymbolTarget>();
    // Check whether the global object has a specified attribute
    const hasOwnProperty = (key: PropertyKey) = > fakeWindow.hasOwnProperty(key) || rawWindow.hasOwnProperty(key);

    const proxy = new Proxy(fakeWindow, {
      set(target: FakeWindow, p: PropertyKey, value: any) :boolean {
        // If the sandbox is running, the property values are updated and the changed properties are recorded
        if (self.sandboxRunning) {
          // Set the property value
          // @ts-ignore
          target[p] = value;
          // Record the properties that were changed
          updatedValueSet.add(p);

          // Don't worry, it's related to systemJs
          interceptSystemJsProps(p, value);

          return true;
        }

        if (process.env.NODE_ENV === 'development') {
          console.warn(`[qiankun] Set window.${p.toString()} while sandbox destroyed or inactive in ${name}! `);
        }

        // In strict-mode, the Proxy's handler.set returns false and raises TypeError, which should be ignored in the case of sandbox uninstallation
        return true;
      },

      // Get the value of the execution property
      get(target: FakeWindow, p: PropertyKey): any {
        if (p === Symbol.unscopables) return unscopables;

        // avoid who using window.window or window.self to escape the sandbox environment to touch the really window
        // see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13
        if (p === 'window' || p === 'self') {
          return proxy;
        }

        if (
          p === 'top' ||
          p === 'parent' ||
          (process.env.NODE_ENV === 'test' && (p === 'mockTop' || p === 'mockSafariTop'))) {// if your master app in an iframe context, allow these props escape the sandbox
          if (rawWindow === rawWindow.parent) {
            return proxy;
          }
          return (rawWindow as any)[p];
        }

        // proxy.hasOwnProperty would invoke getter firstly, then its value represented as rawWindow.hasOwnProperty
        if (p === 'hasOwnProperty') {
          return hasOwnProperty;
        }

        // mark the symbol to document while accessing as document.createElement could know is invoked by which sandbox for dynamic append patcher
        if (p === 'document') {
          document[attachDocProxySymbol] = proxy;
          // remove the mark in next tick, thus we can identify whether it in micro app or not
          // this approach is just a workaround, it could not cover all the complex scenarios, such as the micro app runs in the same task context with master in som case
          // fixme if you have any other good ideas
          nextTick(() = > delete document[attachDocProxySymbol]);
          return document;
        }
        // All of the above are special attributes

        // Get the specific properties, if the properties have getters, the properties of the native object, otherwise the properties of the fakeWindow object (native or user-set)
        // eslint-disable-next-line no-bitwise
        const value = propertiesWithGetter.has(p) ? (rawWindow as any)[p] : (target as any)[p] || (rawWindow as any)[p];
        return getTargetValue(rawWindow, value);
      },

      // Check whether the specified attribute exists
      // see https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/constants.js#L12
      has(target: FakeWindow, p: string | number | symbol): boolean {
        return p in unscopables || p in target || p in rawWindow;
      },

      getOwnPropertyDescriptor(target: FakeWindow, p: string | number | symbol): PropertyDescriptor | undefined {
        /* as the descriptor of top/self/window/mockTop in raw window are configurable but not in proxy target, we need to get it from target to avoid TypeError see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor > A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object. */
        if (target.hasOwnProperty(p)) {
          const descriptor = Object.getOwnPropertyDescriptor(target, p);
          descriptorTargetMap.set(p, 'target');
          return descriptor;
        }

        if (rawWindow.hasOwnProperty(p)) {
          const descriptor = Object.getOwnPropertyDescriptor(rawWindow, p);
          descriptorTargetMap.set(p, 'rawWindow');
          // A property cannot be reported as non-configurable, if it does not exists as an own property of the target object
          if(descriptor && ! descriptor.configurable) { descriptor.configurable =true;
          }
          return descriptor;
        }

        return undefined;
      },

      // trap to support iterator with sandbox
      ownKeys(target: FakeWindow): PropertyKey[] {
        return uniq(Reflect.ownKeys(rawWindow).concat(Reflect.ownKeys(target)));
      },

      defineProperty(target: Window, p: PropertyKey, attributes: PropertyDescriptor): boolean {
        const from = descriptorTargetMap.get(p);
        /* Descriptor must be defined to native window while it comes from native window via Object.getOwnPropertyDescriptor(window, p), otherwise it would cause a TypeError with illegal invocation. */
        switch (from) {
          case 'rawWindow':
            return Reflect.defineProperty(rawWindow, p, attributes);
          default:
            return Reflect.defineProperty(target, p, attributes);
        }
      },

      deleteProperty(target: FakeWindow, p: string | number | symbol): boolean {
        if (target.hasOwnProperty(p)) {
          // @ts-ignore
          delete target[p];
          updatedValueSet.delete(p);

          return true;
        }

        return true; }});this.proxy = proxy; }}Copy the code

createFakeWindow

/** * copy all non-configurable properties from the global object to the fakeWindow object, change the property descriptors of these properties to configurable and freeze * start propertiesWithGetter properties and store them in the propertiesWithGetter map *@param Global Global object => window */
function createFakeWindow(global: Window) {
  // Record the getter property on the window object. Native: window, document, location, top. Object. GetOwnPropertyDescriptor (Windows, 'window') = > {set: undefined, enumerable: true, configurable: false, a get: ƒ}
  // propertiesWithGetter = {"window" => true, "document" => true, "location" => true, "top" => true, "__VUE_DEVTOOLS_GLOBAL_HOOK__" => true}
  const propertiesWithGetter = new Map<PropertyKey, boolean> ();// Store all non-configurable properties and values in the Window object
  const fakeWindow = {} as FakeWindow;

  /* copy the non-configurable property of global to fakeWindow see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor > A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object. */
  Object.getOwnPropertyNames(global)
    // Iterate over all the non-configurable properties of the window object
    .filter(p= > {
      const descriptor = Object.getOwnPropertyDescriptor(global, p);
      return! descriptor? .configurable; }) .forEach(p= > {
      // Get the attribute descriptor
      const descriptor = Object.getOwnPropertyDescriptor(global, p);
      if (descriptor) {
        // Get its get attribute
        const hasGetter = Object.prototype.hasOwnProperty.call(descriptor, 'get');

        /* make top/self/window property configurable and writable, otherwise it will cause TypeError while get trap return. see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get > The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable data property. */
        if (
          p === 'top' ||
          p === 'parent' ||
          p === 'self' ||
          p === 'window' ||
          (process.env.NODE_ENV === 'test' && (p === 'mockTop' || p === 'mockSafariTop'))) {// Change the properties of top, parent, self, window from unconfigurable to configurable
          descriptor.configurable = true;
          /* The descriptor of window.window/window.top/window.self in Safari/FF are accessor descriptors, we need to avoid adding a data descriptor while it was Example: Safari/FF: Object.getOwnPropertyDescriptor(window, 'top') -> {get: function, set: undefined, enumerable: true, configurable: false} Chrome: Object.getOwnPropertyDescriptor(window, 'top') -> {value: Window, writable: false, enumerable: true, configurable: false} */
          if(! hasGetter) {// If there is no getter for these properties, then the writeable property is used to set them to writeable
            descriptor.writable = true; }}// If there is a getter, the propertiesWithGetter map is stored with that property as the key and true as the value
        if (hasGetter) propertiesWithGetter.set(p, true);

        // Set the property and description to the fakeWindow object and freeze the property descriptor, otherwise it may be changed, such as zone.js
        // freeze the descriptor to avoid being modified by zone.js
        // see https://github.com/angular/zone.js/blob/a5fe09b0fac27ac5df1fa746042f96f05ccb6a00/lib/browser/define-property.ts#L71
        rawObjectDefineProperty(fakeWindow, p, Object.freeze(descriptor)); }});return {
    fakeWindow,
    propertiesWithGetter,
  };
}
            
Copy the code

SnapshotSandbox

function iter(obj: object, callbackFn: (prop: any) = >void) {
  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const prop in obj) {
    if(obj.hasOwnProperty(prop)) { callbackFn(prop); }}}/** * Diff-based sandbox for earlier versions of browsers that do not support Proxy */
export default class SnapshotSandbox implements SandBox {
  proxy: WindowProxy;

  name: string;

  type: SandBoxType;

  sandboxRunning = true;

  privatewindowSnapshot! : Window;private modifyPropsMap: Record<any.any> = {};

  constructor(name: string) {
    this.name = name;
    this.proxy = window;
    this.type = SandBoxType.Snapshot;
  }

  active() {
    // Record the current snapshot
    this.windowSnapshot = {} as Window;
    iter(window.prop= > {
      this.windowSnapshot[prop] = window[prop];
    });

    // Restore the previous changes
    Object.keys(this.modifyPropsMap).forEach((p: any) = > {
      window[p] = this.modifyPropsMap[p];
    });

    this.sandboxRunning = true;
  }

  inactive() {
    this.modifyPropsMap = {};

    iter(window.prop= > {
      if (window[prop] ! = =this.windowSnapshot[prop]) {
        // Record the changes and restore the environment
        this.modifyPropsMap[prop] = window[prop];
        window[prop] = this.windowSnapshot[prop]; }});if (process.env.NODE_ENV === 'development') {
      console.info(`[qiankun:sandbox] The ${this.name}origin window restore... `.Object.keys(this.modifyPropsMap));
    }

    this.sandboxRunning = false; }}Copy the code

Style sandbox

patchAtBootstrapping

/** * Add a patch * to createElement, appendChild, insertBefore during initialization@param appName 
 * @param elementGetter 
 * @param sandbox 
 * @param singular 
 * @param scopedCSS 
 * @param excludeAssetFilter 
 */
export function patchAtBootstrapping(
  appName: string,
  elementGetter: () => HTMLElement | ShadowRoot,
  sandbox: SandBox,
  singular: boolean,
  scopedCSS: boolean, excludeAssetFilter? :Function.) :Freer[] {
  // Base patch: add createElement, appendChild, insertBefore
  const basePatchers = [
    () = > patchDynamicAppend(appName, elementGetter, sandbox.proxy, false, singular, scopedCSS, excludeAssetFilter),
  ];

  // Each sandbox requires a basic patch
  const patchersInSandbox = {
    [SandBoxType.LegacyProxy]: basePatchers,
    [SandBoxType.Proxy]: basePatchers,
    [SandBoxType.Snapshot]: basePatchers,
  };

  // Return an array whose elements are the results of patch execution => free
  returnpatchersInSandbox[sandbox.type]? .map(patch= > patch());
}

Copy the code

patch

AppendChild (); insertBefore (); /** * add (); /** add (); And hijack the add action of script, link and style tags to do some special processing => * Determine whether the tag is inserted into the main application or micro application according to whether it is called by the main application, and pass the proxy object to the micro application as its global object. * After initialization, return the free function, which is responsible for clearing the Patch and caching the dynamically added style (because all relevant DOM elements will be deleted after the microapplication is uninstalled) * After the free function is executed, return the rebuild function. * * Just hijack dynamic head append, that could avoid accidentally hijacking the insertion of elements except in head. * Such a case: ReactDOM.createPortal(<style>.test{color:blue}</style>, container), * this could made we append the style element into app wrapper but it will cause an error while the react portal unmounting, as ReactDOM could not find the style in body children list. *@param AppName Micro application name *@param AppWrapperGetter the getter function, <div id="__qiankun_microapp_wrapper_for_${appInstanceId}__" data-name="${appName}">${template}</div> *@param Proxy Window Proxy *@param Mounting Whether to mount *@param Singular is a singleton *@param Scoped CSS Specifies whether to deprecate scopedCSS *@param ExcludeAssetFilter specifies part of the special dynamically loaded microapplication resources (CSS/JS) are not being held back */
export default function patch(
  appName: string,
  appWrapperGetter: () => HTMLElement | ShadowRoot,
  proxy: Window,
  mounting = true,
  singular = true,
  scopedCSS = false, excludeAssetFilter? : CallableFunction,) :Freer {
  // A dynamic style sheet that stores all dynamically added styles
  let dynamicStyleSheetElements: Array<HTMLLinkElement | HTMLStyleElement> = [];

  // Add the createElement method to the multi-instance mode so that it can create elements, but also hijack the script, link, and style elements
  const unpatchDocumentCreate = patchDocumentCreateElement(
    appName,
    appWrapperGetter,
    singular,
    proxy,
    dynamicStyleSheetElements,
  );

  AppendChild, insertBefore, removeChild; AppendChild and insertBefore can handle scripts, styles, and links in addition to their own work
  RemoveChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild removeChild
  const unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions(
    appName,
    appWrapperGetter,
    proxy,
    singular,
    scopedCSS,
    dynamicStyleSheetElements,
    excludeAssetFilter,
  );

  // Record the number of initialization times
  if(! mounting) bootstrappingPatchCount++;// Record the number of mounts
  if (mounting) mountingPatchCount++;

  // After initialization, return the free function, which is responsible for clearing the patch and caching the dynamically added style. Return the rebuild function. The rebuild function adds the cached dynamic style to the micro application when the micro application is remounted
  return function free() {
    // bootstrap patch just called once but its freer will be called multiple times
    if(! mounting && bootstrappingPatchCount ! = =0) bootstrappingPatchCount--;
    if (mounting) mountingPatchCount--;

    // Check whether all microapplications have been uninstalled
    const allMicroAppUnmounted = mountingPatchCount === 0 && bootstrappingPatchCount === 0;
    // Remove the patch and release the overwrite prototype after all the micro apps are unmounted
    unpatchDynamicAppendPrototypeFunctions(allMicroAppUnmounted);
    unpatchDocumentCreate(allMicroAppUnmounted);

    // Since the microapplication is uninstalled and the dynamically added styles are removed, the dynamically added styles are cached here and can be used when the microapplication is unmounted again
    dynamicStyleSheetElements.forEach(stylesheetElement= > {
      if (stylesheetElement instanceof HTMLStyleElement && isStyledComponentsLike(stylesheetElement)) {
        if (stylesheetElement.sheet) {
          // record the original css rules of the style element for restore
          setCachedRules(stylesheetElement, (stylesheetElement.sheet asCSSStyleSheet).cssRules); }}});// Return a rebuild function to be called when the microapplication is remounted
    return function rebuild() {
      // Iterate over the dynamic stylesheet
      dynamicStyleSheetElements.forEach(stylesheetElement= > {
        // Add a style node to the microapplication container
        document.head.appendChild.call(appWrapperGetter(), stylesheetElement);

        // Add the style content from the previous cache to the style node
        if (stylesheetElement instanceof HTMLStyleElement && isStyledComponentsLike(stylesheetElement)) {
          const cssRules = getCachedRules(stylesheetElement);
          if (cssRules) {
            // eslint-disable-next-line no-plusplus
            for (let i = 0; i < cssRules.length; i++) {
              const cssRule = cssRules[i];
              (stylesheetElement.sheet asCSSStyleSheet).insertRule(cssRule.cssText); }}}});// As the hijacker will be invoked every mounting phase, we could release the cache for gc after rebuilding
      if(mounting) { dynamicStyleSheetElements = []; }}; }; }Copy the code

patchDocumentCreateElement

/** * createElement (); /** * createElement (); /** * createElement ()@param AppName Micro application name *@param appWrapperGetter 
 * @param singular 
 * @param proxy 
 * @param dynamicStyleSheetElements 
 */
function patchDocumentCreateElement(
  appName: string,
  appWrapperGetter: () => HTMLElement | ShadowRoot,
  singular: boolean,
  proxy: Window,
  dynamicStyleSheetElements: HTMLStyleElement[],
) {
  // If it is a singleton, return it directly
  if (singular) {
    return noop;
  }

  // Use the runtime proxy as the key to store some information about the microapplication, such as name, proxy, microapplication template, custom style sheet, etc
  proxyContainerInfoMapper.set(proxy, { appName, proxy, appWrapperGetter, dynamicStyleSheetElements, singular });

  // The first microapplication initializes by executing this section, enhancing the createElement method so that it can hijack the creation of script, link, and style tags in addition to creating elements
  if (Document.prototype.createElement === rawDocumentCreateElement) {
    Document.prototype.createElement = function createElement<K extends keyof HTMLElementTagNameMap> (
      this: Document, tagName: K, options? : ElementCreationOptions,) :HTMLElement {
      // Create the element
      const element = rawDocumentCreateElement.call(this, tagName, options);
      // Hijack script, link and style tags
      if (isHijackingTag(tagName)) {
        / / this seems futile, because didn't find any place to perform Settings, proxyContainerInfoMapper. Set (this [attachDocProxySysbol])
        / / get the things of value, and then add the value to the element object, is the key to attachElementContainerSymbol
        const proxyContainerInfo = proxyContainerInfoMapper.get(this[attachDocProxySymbol]);
        if (proxyContainerInfo) {
          Object.defineProperty(element, attachElementContainerSymbol, {
            value: proxyContainerInfo,
            enumerable: false}); }}// Return the created element
      return element;
    };
  }

  // Subsequent microapplication initializations return this function directly and restore the createElement method
  return function unpatch(recoverPrototype: boolean) {
    proxyContainerInfoMapper.delete(proxy);
    if(recoverPrototype) { Document.prototype.createElement = rawDocumentCreateElement; }}; }Copy the code

patchTHMLDynamicAppendPrototypeFunctions

AppendChild, insertBefore, and removeChild methods are enhanced, and the unpatch method is returned
function patchHTMLDynamicAppendPrototypeFunctions(
  appName: string,
  appWrapperGetter: () => HTMLElement | ShadowRoot,
  proxy: Window,
  singular = true,
  scopedCSS = false, dynamicStyleSheetElements: HTMLStyleElement[], excludeAssetFilter? : CallableFunction,) {
  // Just overwrite it while it have not been overwrite
  if (
    HTMLHeadElement.prototype.appendChild === rawHeadAppendChild &&
    HTMLBodyElement.prototype.appendChild === rawBodyAppendChild &&
    HTMLHeadElement.prototype.insertBefore === rawHeadInsertBefore
  ) {
    // Augment the appendChild method
    HTMLHeadElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({
      rawDOMAppendOrInsertBefore: rawHeadAppendChild,
      appName,
      appWrapperGetter,
      proxy,
      singular,
      dynamicStyleSheetElements,
      scopedCSS,
      excludeAssetFilter,
    }) as typeof rawHeadAppendChild;
    HTMLBodyElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({
      rawDOMAppendOrInsertBefore: rawBodyAppendChild,
      appName,
      appWrapperGetter,
      proxy,
      singular,
      dynamicStyleSheetElements,
      scopedCSS,
      excludeAssetFilter,
    }) as typeof rawBodyAppendChild;

    HTMLHeadElement.prototype.insertBefore = getOverwrittenAppendChildOrInsertBefore({
      rawDOMAppendOrInsertBefore: rawHeadInsertBefore as any,
      appName,
      appWrapperGetter,
      proxy,
      singular,
      dynamicStyleSheetElements,
      scopedCSS,
      excludeAssetFilter,
    }) as typeof rawHeadInsertBefore;
  }

  // Just overwrite it while it have not been overwrite
  if (
    HTMLHeadElement.prototype.removeChild === rawHeadRemoveChild &&
    HTMLBodyElement.prototype.removeChild === rawBodyRemoveChild
  ) {
    HTMLHeadElement.prototype.removeChild = getNewRemoveChild({
      appWrapperGetter,
      headOrBodyRemoveChild: rawHeadRemoveChild,
    });
    HTMLBodyElement.prototype.removeChild = getNewRemoveChild({
      appWrapperGetter,
      headOrBodyRemoveChild: rawBodyRemoveChild,
    });
  }

  return function unpatch(recoverPrototype: boolean) {
    if(recoverPrototype) { HTMLHeadElement.prototype.appendChild = rawHeadAppendChild; HTMLHeadElement.prototype.removeChild = rawHeadRemoveChild; HTMLBodyElement.prototype.appendChild = rawBodyAppendChild; HTMLBodyElement.prototype.removeChild = rawBodyRemoveChild; HTMLHeadElement.prototype.insertBefore = rawHeadInsertBefore; }}; }Copy the code

getOverwrittenAppendChildOrInsertBefore

AppendChild and insertBefore methods are enhanced to have some logic in addition to adding elements, such as: * Hijack the addition of script tags, enabling remote loading of scripts and setting the execution context of scripts *@param opts 
 */
function getOverwrittenAppendChildOrInsertBefore(opts: {
  appName: string;
  proxy: WindowProxy;
  singular: boolean;
  dynamicStyleSheetElements: HTMLStyleElement[];
  appWrapperGetter: CallableFunction;
  rawDOMAppendOrInsertBefore: <T extendsNode>(newChild: T, refChild? : Node |null) => T;
  scopedCSS: boolean; excludeAssetFilter? : CallableFunction; }) {
  return function appendChildOrInsertBefore<T extends Node> (
    this: HTMLHeadElement | HTMLBodyElement, newChild: T, refChild? : Node |null.) {
    // The element to insert
    let element = newChild as any;
    // The original method
    const { rawDOMAppendOrInsertBefore } = opts;
    if (element.tagName) {
      // Parse the parameters
      // eslint-disable-next-line prefer-const
      let { appName, appWrapperGetter, proxy, singular, dynamicStyleSheetElements } = opts;
      const { scopedCSS, excludeAssetFilter } = opts;

      // The multi-example pattern will go through a section of logic
      const storedContainerInfo = element[attachElementContainerSymbol];
      if (storedContainerInfo) {
        // eslint-disable-next-line prefer-destructuring
        appName = storedContainerInfo.appName;
        // eslint-disable-next-line prefer-destructuring
        singular = storedContainerInfo.singular;
        // eslint-disable-next-line prefer-destructuring
        appWrapperGetter = storedContainerInfo.appWrapperGetter;
        // eslint-disable-next-line prefer-destructuring
        dynamicStyleSheetElements = storedContainerInfo.dynamicStyleSheetElements;
        // eslint-disable-next-line prefer-destructuring
        proxy = storedContainerInfo.proxy;
      }

      const invokedByMicroApp = singular
        ? // check if the currently specified application is active
          // While we switch page from qiankun app to a normal react routing page, the normal one may load stylesheet dynamically while page rendering,
          // but the url change listener must to wait until the current call stack is flushed.
          // This scenario may cause we record the stylesheet from react routing page dynamic injection,
          // and remove them after the url change triggered and qiankun app is unmouting
          // see https://github.com/ReactTraining/history/blob/master/modules/createHashHistory.js#L222-L230
          checkActivityFunctions(window.location).some(name= > name === appName)
        : // have storedContainerInfo means it invoked by a micro app in multiply mode!!!!! storedContainerInfo;switch (element.tagName) {
        / / link and style
        case LINK_TAG_NAME:
        case STYLE_TAG_NAME: {
          // predicate, newChild is either style or link tag
          const stylesheetElement: HTMLLinkElement | HTMLStyleElement = newChild as any;
          / / href attribute
          const { href } = stylesheetElement as HTMLLinkElement;
          if(! invokedByMicroApp || (excludeAssetFilter && href && excludeAssetFilter(href))) {// The action to create the element is not called by the microapplication, or it is a special link tag that you don't want to be hijacked by qiankun
            // Create it under the main application
            return rawDOMAppendOrInsertBefore.call(this, element, refChild) as T;
          }

          // The microapplication container DOM
          const mountDOM = appWrapperGetter();

          // scoped css
          if (scopedCSS) {
            css.process(mountDOM, stylesheetElement, appName);
          }

          // Store the element in the stylesheet
          // eslint-disable-next-line no-shadow
          dynamicStyleSheetElements.push(stylesheetElement);
          // Reference element
          const referenceNode = mountDOM.contains(refChild) ? refChild : null;
          // Create this element in the microapplication space so that it can be removed together when the microapplication is uninstalled
          return rawDOMAppendOrInsertBefore.call(mountDOM, stylesheetElement, referenceNode);
        }

        / / script tags
        case SCRIPT_TAG_NAME: {
          // Links and text
          const { src, text } = element as HTMLScriptElement;
          // some script like jsonp maybe not support cors which should't use execScripts
          if(! invokedByMicroApp || (excludeAssetFilter && src && excludeAssetFilter(src))) {// In the same way, create the tag under the main application
            return rawDOMAppendOrInsertBefore.call(this, element, refChild) as T;
          }

          // The microapplication container DOM
          const mountDOM = appWrapperGetter();
          // Fetch method provided by the user
          const { fetch } = frameworkConfiguration;
          // Reference node
          const referenceNode = mountDOM.contains(refChild) ? refChild : null;

          // If SRC exists, it is an external script
          if (src) {
            // Perform a remote load and set the proxy as a global object of the script to achieve JS isolation
            execScripts(null, [src], proxy, {
              fetch,
              strictGlobal: !singular,
              beforeExec: () = > {
                Object.defineProperty(document.'currentScript', {
                  get(): any {
                    return element;
                  },
                  configurable: true}); },success: () = > {
                // we need to invoke the onload event manually to notify the event listener that the script was completed
                // here are the two typical ways of dynamic script loading
                // 1. element.onload callback way, which webpack and loadjs used, see https://github.com/muicss/loadjs/blob/master/src/loadjs.js#L138
                // 2. addEventListener way, which toast-loader used, see https://github.com/pyrsmk/toast/blob/master/src/Toast.ts#L64
                const loadEvent = new CustomEvent('load');
                if (isFunction(element.onload)) {
                  element.onload(patchCustomEvent(loadEvent, () = > element));
                } else {
                  element.dispatchEvent(loadEvent);
                }

                element = null;
              },
              error: () = > {
                const errorEvent = new CustomEvent('error');
                if (isFunction(element.onerror)) {
                  element.onerror(patchCustomEvent(errorEvent, () = > element));
                } else {
                  element.dispatchEvent(errorEvent);
                }

                element = null; }});// Create a comment element indicating that the script tag was hijacked by qiankun
            const dynamicScriptCommentElement = document.createComment(`dynamic script ${src} replaced by qiankun`);
            return rawDOMAppendOrInsertBefore.call(mountDOM, dynamicScriptCommentElement, referenceNode);
          }

          // This script is an inline script
          execScripts(null[`<script>${text}</script>`], proxy, {
            strictGlobal: !singular,
            success: element.onload,
            error: element.onerror,
          });
          // Create a comment element indicating that the script tag was hijacked by qiankun
          const dynamicInlineScriptCommentElement = document.createComment('dynamic inline script replaced by qiankun');
          return rawDOMAppendOrInsertBefore.call(mountDOM, dynamicInlineScriptCommentElement, referenceNode);
        }

        default:
          break; }}// Call the original method and insert the element
    return rawDOMAppendOrInsertBefore.call(this, element, refChild);
  };
}

Copy the code

getNewRemoveChild

/** * Enhance removeChild so that it can determine whether to remove script, style, or link elements from the main application or from the micro application * if they are hijacked, remove them from the micro application, otherwise remove them from the main application *@param opts 
 */
function getNewRemoveChild(opts: {
  appWrapperGetter: CallableFunction;
  headOrBodyRemoveChild: typeof HTMLElement.prototype.removeChild;
}) {
  return function removeChild<T extends Node> (this: HTMLHeadElement | HTMLBodyElement, child: T) {
    // The original removeChild
    const { headOrBodyRemoveChild } = opts;
    try {
      const { tagName } = child as any;
      // If the element to be removed is one of script, link, or style
      if (isHijackingTag(tagName)) {
        // Microapplication container space
        let { appWrapperGetter } = opts;

        // storedContainerInfo contains some information about the microapplication, but storedContainerInfo should always be undefeind because the code setting the location never seems to be executed
        const storedContainerInfo = (child as any)[attachElementContainerSymbol];
        if (storedContainerInfo) {
          // eslint-disable-next-line prefer-destructuring
          // A micro application wrapper element, also known as a micro application template
          appWrapperGetter = storedContainerInfo.appWrapperGetter;
        }

        // Remove this element from the microapplication container space
        // container may had been removed while app unmounting if the removeChild action was async
        const container = appWrapperGetter();
        if (container.contains(child)) {
          return rawRemoveChild.call(container, child) asT; }}}catch (e) {
      console.warn(e);
    }

    // Remove elements from the main application
    return headOrBodyRemoveChild.call(this, child) as T;
  };
}

Copy the code

patchAtMounting

It is called during the mounting phase of the microapplication and is mainly responsible for patching each global variable (method)

export function patchAtMounting(
  appName: string,
  elementGetter: () => HTMLElement | ShadowRoot,
  sandbox: SandBox,
  singular: boolean,
  scopedCSS: boolean, excludeAssetFilter? :Function.) :Freer[] {
  const basePatchers = [
    // Timer patch
    () = > patchInterval(sandbox.proxy),
    // Event listener patch
    () = > patchWindowListener(sandbox.proxy),
    // fix umi bug
    () = > patchHistoryListener(),
    // The patch used during initialization
    () = > patchDynamicAppend(appName, elementGetter, sandbox.proxy, true, singular, scopedCSS, excludeAssetFilter),
  ];

  const patchersInSandbox = {
    [SandBoxType.LegacyProxy]: [...basePatchers],
    [SandBoxType.Proxy]: [...basePatchers],
    [SandBoxType.Snapshot]: basePatchers,
  };

  returnpatchersInSandbox[sandbox.type]? .map(patch= > patch());
}

Copy the code

patch => patchInterval

/** * Timer patch. When the timer is set, the timer ID is automatically recorded; when the timer is cleared, the cleared timer ID is automatically deleted; when the patch is released, all uncleared timers are automatically cleared and the timer is restored@param global = windowProxy
 */
export default function patch(global: Window) {
  let intervals: number[] = [];

  // Clear the timer, and clear the timer ID that has been cleared from the intervals
  global.clearInterval = (intervalId: number) = > {
    intervals = intervals.filter(id= >id ! == intervalId);return rawWindowClearInterval(intervalId);
  };

  // Set the timer and record the timer ID
  global.setInterval = (handler: Function, timeout? :number. args:any[]) = > {
    constintervalId = rawWindowInterval(handler, timeout, ... args); intervals = [...intervals, intervalId];return intervalId;
  };

  // Clear all timers and restore the timer method
  return function free() {
    intervals.forEach(id= > global.clearInterval(id));
    global.setInterval = rawWindowInterval;
    global.clearInterval = rawWindowClearInterval;

    return noop;
  };
}

Copy the code

patch => patchWindowListener

/** * listener patch, add the callback function that automatically records the event when the event listener is added, automatically delete the callback function when the event listener is removed, automatically delete all the event listeners when the patch is released, and restore the listening function *@param global windowProxy
 */
export default function patch(global: WindowProxy) {
  // The callback function that records each event
  const listenerMap = new Map<string, EventListenerOrEventListenerObject[]>();

  // Set the listener
  global.addEventListener = (
    type: string, listener: EventListenerOrEventListenerObject, options? :boolean | AddEventListenerOptions,
  ) = > {
    // Retrieve the existing callback function for this event from listenerMap
    const listeners = listenerMap.get(type) | | [];// Save all callbacks for this event
    listenerMap.set(type, [...listeners, listener]);
    // Set the listener
    return rawAddEventListener.call(window.type, listener, options);
  };

  // Remove the listener
  global.removeEventListener = (
    type: string, listener: EventListenerOrEventListenerObject, options? :boolean | AddEventListenerOptions,
  ) = > {
    // Remove the specified callback function for this event from listenerMap
    const storedTypeListeners = listenerMap.get(type);
    if(storedTypeListeners && storedTypeListeners.length && storedTypeListeners.indexOf(listener) ! = = -1) {
      storedTypeListeners.splice(storedTypeListeners.indexOf(listener), 1);
    }
    // Remove event listener
    return rawRemoveEventListener.call(window.type, listener, options);
  };

  // Release patch and remove all event listeners
  return function free() {
    // Remove all event listeners
    listenerMap.forEach((listeners, type) = >
      [...listeners].forEach(listener= > global.removeEventListener(type, listener)),
    );
    // Restore the listener function
    global.addEventListener = rawAddEventListener;
    global.removeEventListener = rawRemoveEventListener;

    return noop;
  };
}

Copy the code

portal

  • Micro front-end framework qiankun from the introduction to source analysis, detailed interpretation of the sandbox implementation of Qiankun 2.x version
  • HTML Entry source analysis, a detailed interpretation of the PRINCIPLE of HTML Entry and the application in Qiankun
  • Single – SPA micro front end framework from beginner to master
  • github