It has been nearly a year since the last article. At that time, I promised to prepare a series of articles to analyze the source code of Vue2, and then did more research on Vue3, but after all, I did not present it in the form of an article. Yes, I reneged. But enthusiastic support with technical articles, though it was late but not absent, I will appear in a series of articles all aspects of the front frame, not only is the core process, more go into details, the principle of understanding can help us make better use of some tools, in turn, better work, but details will really help us grow. On the micro front end of the series of articles, I will start from qian Kun source analysis, in-depth to import-HTML-Entry (an important dependency library of Qian Kun), single-SPA source details implementation, further analysis of the market some other micro front end framework ideas and advantages and disadvantages, We’ll end this series of articles with a view to building a powerful micro-front-end framework that can be used in production environments. Well, let’s get started.

An overview of the

Qiankun, as a well-known framework in the micro front end space, is built on the basis of Single-SPA. Compared to single-SPA, Qiankun does two important things, one is to load resources and the other is to isolate resources. And resource isolation is divided into Js resource isolation and CSS resource isolation, this paper mainly explores the Js resource isolation mechanism of the Universe. The following will be explained in three parts:

  • The history of the isolation mechanism of Qiankun Js;

  • Encoding the core logic of three KINDS of Js isolation mechanism, and analyze their advantages and disadvantages;

  • Analysis of three kinds of Js isolation mechanism of Qiankun source code, and in-depth details of the analysis;

The history of the universe Js isolation mechanism

The Js isolation mechanism is often referred to as sandbox. In fact, there are three Js isolation mechanisms, and the source code uses SnapshotSandbox, LegacySandbox, and ProxySandbox to refer to the three different isolation mechanisms. Below, we use the snapshot sandbox, single-application proxy sandbox, and multi-application proxy sandbox to represent the three different Js isolation mechanisms. So the question is, why are there so many sandboxes? At first there was only one kind of sandbox called the “SnapshotSandbox,” which was implemented by the SnapshotSandbox class. The disadvantage of this sandbox is that it needs to iterate over all the properties on the window, which is poor performance. With the popularity of ES6, Proxy can be used to solve this problem well, which gives birth to LegacySandbox, which can realize the same function as SnapshotSandbox, but has better performance. Just like SnapshotSandbox, it will pollute the global window. LegacySandbox also only allows a page to run a microapplication at the same time, so we also call LegacySandbox a proxy sandbox that supports a single application. Judging by the LegacySandbox class name, it must not have been called LegacySandbox in the first place because it was forced on it by a better mechanism. A better mechanism is ProxySandbox, which allows multiple micro-applications to run on a page. Therefore, we call ProxySandbox a ProxySandbox that supports multiple applications. In fact, LegacySandbox should disappear in the future, as LegacySandbox does everything LegacySandbox does, whereas SanpsshotSandbox will live with ProxySandbox for backward compatibility reasons. Let’s code the core logic that implements these three sandbox mechanisms.

The code implements the core logic of the three sandboxes

In order to understand the convenience, I will use the simplest logic and the most basic syntax to realize the three sandboxes below. The logic will not be very strict, but it can facilitate us to understand the principle more quickly.

Snapshot Sandbox – Minimal edition

class SnapshotSandBox{
    windowSnapshot = {};
    modifyPropsMap = {};
    active(){
        for(const prop in window) {this.windowSnapshot[prop] = window[prop];
        }
        Object.keys(this.modifyPropsMap).forEach(prop= >{
            window[prop] = this.modifyPropsMap[prop];
        });
    }
    inactive(){
        for(const prop in window) {if(window[prop] ! = =this.windowSnapshot[prop]){
                this.modifyPropsMap[prop] = window[prop];
                window[prop] = this.windowSnapshot[prop]; }}}}/ / validation:
let snapshotSandBox = new SnapshotSandBox();
snapshotSandBox.active();
window.city = 'Beijing';
console.log("window.city-01:".window.city);
snapshotSandBox.inactive();
console.log("window.city-02:".window.city);
snapshotSandBox.active();
console.log("window.city-03:".window.city);
snapshotSandBox.inactive();

/ / output:
//window.city-01: Beijing
//window.city-02: undefined
//window.city-03: Beijing
Copy the code

As you can see from the code above, the core logic of the snapshot sandbox is simple: do two things when the sandbox is activated and when the sandbox is deactivated.

