Talking about the three mechanisms of Qiankun JS isolation (snapshot sandbox, two proxy sandboxes)

1. The development history of the three sandboxes of Qiankun Js isolation mechanism

In front-end development, in order to ensure that codes between different applications do not interfere with each other, we need to use a Js isolation mechanism, usually called a sandbox. In the Qiankun framework, we use three different Js isolation mechanisms, namely snapshot sandbox, proxy sandbox supporting single application and proxy sandbox supporting multiple applications.

Initially, the Qiankun framework had only one kind of sandbox, the snapshot sandbox, which was implemented using the SnapshotSandbox class. However, the snapshot sandbox has a disadvantage, that is, it needs to traverse all properties on the window, and the performance is poor. With the popularity of ES6, we can use Proxy to solve this problem, so LegacySandbox was born, which can achieve the same function as snapshot sandbox, but with better performance. Because LegacySandbox also pollutes the global window, it only allows a page to run one micro-application at the same time, which we also call a proxy sandbox that supports a single application.

With the development of business, we need to support one page to run multiple micro-apps, so we have ProxySandbox, which can support multiple micro-apps to run at the same time. Therefore, we call it a proxy sandbox that supports multiple applications. In fact, LegacySandbox may be eliminated in the future, because ProxySandbox can do all the functions of LegacySandbox, and snapshot sandbox may exist for a long time due to backward compatibility.

When coding and implementing the core logic of these three sandbox mechanisms, we need to pay attention to their differences and applicable scenarios in order to choose an appropriate sandbox mechanism to ensure the security and performance of the application.

2. The core logic coding implementation of the three sandboxes

In order to better understand the three Js isolation mechanisms in the Qiankun framework, we can use the most basic syntax and the simplest logic to implement their core logic. Although this implementation may not be rigorous enough, it can help us understand the principles more quickly.

Snapshot Sandbox

class SnapshotSandBox {<!-- -->
  windowSnapshot = {<!-- -->}
  modifyPropsMap = {<!-- -->}
  active() {<!-- -->
    // Save the state of all properties on the window object
    for(const prop in window) {<!-- -->
      this.windowSnapshot[prop] = window[prop]
    }
     // Restore the properties on the window that were modified last time when running the micro-app
    Object.keys(this.modifyPropsMap).forEach(prop => {<!-- -->
      window[prop] = this. modifyPropsMap[prop]
    })
  }
  inactive() {<!-- -->
    for(const prop in window) {<!-- -->
      if(window[prop] !== this.windowSnapshot[prop]){<!-- -->
        // Record which properties on the window have been modified
        this. modifyPropsMap[prop] = window[prop]
        // Restore the property state on the window to the state before the micro-app runs
        window[prop] = this. windowSnapshot[prop]
      }
    }
  }
}

window.city = 'Beijing'
console.log('before activation', window.city)
let snapshotSandBox = new SnapshotSandBox()
snapshotSandBox. active()
window.city = 'Shanghai'
console.log('after activation', window.city)
snapshotSandBox. inactive()
console.log('after deactivation', window.city)

The core logic of the snapshot sandbox is very simple. It does two things when it is activated and when it is deactivated. When activated, it records the state of the window, which is a snapshot, so that it can be restored to the previous state when deactivated. At the same time, it will restore the state changes made to the window during the running of the sandbox recorded during the last deactivation, and keep it consistent. When deactivating, it will record what state changes have occurred on the window, and clear the state changes made by the sandbox to the window after activation, so as to restore the state before it has not changed.

However, there are two important problems with the snapshot sandbox. First of all, it will change the properties of the global window. If multiple micro-apps are running at the same time, and multiple applications rewrite the properties on the window at the same time, the state will be confused. This is why the snapshot sandbox cannot support multiple micro-apps running at the same time. Secondly, it traverses all the properties on the window through for(prop in window) {}, which is a very performance-consuming thing because there are many window properties.

In order to solve these problems, the Qiankun framework introduces a proxy sandbox that supports single applications and a proxy sandbox that supports multiple applications. Both of these sandbox mechanisms can circumvent the problem of the snapshot sandbox. The proxy sandbox that supports a single application uses Proxy to proxy the window object so that the global window property will not be polluted when the micro application is running. The proxy sandbox that supports multiple applications can support multiple micro-applications to run at the same time, because it can create an independent sandbox environment for each micro-application, avoiding the state confusion between multiple applications. At the same time, the proxy sandbox that supports multiple applications does not need to traverse all the properties on the window, because it only proxies the properties required by the micro-application, thereby improving performance.

Proxy sandbox that supports single application

The LegacySandbox sandbox mainly uses three variables:

  1. addedPropsMapInSandbox: Used to record the global variables added during sandbox activation.
  2. modifiedPropsOriginalValueMapInSandbox: Used to record global variables updated during sandbox activation.
  3. currentUpdatedPropsValueMap: Continuously records updated (new and modified) global variables.
