qiankun 2.x runtime sandbox source code analysis

Posted by php new bie on Mon, 07 Feb 2022 05:09:38 +0100

brief introduction

The implementation principle of JS sandbox and style sandbox in qiankun framework is explained in detail from the source code level.

preface

The word sandbox must be familiar to everyone. Even if it is strange, it will not be so strange after reading this article

Sandboxie, also known as sandbox, is a virtual system program that allows you to run browsers or other programs in the sandbox environment, so the changes caused by running can be deleted later. It creates a sandbox like independent working environment, and the programs running in it cannot have a permanent impact on the hard disk. In network security, Sandbox refers to a tool used to test the behavior of untrusted files or applications in an isolated environment

Today's Sandbox comes from the implementation of qiankun to solve the isolation problem in the micro front-end solution. qiankun is the best micro front-end implementation solution at present. It makes secondary packaging based on single spa and solves many problems left by single spa. Runtime sandbox is one of them

Why do you need it

Although single spa is good, there are some problems that need to be solved at the framework level but have not been solved, such as providing a clean and independent running environment for each micro application

JS global object pollution is a very common phenomenon. For example, micro application a adds its own unique attribute window A. At this time, switch to micro application B. how to ensure that the window object is clean? The answer is the runtime sandbox implemented by qiankun

summary

To sum up, the runtime sandbox is divided into JS sandbox and style sandbox

JS sandbox

JS sandbox, through proxy proxy window object, records the addition, deletion, modification and query of attributes on window object

  • Singleton mode

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

  • Multi case mode

    A new object is represented. This object is a part of the copied window object and cannot be configured. All changes are based on this fakeWindow object, so as to ensure that the properties of multiple instances do not affect each other

Take this proxy as the global object of micro application, and all operations are on this proxy object, which is the principle of JS sandbox

Style sandbox

By enhancing the createElement method in multi instance mode, it is responsible for creating elements and hijacking the creation actions of script, link and style tags

Enhance the appendChild and insertBefore methods, which are responsible for adding elements, hijacking the adding actions of script, link and style tags, determining whether the tag is inserted into the main application or micro application according to whether it is called by the main application, and passing the proxy object to the micro application as its global object to achieve the purpose of JS isolation

After initialization, a free function is returned, which will be called when the micro application is unloaded, and is responsible for clearing the patch and caching the dynamically added style (because all relevant DOM elements will be deleted after the micro application is unloaded)

After the free function is executed, it returns the rebuild function, which will be called when the micro application is reloaded, and is responsible for adding the dynamic style just cached to the micro application

In fact, strictly speaking, this style sandbox is not worthy of its name. The real style isolation is provided by the strict style isolation mode and scoped css mode. Of course, if scoped css is enabled, the dynamically added styles in the style sandbox will also be processed by scoped css

Back to the point, what the style sandbox actually does is very simple, that is, insert the dynamically added script, link and style into the right position, insert the main application that belongs to the main application, and insert the micro application that belongs to the micro application into the corresponding micro application, so as to facilitate deletion when the micro application is unloaded,

Of course, the style sandbox also does two additional things:

  • Cache the dynamically added styles before uninstallation and insert them into the micro application when the micro application is re mounted
  • Pass the proxy object to the execScripts function and set it as the execution context of the micro application

The above content is a summary of the runtime sandbox. For a more detailed implementation process, you can continue to read the source code analysis section below

Source code analysis

Next, let's go to the dizzying source code analysis part. To be honest, the code of runtime sandbox is still a little difficult. When I read qiankun source code, I read this part repeatedly for several times, github

Entrance location - createSandbox

/**
 * Generate a runtime sandbox, which is actually composed of two parts = > JS sandbox (execution context) and style sandbox
 *
 * @param appName Micro application name
 * @param elementGetter getter Function, through which you can get < div id = "_qiankun_micro app_wrapper_for ${appinstanceid}_" data-name="${appName}">${template}</div>
 * @param singular Singleton mode
 * @param scopedCSS
 * @param excludeAssetFilter Specifies that some special dynamically loaded micro application resources (css/js) will not be hijacked by qiankun
 */