Note: Sandbox activation is when our microapplication is running, and it is possible to change the properties on the window during this stage. Sandbox deactivation is when our microapplication has stopped impacting Windows

When the sandbox is active:

  • Record the state of the Window (we call this state snapshot, hence the name snapshot sandbox);

  • Restore the state changes to the Window recorded during the last sandbox deactivation, that is, the changes made to the Window since the last sandbox activation, and keep the same changes.

When the sandbox is deactivated:

  • Record what states changed on the window (from the time the sandbox was activated to the time it was deactivated);

  • Clear the state of the window changed after the sandbox is activated. As can be seen from the code, it is to compare the property state of the window at this time with the property state of the window when it is activated. Different property states will be restored to the original state based on the snapshot.

As you can see from the above, there are two important problems with the snapshot sandbox:

  • Changes the properties of the global Window. If multiple microapplications are running at the same time and multiple applications overwrite the properties of the Window at the same time, state chaos is bound to occur, which is why the snapshot sandbox cannot support multiple microapplications running at the same time. The multi-application proxy sandbox described below is a good solution to this problem;

  • For (prop in window){} is used to iterate over all properties of the window. Both single-application and multi-application proxy sandboxes can be circumvented.

Support single application proxy sandbox – minimal edition

class LegacySandBox{
    addedPropsMapInSandbox = new Map(a); modifiedPropsOriginalValueMapInSandbox =new Map(a); currentUpdatedPropsValueMap =new Map(a); proxyWindow;setWindowProp(prop, value, toDelete = false){
        if(value === undefined && toDelete){
            delete window[prop];
        }else{
            window[prop] = value; }}active(){
        this.currentUpdatedPropsValueMap.forEach((value, prop) = >this.setWindowProp(prop, value));
    }
    inactive(){
        this.modifiedPropsOriginalValueMapInSandbox.forEach((value, prop) = >this.setWindowProp(prop, value));
        this.addedPropsMapInSandbox.forEach((_, prop) = >this.setWindowProp(prop, undefined.true));
    }
    constructor(){
        const fakeWindow = Object.create(null);
        this.proxyWindow = new Proxy(fakeWindow,{
            set:(target, prop, value, receiver) = >{
                const originalVal = window[prop];
                if(!window.hasOwnProperty(prop)){
                    this.addedPropsMapInSandbox.set(prop, value);
                }else if(!this.modifiedPropsOriginalValueMapInSandbox.has(prop)){
                    this.modifiedPropsOriginalValueMapInSandbox.set(prop, originalVal);
                }
                this.currentUpdatedPropsValueMap.set(prop, value);
                window[prop] = value;
            },
            get:(target, prop, receiver) = >{
                returntarget[prop]; }}); }}/ / validation:
let legacySandBox = new LegacySandBox();
legacySandBox.active();
legacySandBox.proxyWindow.city = 'Beijing';
console.log('window.city-01:'.window.city);
legacySandBox.inactive();
console.log('window.city-02:'.window.city);
legacySandBox.active();
console.log('window.city-03:'.window.city);
legacySandBox.inactive();
/ / output:
// window.city-01: Beijing
// window.city-02: undefined
// window.city-03: Beijing
Copy the code

Can be seen from the above code, the function of its implementation and snapshot sandbox is the same, different is, to remember the sandbox activated by three variables window to change all of the attributes, so in the subsequent state of reduction when they no longer need to traverse, comparing the window all the attributes to improve the performance of the program is running. But that still doesn’t change the fact that this mechanism still contaminates the state of the Window, and therefore is not up to the task of supporting multiple microapplications running simultaneously.

Multi-application proxy sandbox – Minimal edition

class ProxySandBox{
    proxyWindow;
    isRunning = false;
    active(){
        this.isRunning = true;
    }
    inactive(){
        this.isRunning = false;
    }
    constructor(){
        const fakeWindow = Object.create(null);
        this.proxyWindow = new Proxy(fakeWindow,{
            set:(target, prop, value, receiver) = >{
                if(this.isRunning){ target[prop] = value; }},get:(target, prop, receiver) = >{
                return  prop in target ? target[prop] : window[prop]; }}); }}/ / validation:
