One, the introduction

What is a micro front end?

If you already know about the micro front end, you can skip this section and briefly introduce the micro front end. Micro front end is to make the micro front end moreA differentiationA technical solution similar to back-end microservices, with three shown in the figure belowCan build test deployments independentlyAnd can beThe incremental upgradetheDifferent technology stacksApplications that can be integrated into a pedestal application and displayed together.

Micro-front-end is a kind of technical means and method strategy for multiple teams to jointly build modern Web applications by publishing functions independently.

The micro front-end architecture has the following core values:

  • Technology stack independence

The main framework does not restrict access to the technology stack of the application, and the micro-application has complete autonomy

  • Independent development, independent deployment

The micro application warehouse is independent, the front and back ends can be developed independently, and the main framework automatically completes synchronous update after deployment

  • The incremental upgrade

In the face of a variety of complex scenarios, it is often difficult for us to do a full amount of technology stack upgrade or reconstruction of an existing system, and micro front end is a very good means and strategy to implement progressive reconstruction

  • Standalone runtime

The state is isolated between each microapplication and the runtime state is not shared

Demonstrate a micro front-end project, where menu and map are micro applications, menu is Vue project, map is H5 project, map can be run independently, integrated into the base when the original entrancehtmlWill be converted into adiv.htmlIn thecssIt’s going to be converted tostyle.jsIt’s going to be converted to a string and passedevalThe function executes directly.

What problem does the micro front end solve?

The micro front-end architecture is designed to solve the problem of unmaintainability when a monolithic application evolves from a common application to a Frontend Monolith due to the increase and change of the number of people and teams involved in a relatively long time span. This type of problem is especially common in enterprise Web applications.

How to implement a micro front end?

The technical problems to be solved in the implementation of micro front end are as follows:

  1. Applications access
  2. Application gateways
  3. Application isolation
  4. Style isolation
  5. Application of communication
  6. Application of routing

Why Qiankun?

  1. Some of the problems encountered in building micro-front-end systems using Single SPA or other micro-application frameworks, such asStyle isolation,JS sandbox,Resource preloading,JS side effects handlingAnd so on all the ability you need built-in arrivedqiankuninside
  2. So far, there have been about 200+ apps in useqiankunTo plug in their own micro front end architecture.qiankunIt has been tested with a number of online systems both inside and outside Ant, so it is a reliable production-usable solution.

In just over a year, Qiankun has become one of the most popular micro front-end frameworks. Although the source code is updated all the time, its core technologies remain the same: JS sandbox, CSS style isolation, application HTML entry access, application communication, application routing, and so on. Next, the design and implementation of several technologies will be explained in detail through the way of demo.

II. Design and implementation of JS sandbox isolation

2.1 Introduction to JS Sandbox

The sandbox has a set of global Windows for the main application, and a set of private global Windows for the child applications. All actions of the child applications are only valid in the new global context. The child applications are separated from the main application by boxes. As a result, when the main application loads children, there is no mutual contamination of JS variables, JS side effects, overwriting of CSS styles, etc. The global context of each child is independent.

2.2 Snapshot Sandbox – Snapshot Sandbox

A snapshot sandbox is a snapshot that is taken when an application sandbox is mounted and unmounted, and the snapshot is used to restore the environment when the application is switched.

  • The demo presentation

  • The implementation code
// child application A mountSnapshotSandbox(); window.a = 123; Console. log(' A: after the snapshot sandbox is mounted ', window.a); // 123 unmountSnapshotSandbox(); Console. log(' snapshot sandbox uninstalled a:', window.a); // undefined mountSnapshotSandbox(); Console. log(' A: after the snapshot sandbox is mounted again ', window.a); / / 123
// function iter(obj: object, callbackFn: (prop:)); // function obj: object, callbackFn: (prop:) any) => void) { for (const prop in obj) { if (obj.hasOwnProperty(prop)) { callbackFn(prop); }}} // MountSnapshotSandbox () {this.WindowSnapshot = {} as Window; iter(window, (prop) => { this.windowSnapshot[prop] = window[prop]; }); ForEach ((p: any) => {window[p] = this. modifyPropsmap [p]; }); } // unmountSnapshotSandbox() {this. modifyPropSMap = {}; iter(window, (prop) => { if (window[prop] ! == this.windowsnapshot [prop]) {this.modifyPropSMAP [prop] = window[prop]; window[prop] = this.windowSnapshot[prop]; }}); }
  • advantages

    • Compatible with almost all browsers
  • disadvantages

    • You cannot have multiple runtime snapshot sandboxes at the same time. Otherwise, the modified records on the window will be messy and you can only run one single-instance microapplication per page