export function createSandbox(
  appName: string,
  elementGetter: () => HTMLElement | ShadowRoot,
  singular: boolean,
  scopedCSS: boolean,
  excludeAssetFilter?: (url: string) => boolean,
) {
  /**
   * JS Sandbox records the addition, deletion, modification and query of attributes on the window object through proxy proxy. The difference is:
   *  The singleton mode directly represents the original window object, records the addition, deletion, modification and query of the original window object, and restores the window object to the state when it was about to be deactivated last time when the window object is activated,
   * Restore the window object to its initial state when it is deactivated
   *  The multi instance mode represents a new object. This object is a part of the copied window object and cannot be configured. All changes are based on this fakeWindow object, so as to ensure multiple instances
   * The attributes do not affect each other
   * Sandbox Proxy is the global object of micro application. All operations are on this proxy object. This is the principle of JS sandbox
   */
  let sandbox: SandBox;
  if (window.Proxy) {
    sandbox = singular ? new LegacySandbox(appName) : new ProxySandbox(appName);
  } else {
    // A sandbox implemented by diff for browsers that do not support proxy
    sandbox = new SnapshotSandbox(appName);
  }

  /**
   * Style sandbox
   * 
   * The createElement method in enhanced multi instance mode is responsible for creating elements and hijacking the creation actions of script, link and style tags
   * Enhance the appendChild and insertBefore methods, which are responsible for adding elements, hijacking the addition of script, link and style tags, and doing some special processing = > 
   * The tag is inserted into the main application or micro application according to whether it is called by the main application, and the proxy object is passed to the micro application as its global object to achieve the purpose of JS isolation
   * After initialization, return the free function, which will be called when the micro application is unloaded, and is responsible for clearing the patch and caching the dynamically added style (because all relevant DOM elements will be deleted after the micro application is unloaded)
   * free After the function is executed, it returns the rebuild function, which will be called when the micro application is reloaded, and is responsible for adding the dynamic style just cached to the micro application
   * 
   * In fact, strictly speaking, this style sandbox is not worthy of its name. The real style isolation is provided by the strict style isolation mode and scoped css mode. Of course, if scoped css is enabled,
   * Styles added dynamically in the style sandbox will also be processed by scoped css; Back to the point, what the style sandbox actually does is very simple. It will dynamically add script, link and style 
   * These three elements are inserted into the right position. Those belonging to the main application are inserted into the main application, and those belonging to the micro application are inserted into the corresponding micro application, so that they can be deleted together when the micro application is unloaded,
   * Of course, the style sandbox also does two additional things: first, cache the dynamically added styles before unloading, and insert them into the micro application when the micro application is re mounted; second, pass the proxy object to execScripts
   * Function to set it as the execution context of the micro application
   */
  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 you mount
  let mountingFreers: Freer[] = [];

  let sideEffectsRebuilders: Rebuilder[] = [];

  return {
    proxy: sandbox.proxy,

    /**
     * Sandbox mount ed
     * It may be the mount entered from the bootstrap state
     * It may also wake up from unmount and enter mount again
     * mount The side effect of rebuilding (rebuild function), that is, some things that the micro application wants to do when it is unloaded and reloaded, such as rebuilding the dynamic style of the cache
     */
    async mount() {
      /* ------------------------------------------ Due to the context dependency (window), the following code execution order cannot be changed------------------------------------------ */

      /* ------------------------------------------ 1. Start / restore 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 micro application is mounted again, the dynamic style just cached is rebuilt
        sideEffectsRebuildersAtBootstrapping.forEach(rebuild => rebuild());
      }

      /* ------------------------------------------ 2. Open global variable patch------------------------------------------*/
      // When the render sandbox starts, it starts to hijack all kinds of global monitoring. Try not to have side effects such as event monitoring / timer in the application initialization stage
      mountingFreers = patchAtMounting(appName, elementGetter, sandbox, singular, scopedCSS, excludeAssetFilter);

      /* ------------------------------------------ 3. Reset some side effects during initialization------------------------------------------*/
      // The presence of a rebuild indicates that some side effects need to be rebuilt
      // Now I only see that the patchHistoryListener for umi has a rebuild operation
      if (sideEffectsRebuildersAtMounting.length) {
        sideEffectsRebuildersAtMounting.forEach(rebuild => rebuild());
      }

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

    /**
     * Restore the global state so that it can return to the state before the application is loaded
     */
    // Cancel the patch in the initialization and mount phases; Some things that cache micro applications need to do when they want to be mounted again (rebuild), such as rebuilding dynamic style sheets; Inactivated micro application
    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 uninstalling, execute the free function, release the patch hit during initialization and mounting, store all rebuild functions, and rebuild what is done through the patch when the micro application is mounted again (side effects)
      sideEffectsRebuilders = [...bootstrappingFreers, ...mountingFreers].map(free => free());

      sandbox.inactive();
    },
  };
}