let proxySandBox1 = new ProxySandBox();
let proxySandBox2 = new ProxySandBox();
proxySandBox1.active();
proxySandBox2.active();
proxySandBox1.proxyWindow.city = 'Beijing';
proxySandBox2.proxyWindow.city = 'Shanghai';
console.log('active:proxySandBox1:window.city:', proxySandBox1.proxyWindow.city);
console.log('active:proxySandBox2:window.city:', proxySandBox2.proxyWindow.city);
console.log('window:window.city:'.window.city);
proxySandBox1.inactive();
proxySandBox2.inactive();
console.log('inactive:proxySandBox1:window.city:', proxySandBox1.proxyWindow.city);
console.log('inactive:proxySandBox2:window.city:', proxySandBox2.proxyWindow.city);
console.log('window:window.city:'.window.city);
/ / output:
// active:proxySandBox1:window.city: Beijing
// active:proxySandBox2:window.city: Shanghai
// window:window.city: undefined
// inactive:proxySandBox1:window.city: Beijing
// inactive:proxySandBox2:window.city: Shanghai
// window:window.city: undefined

Copy the code

As you can see in ProxySandbox, there is no state recovery logic, and no logging of property changes, because all changes are inside the sandbox and have nothing to do with the Window. Properties on the Window are never affected. We might ask, well, if ProxySandbox is good, it works well and supports multiple microapplications at the same time, what’s the point of LegacySandbox? That’s a good question, but it’s not going to make much sense in the future. It’s just that it’s still in service for historical reasons, and the word Legacy is already an indication of LegacySandbox’s place in the universe. Proxy is a new thing on the new ES6, and it won’t work on older browsers so SnapshotSandbox is here to stay. Although there is very little logic in the minimalist version, since ProxySandbox supports multiple micro-applications, the logic in This ProxySandbox is richer for both SnapshotsSandbox and LegacySandbox.

In fact, here, if the reader friends have understood the above ideas, can be said to have understood the Js isolation mechanism of the universe. Let’s look at the source code of the universe is how to achieve the three sandbox mechanism.

Qiankun three sandbox source analysis

SnapshotSandbox source code parsing