2.3 Proxysandbox

When there are multiple instances, such as A, B two application, A application will live in A sandbox, B live in B application sandbox, A and B can not interfere with each other such A sandbox is acting the sandbox, the sandbox implementation approach is through the proxy ES6, features through agent.

ProxyObject is used to create a proxy for an object to intercept and customize basic operations (such as property lookup, assignment, enumeration, function calls, and so on).


Simply put, you can impose a layer of interception on the target object. So whatever you do to the target object, you have to go through this layer of interception, right

  • Proxy vs Object.defineProperty

Object.defineProperty can also intercept and customize basic operations, so why use a Proxy? Proxy can solve the following problems:

  1. Deleting or adding object properties cannot be listened on
  2. Array changes cannot be listened for(vue2Exactly in useObject.definePropertyHijack property,watch[Bug MC-108201] – Unable to detect array changes in the
  • The demo presentation

Simple version

Actual scenario version

  • The implementation code
  1. Simple version
const proxyA = new CreateProxySandbox({}); const proxyB = new CreateProxySandbox({}); proxyA.mountProxySandbox(); proxyB.mountProxySandbox(); (function(window) { window.a = 'this is a'; Console. log(' proxy sandbox a:', window.a); // undefined })(proxyA.proxy); (function(window) { window.b = 'this is b'; Console. log(' proxy sandbox b:', window.b); // undefined })(proxyB.proxy); proxyA.unmountProxySandbox(); proxyB.unmountProxySandbox(); (function(window) {console.log(' proxy sandbox a:', window.a); // undefined })(proxyA.proxy); (function(window) {console.log(' proxy sandbox b:', window.b); // undefined })(proxyB.proxy);
  1. Real-life version
<! DOCTYPE HTML > < HTML lang="en"> <body data-qiankun-a > <h5> </h5> <button onClick ="unmountA()"> < sandbox onClick ="unmountA()"> <button Onclick ="mountB()"> proxy sandbox mode mounts B application </button> <button onclick="unmountB()"> proxy sandbox mode unmounts B application </button> <script src="proxySandbox.js"></script> <script src="index.js"></script> </body> </html>

A application JS. All JS loaded during the mount of A application will run in A application sandbox (proxya. proxy)

// a.js window.a = 'this is a'; Console. log(' proxy sandbox 1a :', window.a);

All Js loaded during the mount of B’s application will run in the sandbox of B’s application (proxyb.proxy)

// b.js window.b = 'this is b'; Console. log(' proxy sandbox b:', window.b);
const proxyA = new CreateProxySandbox({}); const proxyB = new CreateProxySandbox({}); function mountA() { proxyA.mountProxySandbox(); fetch('./a.js') .then((response) => response.text()) .then((scriptText) => { const sourceUrl = `//# sourceURL=a.js\n`; window.proxy = proxyA.proxy; eval(`; (function(window, self){with(window){; ${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy); `)}); } function unmountA() { proxyA.unmountProxySandbox(); fetch('./a.js') .then((response) => response.text()) .then((scriptText) => { const sourceUrl = `//# sourceURL=a.js\n`; eval(`; (function(window, self){with(window){; ${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy); `)}); } function mountB() { proxyB.mountProxySandbox(); fetch('./b.js') .then((response) => response.text()) .then((scriptText) => { const sourceUrl = `//# sourceURL=b.js\n`; window.proxy = proxyB.proxy; eval(`; (function(window, self){with(window){; ${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy); `)}); } function unmountB() { proxyB.unmountProxySandbox(); fetch('./b.js') .then((response) => response.text()) .then((scriptText) => { const sourceUrl = `//# sourceURL=b.js\n`; eval(`; (function(window, self){with(window){; ${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy); `)}); }

Proxy sandbox code

// proxySandbox.ts function CreateProxySandbox(fakeWindow = {}) { const _this = this; _this.proxy = new Proxy(fakeWindow, { set(target, p, value) { if (_this.sandboxRunning) { target[p] = value; } return true; }, get(target, p) { if (_this.sandboxRunning) { return target[p]; } return undefined; }}); _this.mountProxySandbox = () => { _this.sandboxRunning = true; } _this.unmountProxySandbox = () => { _this.sandboxRunning = false; }}
  • advantages
  1. Multiple sandboxes can be run simultaneously
  2. Does not pollute the Window environment
  • disadvantages
  1. Not compatible with ie
  2. Pass on the global scopevarfunctionDeclared variables and functions cannot be hijacked by the proxy sandbox because of the proxy objectProxyOnly properties that exist on the object can be identified byvarfunctionThe variable declared by the declaration is opened to a new address and naturally cannot beProxyTo hijack, as
const proxy1 = new CreateProxySandbox({}); proxy1.mountProxySandbox(); (function(window) { mountProxySandbox(); var a = 'this is proxySandbox1'; function b() {}; Console. log(' A, B :', window.a, window.b after agent sandbox 1 is mounted); // undefined undefined })(proxy1.proxy) proxy1.unmountProxySandbox(); (function(window) {console.log(' A, B :', window.a, window.b); // undefined undefined })(proxy1.proxy)

One solution is to not declare global variables and functions without var and function, such as

var a = 1; // fail a = 1; // valid window.a = 1; / / effective function (b) b = {} / / failure () = > {} / / effective window. B = () = > {} / / is valid

Third, the design and implementation of CSS isolation

3.1 Introduction to CSS Isolation

When there are multiple microapplications on A page, to ensure that the styles applied by A do not affect the styles applied by B, you need to isolate the styles applied.

3.2 Dynamic Stylesheet

3.3 Engineering means – BEM, CSS Modules, CSS in JS

Through a series ofThe constraintandDifferent class names are generated at compile time,JS handles CSS to generate different class namesTo solve the isolation problem

3.4 Shadow DOM

Shadow DOMAllows will be hiddenDOMTree attached to regularDOMIn the tree — it takesshadow rootThe node is the starting root node. Below the root node, it can be any element, and the regularDOMElement like, hiddenDOMStyles and the restDOMIt’s completely segregated, sort ofiframeThe style isolation effect of.

Mobile terminal framework
IonicComponent style isolation is adopted by the
Shadow DOMSolution to ensure that the styles of the same component do not clash.


  • The demo presentation

  • Code implementation
<! DOCTYPE HTML > < HTML lang="en"> <body data-qiankun-a > <h5> </h5> <p class="title"> </p> <script SRC =" scopedcss.js "></script> <script SRC ="index.js"></script> </body> </ HTML >
// index.js
var bodyNode = document.getElementsByTagName('body')[0];
openShadow(bodyNode);
// scopedCss.js
function openShadow(domNode) {
  var shadow = domNode.attachShadow({ mode: 'open' });
  shadow.innerHTML = domNode.innerHTML;
  domNode.innerHTML = "";
}
  • advantages
  1. Isolate CSS styles completely
  • disadvantages
  1. When you use some popover component (which in many cases is added to document.body by default), it skips the shadow boundary, runs into the main application, and the style is lost

3.5 Runtime Transformation Styles – Runtime CSS Transformer

Dynamic runtime to change the CSS, such as A of the application of A style p.t itle, after conversion into A div [data – qiankun – A] p.t itle, div [data – qiankun – A] is the application of the outer container node, Make sure that the style applied by A is only valid under div[data-qiankun-a].

  • The demo presentation

  • Code implementation
<! -- index.html --> <html lang="en"> <head> <style> p.title { font-size: 20px; } </style> </head> <body data-qiankun-a >< p class="title" </p> <script SRC =" scopedcss.js "></script> <script> var styleNode = document.getElementsByTagName('style')[0]; scopeCss(styleNode, 'body[data-qiankun-A]'); </script> </body> </html>
// scopedCSS.js function scopeCss(styleNode, prefix) { const css = ruleStyle(styleNode.sheet.cssRules[0], prefix); styleNode.textContent = css; } function ruleStyle(rule, prefix) { const rootSelectorRE = /((? :[^\w\-.#]|^)(body|html|:root))/gm; let { cssText } = rule; // Bind selector, a,span,p,div {... } cssText = cssText.replace(/^[\s\S]+{/, (selectors) => selectors.replace(/(^|,\n?) ([^,] +)/g, (item, p, s) = > {/ div/binding, body, span {... } if (rootSelectorRE.test(item)) { return item.replace(rootSelectorRE, (m) => {// Do not lose the body, HTML or *:not(:root) const WhitePrevchars = [',', '(']; if (m && whitePrevChars.includes(m[0])) { return `${m[0]}${prefix}`; } // Replace the root selector with the prefix return prefix; }); } return `${p}${prefix} ${s.replace(/^ */, '')}`; })); return cssText; }
  • advantages
  1. Supports most style isolation requirements
  2. To solve theShadow DOMLoss of root node caused by the scheme
  • disadvantages
  1. Reloading styles at run time can incur some performance penalty

Four, clear JS side effect design and implementation

4.1 Introduction to the side effects of removing JS

When a child uses a global API in the sandbox, such as window.addEventListener, setInterval, etc., that needs to listen asynchronously, make sure that the child removes the listener event as well, otherwise it will cause side effects for other applications.

4.2 Realize to clear the side effects of JS operation

  • The demo presentation

  • Code implementation
<! DOCTYPE HTML > < HTML lang="en"> <body> <h5> </h5> <button onClick ="unmountSandbox()" </button> <button onClick ="unmountSandbox(true)"> Unmountsandbox and close side effects </button> <button onclick="unmountSandbox()"> </button> <script SRC =" proxysandbox.js "></script> <script src="patchSideEffects.js"></script> <script src="index.js"></script> </body> </html>
let mountingFreer; const proxy2 = new CreateProxySandbox({}); function mountSandbox() { proxy2.mountProxySandbox(); Function (window, self) {with(window) {// MountingFreer = patchsideEffects (window); window.a = 'this is proxySandbox2'; Console. log(' A: after agent sandbox 2 is mounted ', window.a); Window.addEventListener ('resize', () => {console.log('resize'); }); SetInterval (() => {console.log('Interval'); }, 500); } }).bind(proxy2.proxy)(proxy2.proxy, proxy2.proxy); } /** * @Param isPatch whether to disable side effect */ function unmountSandBox (isPatch = false) {proxy2.mountProxySandBox (); Console. log(' agent sandbox 2 uninstalled a:', window.a); // undefined if (isPatch) { mountingFreer(); }}
// patchSideEffects.js const rawAddEventListener = window.addEventListener; const rawRemoveEventListener = window.removeEventListener; const rawWindowInterval = window.setInterval; const rawWindowClearInterval = window.clearInterval; function patch(global) { const listenerMap = new Map(); let intervals = []; global.addEventListener = (type, listener, options) => { const listeners = listenerMap.get(type) || []; listenerMap.set(type, [...listeners, listener]); return rawAddEventListener.call(window, type, listener, options); }; global.removeEventListener = (type, listener, options) => { const storedTypeListeners = listenerMap.get(type); if (storedTypeListeners && storedTypeListeners.length && storedTypeListeners.indexOf(listener) ! == -1) { storedTypeListeners.splice(storedTypeListeners.indexOf(listener), 1); } return rawRemoveEventListener.call(window, type, listener, options); }; global.clearInterval = (intervalId) => { intervals = intervals.filter((id) => id ! == intervalId); return rawWindowClearInterval(intervalId); }; global.setInterval = (handler, timeout, ... args) => { const intervalId = rawWindowInterval(handler, timeout, ... args); intervals = [...intervals, intervalId]; return intervalId; }; return function free() { listenerMap.forEach((listeners, type) => [...listeners].forEach((listener) => global.removeEventListener(type, listener)), ); global.addEventListener = rawAddEventListener; global.removeEventListener = rawRemoveEventListener; intervals.forEach((id) => global.clearInterval(id)); global.setInterval = rawWindowInterval; global.clearInterval = rawWindowClearInterval; }; } function patchSideEffects(global) { return patch(global); }

To be continued

In the next issue, we will continue to explore the micro-front-end technology from the design and implementation of application access, communication design and implementation, application routing design and implementation of the design and implementation, stay tuned, if you find this article helpful, please click the like support, your support is even updated drop power!

References:

Micro Front-End Serial 6/7: Micro Front-End Frame – Qiankun Dafa Good

Qiankun official documentation