JS sandbox

SingularProxySandbox single case JS sandbox
/**
 * The sandbox in the singleton mode based on Proxy implementation directly operates the original window object, records the addition, deletion, modification and query of the window object, and initializes the window object every time the micro application is switched;
 * On activation: restores the window object to the state when it was about to be deactivated last time
 * Deactivation: restore the window object to its original state
 * 
 * TODO: In order to be compatible, the sandbox is still used in singular mode. Switch after the new Sandbox is stable
 */
export default class SingularProxySandbox implements SandBox {
  // Global variables added during sandbox
  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 updated (newly added and modified) global variable map, which is used to take a snapshot at any time
  private currentUpdatedPropsValueMap = new Map<PropertyKey, any>();

  name: string;

  proxy: WindowProxy;

  type: SandBoxType;

  sandboxRunning = true;

  // Activate sandbox
  active() {
    // If the sandbox is deactivated - > activated, the window object will be restored to the state when it was last deactivated
    if (!this.sandboxRunning) {
      this.currentUpdatedPropsValueMap.forEach((v, p) => setWindowProp(p, v));
    }

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

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

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

    // Switch sandbox status 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)) {
            // Property does not exist, add
            addedPropsMapInSandbox.set(p, value);
          } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {
            // If the attribute exists in the current window object and has not been recorded in the record map, the initial value of the attribute is recorded, indicating that the existing attribute is changed
            const originalValue = (rawWindow as any)[p];
            modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
          }

          currentUpdatedPropsValueMap.set(p, value);
          // Set the native window object directly, because it is in singleton mode and will not have other effects
          // 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 handler of Proxy If set returns false, TypeError will be thrown. In the case of sandbox unloading, the error should be ignored
        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 in rawWindow;
      },
    });

    this.proxy = proxy;
  }
}

/**
 * Set the key value or delete the specified attribute (key) on the window object
 * @param prop key
 * @param value value
 * @param toDelete Delete
 */
function setWindowProp(prop: PropertyKey, value: any, toDelete?: boolean) {
  if (value === undefined && toDelete) {
    // Delete window[key]
    delete (window as any)[prop];
  } else if (isPropConfigurable(window, prop) && typeof prop !== 'symbol') {
    // window[key] = value
    Object.defineProperty(window, prop, { writable: true, configurable: true });
    (window as any)[prop] = value;
  }
}
ProxySandbox multi case JS sandbox
// Record the number of sandboxes activated
let activeSandboxCount = 0;

/**
 * Sandbox in multi instance mode based on Proxy implementation
 * proxy the fakeWindow object. All changes are based on fakeWindow, which is different from the singleton (very important),
 * This ensures that the properties of each ProxySandbox instance do not affect each other
 */
export default class ProxySandbox implements SandBox {
  /** window Value change record */
  private updatedValueSet = new Set<PropertyKey>();

  name: string;

  type: SandBoxType;

  proxy: WindowProxy;

  sandboxRunning = true;

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

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

