Contents
Preface
In large-scale projects, micro-frontend is a common optimization method. This article explains the mechanism and principle of sandbox in micro-frontend.
First, what is a micro frontend?
Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently . — Micro Frontends
The front-end is a technical means and method strategy for multiple teams to jointly build modern web applications by publishing functions independently.
Common micro front-end implementation mechanisms

iframe
If you still don’t understand what a micro frontend is, then just think of it as a kind of iframeBut why don’t we use it directly?
iframeThe biggest feature is that it provides a browser-native hard isolation solution, which can perfectly solve problems such as style isolation and js isolation. But its biggest problem is that its isolation cannot be broken, resulting in the inability to share context between applications, resulting in development experience and product experience problems.
- The url is out of sync. When the browser refreshes the iframe url, the status is lost and the back and forward buttons cannot be used.
- The UI is not synchronized and the DOM structure is not shared. Imagine that there is a pop-up box with a mask layer in the iframe 1/4 of the lower right corner of the screen. At the same time, we require that the pop-up box be displayed in the center of the browser and automatically centered when the browser resizes..
- The global context is completely isolated and memory variables are not shared. For communication, data synchronization and other requirements within and outside the iframe system, the cookies of the main application must be transparently transmitted to sub-applications with different root domain names to achieve the login-free effect.
- slow. Each time a sub-application is entered, it is a process of rebuilding the browser context and reloading resources.
Some of the problems are easier to solve (Question 1), some we can turn a blind eye to (Question 4), but some are difficult to solve (Question 3) or even impossible (Question 2) , and these unsolvable problems will bring very serious experience problems to the product, which ultimately led us to abandon the iframe solution.
Taken from the article: Why Not Iframe
Micro frontend sandbox
In the micro front-end scenario, since multiple independent applications are organized together, without similar iframenative isolation, conflicts are bound to occur, such as global variable conflicts and style conflicts. These conflicts may lead to abnormal application styles or even functional failures. use.
At this time we need an independent running environment, and this environment is called a sandbox, that is sandbox.
The first step in implementing a sandbox is to create a scope. This scope will not contain global property objects.
First, we need to isolate the browser’s native objects, but how to isolate them and establish a sandbox environment?
Proxy-based sandbox
Assuming that there is only one micro-application running on the current page, it can monopolize the entire windowenvironment. When switching micro-applications, only windowthe environment can be restored to ensure the next use.
This is the single instance scenario .
single instance
A simplest implementation demo:
const varBox = {};
const fakeWindow = new Proxy ( window , {
get ( target, key ) {
return varBox[key] || window [key];
},
set ( target, key, value ) {
varBox[key] = value;
return true ;
},
});
window .test = 1 ;
A simple agent proxycan be implemented windowto store data varBoxin without affecting the original windowvalue of
In some articles, he implemented the sandbox more specifically, and also had enabling and deactivating functions:
// Modify the global object window method
const setWindowProp = ( prop, value, isDel ) => {
if (value === undefined || isDel) {
delete window [prop];
} else {
window [prop] = value;
}
}
class Sandbox {
name;
proxy = null ;
//Global variables added during the sandbox
addedPropsMap = new Map ();
//Global variables updated during sandbox
modifiedPropsOriginalValueMap = new Map ();
//Continuously record the map of updated (new and modified) global variables, used for sandbox activation at any time
currentUpdatedPropsValueMap = new Map ();
// The application sandbox is activated
active () {
// Re-modify the properties of the window based on the previously modified records, that is, restore the state before the sandbox
this . currentUpdatedPropsValueMap . forEach ( ( v, p ) => setWindowProp (p, v));
}
// The application sandbox is uninstalled
inactive () {
// 1 Restore the properties modified during the sandbox to the original properties
this . modifiedPropsOriginalValueMap . forEach ( ( v, p ) => setWindowProp (p, v));
// 2 Restore the global variables added during the sandbox Eliminate
this . addedPropsMap . forEach ( ( _, p ) => setWindowProp (p, undefined , true ));
}
constructor ( name ) {
this . name = name;
const fakeWindow = Object . create ( null ); // Create an empty object with a prototype of null
const { addedPropsMap, modifiedPropsOriginalValueMap, currentUpdatedPropsValueMap } = this ;
const proxy = new Proxy (fakeWindow, {
set ( _, prop, value ) {
if (! window . hasOwnProperty (prop)) {
// If there is no property on the window, record it in the new property
addedPropsMap. set (prop, value);
} else if (!modifiedPropsOriginalValueMap. has (prop)) {
// If the current window object has this property and has not been updated, record the initial value of the property on the window
const originalValue = window [prop];
modifiedPropsOriginalValueMap.set ( prop, originalValue);
}
// Record modified properties and modified values
currentUpdatedPropsValueMap. set (prop, value);
//Set the value to the global window
setWindowProp (prop,value);
console . log ( 'window.prop' , window [prop]);
return true ;
},
get ( target, prop ) {
return window [prop];
},
});
this .proxy = proxy;
}
}
// Initialize a sandbox
const newSandBox = new Sandbox ( 'app1' );
const proxyWindow = newSandBox. proxy ;
proxyWindow. test = 1 ;
console . log ( window . test , proxyWindow. test ) // 1 1;
// Close the sandbox
newSandBox. inactive ();
console . log ( window . test , proxyWindow. test ); // undefined undefined;
// Restart the sandbox
newSandBox. active ();
console . log ( window . test , proxyWindow. test ) // 1 1 ;
The sandbox activeand inactivesolutions are added to activate or uninstall the sandbox. The core function proxyis created in the constructor.
The principle is similar to the above-mentioned simple demoimplementation, but there is no direct interception window, but creation of one fakeWindow, which leads us to Talking about
multi-instance sandbox
multiple instances
We fakeWindowuse it and put the variables used by the micro-application fakeWindowinto , and the shared variables are read windowfrom .
class Sandbox {
name;
constructor ( name, context = {} ) {
this . name = name;
const fakeWindow = Object . create ({});
return new Proxy (fakeWindow, {
set ( target, name, value ) {
if ( Object . keys (context). includes (name)) {
context[name] = value;
}
target[name] = value;
},
get ( target, name ) {
// Prioritize using shared objects
if ( Object . keys (context). includes (name)) {
return context[name];
}
if ( typeof target[name] === 'function' && /^[az]/ . test (name)) {
return target[name]. bind && target[name]. bind (target);
} else {
return target[name];
}
}
});
}
// ...
}
/**
* Note that the context here is very important, because our fakeWindow is an empty object, and there are no attributes on the window.
* In actual projects, the context here should contain a large number of window attributes.
*/
// Initialize 2 sandboxes, share document and a global variable
const context = { document : window . document , globalData : 'abc' };
const newSandBox1 = new Sandbox ( 'app1' , context);
const newSandBox2 = new Sandbox ( 'app2' , context);
newSandBox1. test = 1 ;
newSandBox2. test = 2 ;
window . test = 3 ;
/**
* Private properties of each environment are isolated
*/
console . log (newSandBox1. test , newSandBox2. test , window . test ); // 1 2 3;
/**
* The shared properties are shared by the sandbox, and the globalData in the newSandBox2 environment has also been changed.
*/
newSandBox1. globalData = '123' ;
console . log (newSandBox1. globalData , newSandBox2. globalData ); // 123 123;
diff-based sandbox
It is also called snapshot sandbox . As the name suggests, it takes a snapshot of the current running environment at a certain stage, and then restores the snapshot when needed to achieve isolation.
Similar to the SL method of playing games, save it at a certain moment, then load it again after the operation is completed, and return to the previous state.
Its implementation can be said to be a simplified version of a single instance, divided into two parts: activation and uninstallation.
active () {
// Cache the active state of the sandbox
this . windowSnapshot = {};
for ( const item in window ) {
this . windowSnapshot [item] = window [item];
}
Object . keys ( this . modifyMap ). forEach ( p => {
window [p] = this . modifyMap [p];
})
}
inactive () {
for ( const item in window ) {
if ( this . windowSnapshot [item] !== window [item]) {
// Record changes
this . modifyMap [item] = window [item];
// Restore window
window [item] ] = this . windowSnapshot [item];
}
}
}
When activate, traverse windowthe variables on , save as windowSnapshot
when , deactivatetraverse windowthe variables on again, windowSnapshotcompare them with respectively, save the different ones in modifyMap, and windowrestore the variables
of when the application is switched again, you can modifyMaprestore the variables of on the window to achieve A sandbox switch.
class Sandbox {
private windowSnapshot
private modifyMap
activate : () => void ;
deactivate : () => void ;
}
const sandbox = new Sandbox ();
sandbox.activate ( );
// Execute arbitrary code
sandbox.deactivate ( );
This solution is much more complicated to implement in actual projects, and its comparison algorithm needs to consider many situations. For example, for window.a.b.c = 123this modification or the modification of the prototype chain, it is impossible to roll back to the global state before the application is loaded. Therefore, this solution is generally not the first choice. It is a downgrade treatment for old browsers.
This downgrade scheme is also available in qiankun, and is called SnapshotSandbox.
iframe based sandbox
As mentioned above, as an implementation method of micro front-end, it also has its unique role iframein the sandbox .iframe
const iframe = document . createElement ( 'iframe' , { url : 'about:blank' });
const sandboxGlobal = iframe.contentWindow ; sandbox
( sandboxGlobal);
Note: Only iframes in the same domain can retrieve the corresponding contentWindow. Therefore, it is necessary to provide an empty same-domain URL of the host application as the initial loading URL of this iframe. According to the HTML specification, this URL uses about:blank to ensure that the same domain is guaranteed, and resource loading will not occur.
class SandboxWindow {
constructor ( options, context, frameWindow ) {
return new Proxy (frameWindow, {
set ( target, name, value ) {
if ( Object . keys (context). includes (name)) {
context[name] = value;
}
target[name] = value;
},
get ( target, name ) {
// Prioritize using shared objects
if ( Object . keys (context). includes (name)) {
return context[name];
}
if ( typeof target[name] === 'function' && /^[az]/ . test (name)) {
return target[name]. bind && target[name]. bind (target);
} else {
return target[name];
}
}
});
}
// ...
}
const iframe = document . createElement ( 'iframe' , { url : 'about:blank' });
document . body . appendChild (iframe);
const sandboxGlobal = iframe. contentWindow ;
// Variables that need to be shared globally
const context = { document : window . document , history : window . history };
const newSandBoxWindow = new SandboxWindow ({}, context, sandboxGlobal);
// newSandBoxWindow.history global object
// newSandBoxWindow.abc is 'abc' sandbox environment global variable
// window .abc is undefined
To summarize, iframethe following features can be achieved using the sandbox:
- Global variable isolation, such as ,
setTimeoutdifferent version isolationlocationreact - Routing isolation, applications can implement independent routing or share global routing
- Multi-instance, multiple independent micro-applications can run simultaneously
- Security policy can configure micro-application restrictions on resource loading
Cookie.localStorage
The sandbox solution iframeis better, but there are still the following problems:
- Compatibility issues: There may be differences in implementation solutions between different browsers, which may lead to compatibility issues.
- additional performance overhead
- Compared with other solutions, communication methods between applications are more troublesome
Sandbox based on ShadowRealm
ShadowRealmThe proposal provides a new mechanism to execute code in the context of new global objects and JavaScriptbuilt-in assemblies .JavaScript
const sr = new ShadowRealm (); // Sets a new global within the ShadowRealm only sr.evaluate( 'globalThis.x = "my shadowRealm"' ); globalThis. x = "root" ; // const srx = sr.evaluate( 'globalThis.x' ); srx; // "my shadowRealm" x; // "root"
In addition to directly pointing to the string code, you can also reference the file for execution:
const sr = new ShadowRealm (); const redAdd = await sr. importValue ( './inside-code.js' , 'add' ); let result = redAdd ( 2 , 3 ); console . assert (result === 5 );
Click here to view detailed introduction
Back to the topic, ShadowRealmthere are many restrictions on security, and there is a lack of some information interaction methods. Finally, its compatibility is also a major pain point:
As of the current Chromeversion 117.0.5938.48, this API is not supported, and we still need polyfillto use it.
Based on VM sandbox
VM sandboxing uses a module similar nodeto vm, by creating a sandbox and then passing in the code that needs to be executed.
const vm = require ( 'node:vm' );
const x = 1 ;
const context = { x : 2 };
vm. createContext (context); // Contextify the object.
const code = 'x += 40; var y = 17;' ;
// `x` and `y` are global variables in the context.
// Initially, x has the value 2 because that is the value of context.x .
vm. runInContext (code, context);
console . log (context. x ); // 42
console . log (context. y ); // 17
console . log (x); // 1; y is not defined.
vmAlthough nodeit has been implemented in sandbox, it does not play a big role in the micro-front-end implementation of front-end projects.
Summarize
This article lists a variety of sandbox implementation solutions. In the current front-end field, there are various sandbox implementations. Currently, there is no perfect solution. It is more about adopting suitable solutions in suitable scenarios.