import type { SandBox } from '.. /interfaces';
import { SandBoxType } from '.. /interfaces';
function iter(obj: typeof window, callbackFn: (prop: any) = >void) {
  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const prop in obj) {
    // patch for clearInterval for compatible reason, see #1490
    if (obj.hasOwnProperty(prop) || prop === 'clearInterval') { callbackFn(prop); }}}/** * Diff - based sandbox for older 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

As for the SnapshotSandbox, there is not much difference from the minimalist version due to its relatively simple logic. It’s worth mentioning that the iter method takes the code out of iterating over the window property, and after calling the utility method, we just need to focus on iterating over the property.

LegacySandbox source code parsing

/ * * *@author Kuitos
 * @since The 2019-04-11 * /
 import type { SandBox } from '.. /.. /interfaces';
 import { SandBoxType } from '.. /.. /interfaces';
 import { getTargetValue } from '.. /common';
 
 function isPropConfigurable(target: WindowProxy, prop: PropertyKey) {
   const descriptor = Object.getOwnPropertyDescriptor(target, prop);
   return descriptor ? descriptor.configurable : true;
 }
 
 /** * Proxy based implementation of the sandbox *TODO:Use this sandbox again for compatibility in Singular mode, switch */ after the new sandbox is stable
 export default class LegacySandbox implements SandBox {
   /** New global variable */ added during sandbox
   private addedPropsMapInSandbox = new Map<PropertyKey, any> ();/** Global variables updated during sandbox */
   private modifiedPropsOriginalValueMapInSandbox = new Map<PropertyKey, any> ();/** Keeps a map of updated (new and modified) global variables, used to do snapshot */ at any time
   private currentUpdatedPropsValueMap = new Map<PropertyKey, any> (); name:string;
 
   proxy: WindowProxy;
 
   globalContext: typeof window;
 
   type: SandBoxType;
 
   sandboxRunning = true;
 
   latestSetProp: PropertyKey | null = null;
 
   private setWindowProp(prop: PropertyKey, value: any, toDelete? :boolean) {
     if (value === undefined && toDelete) {
       // eslint-disable-next-line no-param-reassign
       delete (this.globalContext as any)[prop];
     } else if (isPropConfigurable(this.globalContext, prop) && typeofprop ! = ='symbol') {
       Object.defineProperty(this.globalContext, prop, { writable: true.configurable: true });
       // eslint-disable-next-line no-param-reassign
       (this.globalContext as any)[prop] = value; }}active() {
     if (!this.sandboxRunning) {
       this.currentUpdatedPropsValueMap.forEach((v, p) = > this.setWindowProp(p, v));
     }
 
     this.sandboxRunning = true;
   }
 
   inactive() {
     if (process.env.NODE_ENV === 'development') {
       console.info(`[qiankun:sandbox] The ${this.name}modified global properties restore... `, [
         ...this.addedPropsMapInSandbox.keys(),
         ...this.modifiedPropsOriginalValueMapInSandbox.keys(),
       ]);
     }
 
     // renderSandboxSnapshot = snapshot(currentUpdatedPropsValueMapForSnapshot);
     // restore global props to initial snapshot
     this.modifiedPropsOriginalValueMapInSandbox.forEach((v, p) = > this.setWindowProp(p, v));
     this.addedPropsMapInSandbox.forEach((_, p) = > this.setWindowProp(p, undefined.true));
 
     this.sandboxRunning = false;
   }
 
   constructor(name: string, globalContext = window) {
     this.name = name;
     this.globalContext = globalContext;
     this.type = SandBoxType.LegacyProxy;
     const { addedPropsMapInSandbox, modifiedPropsOriginalValueMapInSandbox, currentUpdatedPropsValueMap } = this;
 
     const rawWindow = globalContext;
     const fakeWindow = Object.create(null) as Window;
 
     const setTrap = (p: PropertyKey, value: any, originalValue: any, sync2Window = true) = > {
       if (this.sandboxRunning) {
         if(! rawWindow.hasOwnProperty(p)) { addedPropsMapInSandbox.set(p, value); }else if(! modifiedPropsOriginalValueMapInSandbox.has(p)) {// If the property exists in the current Window object and is not recorded in the Record map, the initial value of the property is recorded
           modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
         }
 
         currentUpdatedPropsValueMap.set(p, value);
 
         if (sync2Window) {
           // The window object must be reset to get updated data the next time you get it
           (rawWindow as any)[p] = value;
         }
 
         this.latestSetProp = p;
 
         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, Proxy handler.set returns false and raises TypeError, which should be ignored in sandbox unload cases
       return true;
     };
 
     const proxy = new Proxy(fakeWindow, {
       set: (_: Window, p: PropertyKey, value: any) :boolean= > {
         const originalValue = (rawWindow as any)[p];
         
         return setTrap(p, value, originalValue, 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;
         }
 
         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;
       },
 
       getOwnPropertyDescriptor(_: Window, p: PropertyKey): PropertyDescriptor | undefined {
         const descriptor = Object.getOwnPropertyDescriptor(rawWindow, p);
         // 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;
       },
 
       defineProperty(_: Window, p: string | symbol, attributes: PropertyDescriptor): boolean {
         const originalValue = (rawWindow as any)[p];
         const done = Reflect.defineProperty(rawWindow, p, attributes);
         const value = (rawWindow as any)[p];
         setTrap(p, value, originalValue, false);
 
         returndone; }});this.proxy = proxy; }}Copy the code

And compared minimalist version, will be found in the source LegacySandbox proxy objects in addition to the get, set, and from the getWownPropertyDescriptor, defineProperty, see will find that the logic here, whether the return value or set values, Get and set are both global Windows. If you just set for assignment and ignore object.defineProperty to change values, the program will be buggy. Similarly, if you don’t care about the state of descriptor, you might ignore the comments in the code during actual coding


// A property cannot be reported as non-configurable, if it does not exists as an own property of the target object

/ /" In the getOwnPropertyDescriptor method, if the descriptor returned is not the descriptor of the target's own property, Method is to pass the Object. GetOwnPropertyDescriptor access descriptor), the configurable value of this descriptor cannot is false

Copy the code

ProxySandbox source code parsing

/* eslint-disable no-param-reassign */
/ * * *@author Kuitos
 * @since The 2020-3-31 * /
 import type { SandBox } from '.. /interfaces';
 import { SandBoxType } from '.. /interfaces';
 import { nativeGlobal, nextTask } from '.. /utils';
 import { getTargetValue, setCurrentRunningApp, getCurrentRunningApp } from './common';
 
 type SymbolTarget = 'target' | 'globalContext';
 
 type FakeWindow = Window & Record<PropertyKey, any>;
 
 /**
  * fastest(at most time) unique array method
  * @see https://jsperf.com/array-filter-unique/30
  */
 function uniq(array: Array<string | symbol>) {
   return array.filter(function filter(this: PropertyKey[], element) {
     return element in this ? false : ((this as any)[element] = true);
   }, Object.create(null));
 }
 
 // zone.js will overwrite Object.defineProperty
 const rawObjectDefineProperty = Object.defineProperty;
 
 const variableWhiteListInDev =
   process.env.NODE_ENV === 'development' || window.__QIANKUN_DEVELOPMENT__
     ? [
         // for react hot reload
         // see https://github.com/facebook/create-react-app/blob/66bf7dfc43350249e2f09d138a20840dae8a0a4a/packages/react-error-overlay/ src/index.js#L180
         '__REACT_ERROR_OVERLAY_GLOBAL_HOOK__'] : [];// who could escape the sandbox
 const variableWhiteList: PropertyKey[] = [
   // FIXME System.js used a indirect call with eval, which would make it scope escape to global
   // To make System.js works well, we write it back to global window temporary
   // see https://github.com/systemjs/systemjs/blob/457f5b7e8af6bd120a279540477552a07d5de086/src/evaluate.js#L106
   'System'.// see https://github.com/systemjs/systemjs/blob/457f5b7e8af6bd120a279540477552a07d5de086/src/instantiate.js#L357
   '__cjsWrapper'. variableWhiteListInDev, ];/* variables who are impossible to be overwrite need to be escaped from proxy sandbox for performance reasons */
 const unscopables = {
   undefined: true.Array: true.Object: true.String: true.Boolean: true.Math: true.Number: true.Symbol: true.parseFloat: true.Float32Array: true.isNaN: true.Infinity: true.Reflect: true.Float64Array: true.Function: true.Map: true.NaN: true.Promise: true.Proxy: true.Set: true.parseInt: true.requestAnimationFrame: true};const useNativeWindowForBindingsProps = new Map<PropertyKey, boolean>([
   ['fetch'.true],
   ['mockDomAPIInBlackList', process.env.NODE_ENV === 'test']]);function createFakeWindow(globalContext: Window) {
   // map always has the fastest performance in has check scenario
   // see https://jsperf.com/array-indexof-vs-set-has/23
   const propertiesWithGetter = new Map<PropertyKey, boolean> ();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(globalContext)
     .filter((p) = > {
       const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
       return! descriptor? .configurable; }) .forEach((p) = > {
       const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
       if (descriptor) {
         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'))
         ) {
           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) { descriptor.writable =true; }}if (hasGetter) propertiesWithGetter.set(p, true);
 
         // 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,
   };
 }
 
 let activeSandboxCount = 0;
 
 /** * Proxy based implementation of the sandbox */
 export default class ProxySandbox implements SandBox {
   /** window value change record */
   private updatedValueSet = new Set<PropertyKey>();
 
   name: string;
 
   type: SandBoxType;
 
   proxy: WindowProxy;
 
   globalContext: typeof window;
 
   sandboxRunning = true;
 
   latestSetProp: PropertyKey | null = null;
 
   private registerRunningApp(name: string, proxy: Window) {
     if (this.sandboxRunning) {
       const currentRunningApp = getCurrentRunningApp();
       if(! currentRunningApp || currentRunningApp.name ! == name) { setCurrentRunningApp({ name,window: proxy });
       }
       // FIXME if you have any other good ideas
       // 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 complex cases, such as the micro app runs in the same task context with master in some case
       nextTask(() = > {
         setCurrentRunningApp(null); }); }}active() {
     if (!this.sandboxRunning) activeSandboxCount++;
     this.sandboxRunning = true;
   }
 
   inactive() {
     if (process.env.NODE_ENV === 'development') {
       console.info(`[qiankun:sandbox] The ${this.name}modified global properties restore... `, [
         ...this.updatedValueSet.keys(),
       ]);
     }
 
     if (--activeSandboxCount === 0) {
       variableWhiteList.forEach((p) = > {
         if (this.proxy.hasOwnProperty(p)) {
           // @ts-ignore
           delete this.globalContext[p]; }}); }this.sandboxRunning = false;
   }
 
   constructor(name: string, globalContext = window) {
     this.name = name;
     this.globalContext = globalContext;
     this.type = SandBoxType.Proxy;
     const { updatedValueSet } = this;
 
     const { fakeWindow, propertiesWithGetter } = createFakeWindow(globalContext);
 
     const descriptorTargetMap = new Map<PropertyKey, SymbolTarget>();
     const hasOwnProperty = (key: PropertyKey) = > fakeWindow.hasOwnProperty(key) || globalContext.hasOwnProperty(key);
 
     const proxy = new Proxy(fakeWindow, {
       set: (target: FakeWindow, p: PropertyKey, value: any) :boolean= > {
         if (this.sandboxRunning) {
           this.registerRunningApp(name, proxy);
           // We must kept its description while the property existed in globalContext before
           if(! target.hasOwnProperty(p) && globalContext.hasOwnProperty(p)) {const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
             const{ writable, configurable, enumerable } = descriptor! ;if (writable) {
               Object.defineProperty(target, p, { configurable, enumerable, writable, value, }); }}else {
             // @ts-ignore
             target[p] = value;
           }
 
           if(variableWhiteList.indexOf(p) ! = = -1) {
             // @ts-ignore
             globalContext[p] = value;
           }
 
           updatedValueSet.add(p);
 
           this.latestSetProp = p;
 
           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, Proxy handler.set returns false and raises TypeError, which should be ignored in sandbox unload cases
         return true;
       },
 
       get: (target: FakeWindow, p: PropertyKey): any= > {
         this.registerRunningApp(name, proxy);
 
         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;
         }
 
         // hijack globalWindow accessing with globalThis keyword
         if (p === 'globalThis') {
           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 (globalContext === globalContext.parent) {
             return proxy;
           }
           return (globalContext as any)[p];
         }
 
         // proxy.hasOwnProperty would invoke getter firstly, then its value represented as globalContext.hasOwnProperty
         if (p === 'hasOwnProperty') {
           return hasOwnProperty;
         }
 
         if (p === 'document') {
           return document;
         }
 
         if (p === 'eval') {
           return eval;
         }
 
         const value = propertiesWithGetter.has(p)
           ? (globalContext as any)[p]
           : p in target
           ? (target as any)[p]
           : (globalContext as any)[p];
         /* Some dom api must be bound to native window, otherwise it would cause exception like 'TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation' See this code: const proxy = new Proxy(window, {}); const proxyFetch = fetch.bind(proxy); proxyFetch('https://qiankun.com'); * /
         const boundTarget = useNativeWindowForBindingsProps.get(p) ? nativeGlobal : globalContext;
         return getTargetValue(boundTarget, value);
       },
 
       // trap in operator
       // 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 globalContext;
       },
 
       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 (globalContext.hasOwnProperty(p)) {
           const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
           descriptorTargetMap.set(p, 'globalContext');
           // 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): ArrayLike<string | symbol> {
         return uniq(Reflect.ownKeys(globalContext).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 'globalContext':
             return Reflect.defineProperty(globalContext, p, attributes);
           default:
             return Reflect.defineProperty(target, p, attributes); }},deleteProperty: (target: FakeWindow, p: string | number | symbol): boolean= > {
         this.registerRunningApp(name, proxy);
         if (target.hasOwnProperty(p)) {
           // @ts-ignore
           delete target[p];
           updatedValueSet.delete(p);
 
           return true;
         }
 
         return true;
       },
 
       // makes sure `window instanceof Window` returns truthy in micro app
       getPrototypeOf() {
         return Reflect.getPrototypeOf(globalContext); }});this.proxy = proxy; activeSandboxCount++; }}Copy the code

Here’s the full ProxySandbox source code, so I’ll talk about one of the details. There’s a uniQ method in there, and I’ll put it down there separately. The first argument to filter is a function and the second argument is an object. For details about parameters, see related documents. The reason why it is mentioned here is because it cleverly uses this. De-duplicate is implemented by setting a property on this and making it conditional. Function filter(this: The first argument to PropertyKey[], element) is this. Normally this would run wrong in javascript, but typescript only uses this argument for type inference. So we can assume that the first argument here is element, and IT took me a while to figure it out in the documentation when I first read it because I wasn’t clear about this little typescript syntax point.

/**
 * fastest(at most time) unique array method
 * @see https://jsperf.com/array-filter-unique/30
 */
 function uniq(array: Array<string | symbol>) {
    return array.filter(function filter(this: PropertyKey[], element) {
      return element in this ? false : ((this as any)[element] = true);
    }, Object.create(null));
  }
Copy the code

There are a lot of details in it, some of which are not very important, and there are some things THAT I still don’t understand, such as why there is a need for a propertiesWithGetter variable to do some conditional judgment. I need to continue studying, and I will fill in the relevant parsing if I understand it later. Readers are also welcome to leave more messages, dig details, and make progress together.

Please follow my wechat subscription number: Yang Yitao can read new articles earlier.