    // Number of sandboxes activated - 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 the properties with getter properties are still in propertyswithgetter map, and value is true
    const { fakeWindow, propertiesWithGetter } = createFakeWindow(rawWindow);

    const descriptorTargetMap = new Map<PropertyKey, SymbolTarget>();
    // Judge whether the specified attribute exists in the global object
    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, update the property value and record the changed property
        if (self.sandboxRunning) {
          // Set attribute value
          // @ts-ignore
          target[p] = value;
          // Record changed properties
          updatedValueSet.add(p);

          // Never mind, 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 handler of Proxy If set returns false, TypeError will be thrown. In the case of sandbox unloading, the error should be ignored
        return true;
      },

      // Gets 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;
        }
        // The above contents are the processing of some special attributes

        // Get a specific attribute. If the attribute has a getter, it indicates the attributes of the native object. Otherwise, it is the attribute on 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);
      },

      // Determine 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;
  }
}
createFakeWindow
/**
 * Copy all non configurable attributes on the global object to the fakeWindow object, change the attribute descriptors of these attributes to configurable, and then freeze them
 * Store the property with getter property in propertiesWithGetter map
 * @param global Global object = > window
 */
function createFakeWindow(global: Window) {
  // Record the getter attribute on the window object. The native ones are window, document, location and top, such as object getOwnPropertyDescriptor(window, 'window') => {set: undefined, enumerable: true, configurable: false, 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)
    // Traverse all non configurable properties of the window object
    .filter(p => {
      const descriptor = Object.getOwnPropertyDescriptor(global, p);
      return !descriptor?.configurable;
    })
    .forEach(p => {
      // Get attribute descriptor
      const descriptor = Object.getOwnPropertyDescriptor(global, p);
      if (descriptor) {
        // Get its get property
        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 top, parent, self and window attributes from non configurable 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 these attributes do not have getter s, it means that the writeable attribute sets them to writable
            descriptor.writable = true;
          }
        }

        // If there is a getter, take the property as the key and true as the value and store it in the propertiesWithGetter map
        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,
  };
}
            
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);
    }
  }
}

/**
 * Sandbox based on diff mode is used for low version browsers that do not support Proxy
 */
export default class SnapshotSandbox implements SandBox {
  proxy: WindowProxy;

  name: string;

  type: SandBoxType;

  sandboxRunning = true;

  private windowSnapshot!: Window;

  private modifyPropsMap: Record<any, any> = {};

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

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

    // Restore 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 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] ${this.name} origin window restore...`, Object.keys(this.modifyPropsMap));
    }

    this.sandboxRunning = false;
  }
}

Style sandbox

patchAtBootstrapping
/**
 * In the initialization phase, make a patch for the createElement, appendChild and insertBefore methods
 * @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[] {
  // Basic patch, enhanced createElement, appendChild and insertBefore methods
  const basePatchers = [
    () => patchDynamicAppend(appName, elementGetter, sandbox.proxy, false, singular, scopedCSS, excludeAssetFilter),
  ];

  // Each kind of sandbox needs to lay a foundation patch
  const patchersInSandbox = {
    [SandBoxType.LegacyProxy]: basePatchers,
    [SandBoxType.Proxy]: basePatchers,
    [SandBoxType.Snapshot]: basePatchers,
  };

  // Returns an array whose elements are the execution result of patch = > free function
  return patchersInSandbox[sandbox.type]?.map(patch => patch());
}
patch
/**
 * The createElement method in enhanced multi instance mode is responsible for creating elements and hijacking the creation actions of script, link and style tags
 * Enhance the appendChild and insertBefore methods, which are responsible for adding elements, hijacking the addition of script, link and style tags, and doing 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 for the purpose of packaging JS isolation
 * After initialization, return the free function, which is responsible for clearing the patch and caching the dynamically added styles (because all relevant DOM elements will be deleted after the micro application is unloaded)
 * free After the function is executed, it returns the rebuild function, which adds the cached dynamic style to the micro application when the micro application is reloaded
 * 
 * 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 getter Function, through which you can get < div id = "_qiankun_micro app_wrapper_for ${appinstanceid}_" data-name="${appName}">${template}</div>
 * @param proxy window agent
 * @param mounting Is it the mounting stage
 * @param singular Is it a single instance
 * @param scopedCSS Discard scoped css
 * @param excludeAssetFilter Specifies that some special dynamically loaded micro application resources (css/js) will not be hijacked by qiankun
 */