class LegacySandBox {<!-- -->
  currentUpdatedPropsValueMap = new Map()
  modifiedPropsOriginalValueMapInSandbox = new Map();
  addedPropsMapInSandbox = new Map();
  proxyWindow = {<!-- -->}
  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.hasOwnProperty(prop)){<!-- -->
          this.modifiedPropsOriginalValueMapInSandbox.set(prop, originalVal)
        }
        this.currentUpdatedPropsValueMap.set(prop, value)
        window[prop] = value
      },
      get: (target, prop, receiver) => {<!-- -->
        return window[prop]
      }
    })
  }
  setWindowProp(prop, value, isToDelete = false) {<!-- -->
    //It may be a new attribute, which will not be needed later
    if(value === undefined & amp; & amp; isToDelete) {<!-- -->
      delete window[prop]
    } else {<!-- -->
      window[prop] = value
    }
  }
  active() {<!-- -->
    // Restore all changes made to the window when the micro-app was running last time
    this.currentUpdatedPropsValueMap.forEach((value, prop) => {<!-- -->
      this.setWindowProp(prop, value)
    })
  }
  inactive() {<!-- -->
    // Restore the original properties on the window
    this.modifiedPropsOriginalValueMapInSandbox.forEach((value, prop) => {<!-- -->
      this.setWindowProp(prop, value)
    })
    // Delete the newly added properties on the window during the running of the micro-app
    this.addedPropsMapInSandbox.forEach((_, prop) => {<!-- -->
      this.setWindowProp(prop, undefined, true)
    })
  }
}

window.city = 'Beijing'
let legacySandBox = new LegacySandBox();
console.log('before activation', window.city)
legacySandBox. active();
legacySandBox.proxyWindow.city = 'Shanghai';
console.log('after activation', window.city)
legacySandBox. inactive();
console.log('after deactivation', window.city)

The above code implements a function similar to the snapshot sandbox, that is, recording the state of the window object and restoring the state of the window object when the sandbox is deactivated. The difference is that LegacySandbox uses three variables to record all the properties of the window that have changed after the sandbox is activated, which avoids traversing all properties of the window for comparison and improves the performance of the program. However, this mechanism will still change the state of the window, so it cannot undertake the task of supporting multiple micro-apps to run at the same time. Therefore, the Qiankun framework introduces a proxy sandbox that supports single applications and a proxy sandbox that supports multiple applications to avoid this problem. The proxy sandbox that supports a single application uses Proxy to proxy the window object so that the global window property will not be polluted when the micro application is running. A proxy sandbox that supports multiple applications can create an independent sandbox environment for each micro-application, avoiding state confusion among multiple applications.

A proxy sandbox that supports multiple applications

Sandbox activation/uninstallation process

  1. After the sandbox is activated, each time the window attribute is obtained, it will first search from the fakeWindow in the current sandbox environment, and if it does not exist, it will search from the external window. Doing so ensures that operations inside the sandbox will not affect the global window object.
  2. At the same time, when the window object is modified, use the set method of the proxy to intercept it, and directly operate the proxy object fakeWindow instead of the global window object, so as to achieve real isolation. This mechanism can avoid the state confusion between different micro-applications and ensure the independence of micro-applications.
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)=>{<!-- -->
        // Only operate fakeWindow when setting
            if(this.isRunning){<!-- -->
                target[prop] = value;
            }
        },
        get:(target, prop, receiver)=>{<!-- -->
            return prop in target ? target[prop] : window[prop];
        }
    })
  }
}

window.city = 'Beijing'
let proxySandBox1 = new ProxySandBox();
let proxySandBox2 = new ProxySandBox();
proxySandBox1. active();
proxySandBox2. active();
proxySandBox1.proxyWindow.city = 'Shanghai';
proxySandBox2.proxyWindow.city = 'Chengdu';
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);

As can be seen from the above code, ProxySandbox does not have state recovery logic, because all changes are internal changes in the sandbox, and have nothing to do with the window, and the properties on the window have not been affected from beginning to end. ProxySandbox supports multiple micro-applications to run at the same time, and also supports the operation of a single micro-application, so it has become the main sandbox mechanism of the Qiankun framework. While LegacySandbox exists for historical reasons, it has little meaning in the future. SnapshotSandbox exists for a long time because Proxy is not compatible with low-version browsers. Although the code logic here is very simple, in actual applications, ProxySandbox needs to support the operation of multiple micro-applications, so its internal logic will be richer than SnapshotSandbox and LegacySandbox. In short, after understanding the idea of the above-mentioned sandbox mechanism, you can understand the Js isolation mechanism of the Qiankun framework.