export default function patch(
  appName: string,
  appWrapperGetter: () => HTMLElement | ShadowRoot,
  proxy: Window,
  mounting = true,
  singular = true,
  scopedCSS = false,
  excludeAssetFilter?: CallableFunction,
): Freer {
  // Add style sheet, dynamic storage, all
  let dynamicStyleSheetElements: Array<HTMLLinkElement | HTMLStyleElement> = [];

  // In the multi instance mode, the createElement method is enhanced so that it can not only create elements, but also hijack the creation of script, link and style elements
  const unpatchDocumentCreate = patchDocumentCreateElement(
    appName,
    appWrapperGetter,
    singular,
    proxy,
    dynamicStyleSheetElements,
  );

  // Enhance the three elements of appendChild, insertBefore and removeChild; In addition to their own work, appendChild and insertBefore can also handle script, style and link
  // The insertion of three tags can determine whether the elements are inserted into the micro application template space or the main application template space according to the situation. removeChild can also remove the elements of the main application or the three elements in the micro application according to the situation
  const unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions(
    appName,
    appWrapperGetter,
    proxy,
    singular,
    scopedCSS,
    dynamicStyleSheetElements,
    excludeAssetFilter,
  );

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

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

    // Determine whether all micro applications have been uninstalled
    const allMicroAppUnmounted = mountingPatchCount === 0 && bootstrappingPatchCount === 0;
    // Release the overwrite prototype after all the micro apps unmounted
    unpatchDynamicAppendPrototypeFunctions(allMicroAppUnmounted);
    unpatchDocumentCreate(allMicroAppUnmounted);

    // Because the dynamically added styles will be deleted when the micro application is unloaded. The dynamically added styles are cached here and can be used when the micro application is unloaded and re mounted
    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 as CSSStyleSheet).cssRules);
        }
      }
    });

    // Returns a rebuild function, which is called when the micro application is re mounted
    return function rebuild() {
      // Traverse dynamic style sheets
      dynamicStyleSheetElements.forEach(stylesheetElement => {
        // Like adding style nodes to the micro application container
        document.head.appendChild.call(appWrapperGetter(), stylesheetElement);

        // Add the style content to the style node. The style content is found from the cache just now
        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 as CSSStyleSheet).insertRule(cssRule.cssText);
            }
          }
        }
      });

      // As the hijacker will be invoked every mounting phase, we could release the cache for gc after rebuilding
      if (mounting) {
        dynamicStyleSheetElements = [];
      }
    };
  };
}
patchDocumentCreateElement
/**
 * In the multi instance mode, the createElement method is enhanced so that in addition to the function of creating elements, it can also hijack the creation of script, link and style elements
 * @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 in singleton mode, return directly
  if (singular) {
    return noop;
  }

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

  // This section will be executed during the initialization of the first micro application. The createElement method is enhanced so that it can not only create elements, but also hijack the creation actions of script, link and style tags
  if (Document.prototype.createElement === rawDocumentCreateElement) {
    Document.prototype.createElement = function createElement<K extends keyof HTMLElementTagNameMap>(
      this: Document,
      tagName: K,
      options?: ElementCreationOptions,
    ): HTMLElement {
      // Create element
      const element = rawDocumentCreateElement.call(this, tagName, options);
      // Hijack script, link and style tags
      if (isHijackingTag(tagName)) {
        // The following paragraph seems useless, because I didn't find any place to execute the setting, proxycontainerinfomapper set(this[attachDocProxySysbol])
        // Get the value of this thing, and then add the value to the element object with attachElementContainerSymbol as the key
        const proxyContainerInfo = proxyContainerInfoMapper.get(this[attachDocProxySymbol]);
        if (proxyContainerInfo) {
          Object.defineProperty(element, attachElementContainerSymbol, {
            value: proxyContainerInfo,
            enumerable: false,
          });
        }
      }

      // Returns the created element
      return element;
    };
  }

  // This function is returned directly during subsequent micro application initialization, and is responsible for restoring the createElement method
  return function unpatch(recoverPrototype: boolean) {
    proxyContainerInfoMapper.delete(proxy);
    if (recoverPrototype) {
      Document.prototype.createElement = rawDocumentCreateElement;
    }
  };
}
patchTHMLDynamicAppendPrototypeFunctions
// Enhance the appendChild, insertBefore and removeChild methods, return the unpatch method, and cancel the enhancement
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
  ) {
    // Enhanced 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;
    }
  };
}
getOverwrittenAppendChildOrInsertBefore
/**
 * Enhance the appendChild and insertBefore methods, so that in addition to the function of adding elements, they also have some other logic, such as:
 * Determine whether the insertion position of link, style and script elements is in the main application or micro application according to whether it is a micro application or a special element
 * The addition of hijacking script tag supports remote loading of scripts and setting the execution context (proxy) of scripts
 * @param opts 
 */
function getOverwrittenAppendChildOrInsertBefore(opts: {
  appName: string;
  proxy: WindowProxy;
  singular: boolean;
  dynamicStyleSheetElements: HTMLStyleElement[];
  appWrapperGetter: CallableFunction;
  rawDOMAppendOrInsertBefore: <T extends Node>(newChild: T, refChild?: Node | null) => T;
  scopedCSS: boolean;
  excludeAssetFilter?: CallableFunction;
}) {
  return function appendChildOrInsertBefore<T extends Node>(
    this: HTMLHeadElement | HTMLBodyElement,
    newChild: T,
    refChild?: Node | null,
  ) {
    // Element to insert
    let element = newChild as any;
    // Original method
    const { rawDOMAppendOrInsertBefore } = opts;
    if (element.tagName) {
      // Analytical parameters
      // eslint-disable-next-line prefer-const
      let { appName, appWrapperGetter, proxy, singular, dynamicStyleSheetElements } = opts;
      const { scopedCSS, excludeAssetFilter } = opts;

      // A section of logic that the multi instance mode will follow
      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: {
          // Assert that newChild is a style or link tag
          const stylesheetElement: HTMLLinkElement | HTMLStyleElement = newChild as any;
          // href attribute
          const { href } = stylesheetElement as HTMLLinkElement;
          if (!invokedByMicroApp || (excludeAssetFilter && href && excludeAssetFilter(href))) {
            // It is explained that the action of creating the element is not called by the micro application, or it is a special link tag that specifies that you do not want to be hijacked by qiankun
            // Create it under the main application
            return rawDOMAppendOrInsertBefore.call(this, element, refChild) as T;
          }

          // DOM application micro container
          const mountDOM = appWrapperGetter();

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

          // Save the element to the style sheet
          // eslint-disable-next-line no-shadow
          dynamicStyleSheetElements.push(stylesheetElement);
          // Reference element
          const referenceNode = mountDOM.contains(refChild) ? refChild : null;
          // Create this element in the micro application space so that it can be deleted directly when uninstalling the micro application
          return rawDOMAppendOrInsertBefore.call(mountDOM, stylesheetElement, referenceNode);
        }

        // script tag
        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))) {
            // Similarly, create the label under the main application
            return rawDOMAppendOrInsertBefore.call(this, element, refChild) as T;
          }

          // Micro application container DOM
          const mountDOM = appWrapperGetter();
          // User provided fetch method
          const { fetch } = frameworkConfiguration;
          // Reference node
          const referenceNode = mountDOM.contains(refChild) ? refChild : null;

          // If src exists, it is an outreach script
          if (src) {
            // Execute remote loading and set proxy as the global object of the script to achieve the purpose of 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 to indicate that the script tag has been hijacked by qiankun
            const dynamicScriptCommentElement = document.createComment(`dynamic script ${src} replaced by qiankun`);
            return rawDOMAppendOrInsertBefore.call(mountDOM, dynamicScriptCommentElement, referenceNode);
          }

          // Description the script is an inline script
          execScripts(null, [`<script>${text}</script>`], proxy, {
            strictGlobal: !singular,
            success: element.onload,
            error: element.onerror,
          });
          // Create a comment element to indicate that the script tag has been 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);
  };
}
getNewRemoveChild
/**
 * Enhance removeChild so that it can decide whether to remove the specified elements from the main application or script, style and link elements from the micro application according to the situation
 * If it is a hijacked element, it is removed from the micro application, otherwise it is removed 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) {
    // Original removeChild
    const { headOrBodyRemoveChild } = opts;
    try {
      const { tagName } = child as any;
      // Special handling when the removed element is one of script, link and style
      if (isHijackingTag(tagName)) {
        // Micro application container space
        let { appWrapperGetter } = opts;

        // The storedContainerInfo set when creating a new application contains some information of the micro application, but the storedContainerInfo should always be undefeind, because the code for setting the location seems never to be executed
        const storedContainerInfo = (child as any)[attachElementContainerSymbol];
        if (storedContainerInfo) {
          // eslint-disable-next-line prefer-destructuring
          // The wrapping element of micro application can also be called micro application template
          appWrapperGetter = storedContainerInfo.appWrapperGetter;
        }

        // Remove the element from the micro application 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) as T;
        }
      }
    } catch (e) {
      console.warn(e);
    }

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

patchAtMounting

It will be called in the mounting phase of the micro application, which is mainly responsible for patch ing 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 listening patch
    () => patchWindowListener(sandbox.proxy),
    // fix umi bug
    () => patchHistoryListener(),
    // The patch in the initialization phase
    () => patchDynamicAppend(appName, elementGetter, sandbox.proxy, true, singular, scopedCSS, excludeAssetFilter),
  ];

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

  return patchersInSandbox[sandbox.type]?.map(patch => patch());
}
patch => patchInterval
/**
 * Timer patch: automatically record the timer id when setting the timer, automatically delete the cleared timer id when clearing the timer, automatically clear all the timers that have not been cleared when releasing the patch, and restore the timer method
 * @param global = windowProxy
 */
export default function patch(global: Window) {
  let intervals: number[] = [];

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

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

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

    return noop;
  };
}
patch => patchWindowListener
/**
 * Listener patch: add a callback function that automatically records events when listening for events, automatically delete the callback function when removing events, automatically delete all event listeners when releasing patch, and restore the listening function
 * @param global windowProxy
 */
export default function patch(global: WindowProxy) {
  // Callback function for recording each event
  const listenerMap = new Map<string, EventListenerOrEventListenerObject[]>();

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

  // Remove listener
  global.removeEventListener = (
    type: string,
    listener: EventListenerOrEventListenerObject,
    options?: boolean | AddEventListenerOptions,
  ) => {
    // Removes the specified callback function for this event from the listener map
    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 the patch and remove all event listeners
  return function free() {
    // Remove all event listeners
    listenerMap.forEach((listeners, type) =>
      [...listeners].forEach(listener => global.removeEventListener(type, listener)),
    );
    // Resume listening function
    global.addEventListener = rawAddEventListener;
    global.removeEventListener = rawRemoveEventListener;

    return noop;
  };
}

link

  • Micro front end column

    • qiankun of micro front-end framework from introduction to source code analysis
    • HTML Entry source code analysis
    • Single spa of micro front-end framework from entry to mastery
  • github

Thank you for your likes, collections and comments. I'll see you next time.

When learning becomes habit, knowledge becomes commonsense. Scanning code concerns WeChat official account, learning and progress together. The article has been included in github , welcome to Watch and Star.

Topics: Javascript Front-end ECMAScript