directory
- preface
- How to quickly build a micro front end application with Qiankun
- Manually load microapplications/components
- Commonly used API
- How does Qiankun solve the problems encountered in the use of single-SPA
- Child application loading – Html Entry vs Js Entey
- Js isolation
- CSS isolation
- Subapplication uninstallation side effects cleaning
- The status of the sub-application is restored
- Subapplication communication
- Child application preloading
- The whole process of qiankun’s work
- conclusion
preface
At present, single-SPA is a relatively popular and mature micro front-end solution. It can provide us with user experience similar to single-page application, achieve the coexistence of multiple applications and technology stack independence, and help us improve development efficiency through its rich ecology. However, in actual projects, faced with common problems such as sub-application loading, js and CSS isolation between applications, cleaning up the side effects left by sub-application switching, state recovery of sub-applications and communication between sub-applications, and pre-loading of sub-applications, the single-SPA framework itself does not provide corresponding solutions. It’s not very developer-friendly for developers to write their own code.
To address these issues, Qiankun offers a new micro front-end solution. Based on single-SPA, Qiankun has done secondary development, providing universal sub-application loading, communication and pre-loading scheme, and realized JS and CSS isolation between applications, side effect cleaning and state recovery through technical means, helping developers to achieve a micro front-end application more easily and quickly.
Let’s take a look at the usage of Qiankun and how it solves the problems mentioned above.
How to quickly build a micro front end application with Qiankun
As Qiankun is a secondary development based on single-SPA, the usage of Qiankun is basically the same as single-SPA, which is divided into application mode and parcel mode.
The Application pattern works based on routing and divides applications into two categories: base applications and sub-applications. The base application needs to maintain a routing registry and switch sub-applications according to the changes of routes. Sub-applications are independent applications that need to provide a lifecycle approach for the base application. Parcel mode is the opposite of Application mode in that it has nothing to do with routing and the child application switching is controlled manually.
Next, we go through the official example github.com/umijs/qiank… To see how the Application pattern is used.
The sample project structure is shown below:
After the example is started, the effect is as follows:
As with single-SPA, we need to modify the base application and sub-application separately.
-
Base application modification
The application is basically the same as the single SPA. Build a routing registry and then use the registerMicroApps method provided by Qiankun to register sub-applications according to the routing registry. Finally, execute the start method to start Qiankun.
The specific code is as follows:
import { registerMicroApps, start } from 'qiankun'; . const apps = [ { name: 'react16', entry: '//localhost:7100', container: '#subapp-viewport', activeRule: '/react16', ... }, { name: 'react15', entry: '//localhost:7102', container: '#subapp-viewport', activeRule: '/react15', ... }, { name: 'vue', entry: '//localhost:7101', container: '#subapp-viewport', activeRule: '/vue', ... }, { name: 'angular9', entry: '//localhost:7103', container: '#subapp-viewport', activeRule: '/angular9', ... }, { name: 'purehtml', entry: '//localhost:7104', container: '#subapp-viewport', activeRule: '/purehtml', }, { name: 'vue3', entry: '//localhost:7105', container: '#subapp-viewport', activeRule: '/vue3', ... }, ] const lifeCycles = { beforeLoad: [ app => { console.log('[LifeCycle] before load %c%s', 'color: green;', app.name); }, ], beforeMount: [ app => { console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name); }, ], afterUnmount: [ app => { console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name); }, ], } registerMicroApps(apps, lifeCycles); . start();Copy the code
The structure of the routing registry in the Qiankun base application is similar to that of single-SPA. The configuration items for each sub-application need to be specified as name, Entry, Container, and activeRule.
-
name
The unique identifier of the sub-application is a string that cannot be repeated.
-
entry
The entry of the child application is generally a URL string, that is, the access address of the child application, such as localhost:8080;
-
container
The node mounted by the child application can be a domElement instance or a DOM element ID string.
-
activeRule
The condition for activation of a subapplication, usually a function. When a routing change occurs, the base application traverses the registered subapplications, using the activeRule condition for the subapplication to find the one that needs to be activated.
-
-
Sub-application modification
The transformation of sub-application, and single-SPA is exactly the same, involving two aspects:
- Add life cycle methods to entry file index.js – mount, unmount, update, etc.
- Packaging construction and transformation;
The subapplication entry file -index is modified as follows:
. function render() { ... } export function mount(props) { ... render(); } export function unmount(props) {... }...Copy the code
The package construction transformation is as follows:
// vue.config.js module.exports = { configureWebpack: { ... publicPath: 'http://localhost:7101' output: { library: 'vue', libraryTarget: 'umd' }, ... }}Copy the code
After completing the transformation of the base application and the sub-application, we can start the base application and switch the sub-application by modifying the URL.
After learning about the Application mode in Qiankun, let’s learn more about the Parcel mode.
Parcel mode is routing independent and gives us the opportunity to manually mount/uninstall/update the child application using loadMicroApp provided by Qiankun. The use of loadMicroApp is described in detail on the official website and will not be illustrated here.
Commonly used API
The apis for Qiankun are already detailed in the official documentation, so I won’t go into them in this article. The apis of Qiankun are listed below. You can check them out on the website.
- registerMicroApps
- start
- setDefaultMountApp
- runAfterFirstMounted
- loadMicroApp
- prefetchApps
- initGlobalState
How does Qiankun solve the problems encountered in the use of single-SPA
Child application loading – Html Entry vs Js Entry
When using single-SPA, the most critical step is to determine the child application’s loading method – loadAppFunc – when creating the routing registry.
In general, we bundle all the static resources of the child application into a JS bundle, and then load and execute the JS bundle in loadAppFunc to obtain the lifecycle methods provided by the child application. Then execute the mount method of the child application to load the child application. This approach is called Js Entry.
Js Entry mode is troublesome to use, which can be shown as:
-
First of all, different subapplications may have different js bundle names, and updating the subapplication will cause the js bundle name to change from time to time. This makes it possible to define loadAppFunc, You must be able to dynamically retrieve the child JS bundle name (the child JS bundle name must be saved for loadAppFunc to retrieve).
-
Second, with all the static resources bundled together, optimizations like CSS extraction, parallel resource loading, and first-screen loading are eliminated.
-
Finally, in order to make the on-demand loading function of the sub-application effective, we need to modify the corresponding configuration in the sub-application packaging process to complete the js resource path of the sub-application.
Qiankun uses a new subapp loading method – HTML Entry, to help us solve the pain of JS entry.
With Qiankun, creating the routing registry is still the most critical step. But the difference is that we no longer need to define the child application loading method – loadAppFunc, only need to determine the child application entry – entry, child application loading method – loadAppFunc, Qiankun will help us implement.
Qiankun implements loadAppFunc based on native FETCH. In short, when loading a child application, Qiankun uses the FETCH method to fetch the HTML content string of the child application according to the URL specified in the entry configuration item. Then, qiankun parses the HTML content and collects the style and JS script of the child application. Install the style and execute the JS script to get the lifecycle method of the child application, and then execute the mount method of the child application.
The whole detailed process can be understood through the following flowchart:
Through the above process, we can get all the js scripts of the child application and the parsed HTML template, and then add the parsed HTML template to the node specified by container, and then manually trigger the execution of all THE JS scripts. This allows you to get the child application’s lifecycle method, mount, and mount the child application.
Compared to Js Entry, Html Entry has the following advantages:
-
There is no need to bundle the child application into a JS bundle, and CSS extraction, parallel resource loading, and first-screen loading optimizations can be used as usual (fetching the content of the external stylesheet through the FETCH is parallel);
-
Do not care about the child application JS file name and the child application after the update of JS file name changes;
-
There is no need to modify the packaging configuration for the on-demand loading function.
Combined with the flowchart of Html Entry above, let’s take a look at how Qiankun works:
Finally, a question. Why did Qiankun convert external stylesheets into internal stylesheets and add them to HTML templates and collect js scripts for manual execution? What’s the point of doing that?
In fact, the reason why Qiankun will do this is to solve the js and CSS isolation through technical means. Next we will be in the JS isolation, CSS isolation section specific analysis.
Js isolation
When using a micro front end solution, we want to ensure that global variables between applications do not affect each other. For example, child application A defines A new property globalState for the Window object, and child application B defines A new property globalState for the Window object. Then we need to make sure that subapplication A and subapplication B get the globalState they define.
In single-SPA, we usually do js isolation by adding named prefixes by convention. For example, the child application A defines A new property for the window object -a_GlobalState. The child application B defines a new property B_globalState for the Window object. This method is not only inefficient, but also prone to bugs. Compared to this approach, Qiankun provides a better way — sandbox to help us achieve JS isolation.
A sandbox, a computer term, is a security mechanism in the field of computer security that provides an isolated environment for running programs. Using a sandbox, Qiankun provides an isolated runtime environment for each sub-application, ensuring that the js code of the sub-application is executed using global variables that are unique to the current sub-application.
It’s easy to understand how the Sandbox works in Qiankun.
-
First, create a unique window object for each child application.
-
The second step is to manually execute the js script of the child application, take the class window object as a global variable, and read and write the global variable on the class window object;
In this step, all javascript script strings parsed during the HTML entry phase are first wrapped in an IIFE – Execute immediately function and then triggered manually via the eval method, as follows:
var fakeWindowA = { name: 'appA'}; Var fakeWindowB = {name: 'appB'}; Var jsStr = 'console.log(name)'; var jsStr = 'console.log(name)'; Var codeA = '(function(window){with(window){${jsStr}}})(fakeWindowA)'; var codeA = '(function(window){with(window){${jsStr}}})(fakeWindowA)'; var codeB = `(function(window){with(window){${jsStr}}})(fakeWindowB)`; eval(codeA); // appA eval(codeB); // appBCopy the code
In this way, through the above two steps, the global variables used in the JS code execution of each child application are unique to the current child application and will not affect each other.
Now that we know how sandbox works, let’s take a look at how Qiankun implements sandbox. The relevant source code is as follows:
class ProxySandbox { ... name: string; // Sandbox name: proxy: WindowProxy; // Proxy object sandboxRunning: Boolean; // Activate the sandbox by using the active() method when the child application is mounted. this.sandboxRunning = true; } // Deactivating the sandbox. Inactive () {... this.sandboxRunning = false; } constructor(name) {// use the name of the child application as the sandbox name this.name = name; const self = this; // Get the native window object const rawWindow = window; Const fakeWindow = {}; // Use a proxy to block read/write operations in fakeWindow. // Use a setTimeout method in a child application, but there is no setTimeout method in fakeWindow. This.proxy = new proxy (fakeWindow, {set(target, key, Value) {if (self.sandboxrunning) {// The sandbox has been activated... FakeWindow target[key] = value; fakeWindow target[key] = value; } }, get(target, key) { ... // Return key in target from rawWindow; // Return key in target from rawWindow; target[key] : rawWindow[key]; },... }); }}Copy the code
In the sandbox implementation, qiankun constructs an empty object, fakeWindow, as a fakeWindow object, and then creates a Proxy object on top of the fakeWindow object. This proxy will eventually be used as a global variable in the execution of the child application JS code. With this proxy, we can easily hijack global variable reads and writes in JS code. When a child application needs to add (modify) a global variable, add it directly in fakeWindow. When a child application needs to read a property (method) from a global variable, it gets it first from fakeWindow, and then from the native window if it doesn’t.
Of course, for browsers that do not support proxy, Qiankun also provides corresponding solutions. The specific implementation is as follows:
class SnapshotSandbox { ... name: string; // Subapplication name: proxy: WindowProxy; // Proxy object sandboxRunning: Boolean; // Check whether the sandbox has enabled private windowSnapshot! : Window; Private modifyPropsMap: Record<any, any> = {}; // constructor(name: string) {this.name = name; // constructor(name: string) {this.name = name; this.proxy = window; this.type = SandBoxType.Snapshot; This. WindowSnapshot = {} as Window; // Iterate over the properties of the window object, Iter (window, (prop) => {this.windowsnapshot [prop] = window[prop]; }); Object.keys(this.modifyPropsMap).foreach ((p: any) => {window[p] = this.modifyPropsMap[p]; }); this.sandboxRunning = true; } // Inactive method of the sandbox inactive() {this.modifyPropsMap = {}; iter(window, (prop) => { if (window[prop] ! == this.windowSnapshot[prop]) {this.modifyPropsMap[prop] = window[prop]; window[prop] = this.windowSnapshot[prop]; }}); . this.sandboxRunning = false; }}Copy the code
A SnapshotSandbox is called a SnapshotSandbox. The fakeWindow object will be used as a global variable in the js code of the child application. Because there is no support for proxies (or setters/getters), qiankun copied all properties and methods from the native window to fakeWindow so that children can read global variables in fakeWindow.
In addition to ProxySandbox, SnapshotSandbox, Qiankun also provides another sandbox – SingularProxySandbox, singleton sandbox. The singleton sandbox is created automatically when the singleton mode is enabled (only one child can be mounted to the parent application). SingularProxySandbox is also implemented based on proxy. However, unlike ProxySandbox, SingularProxySandbox modifies properties directly on the native Window object, which results in global variable interactions between the parent and child applications. Currently, Qiankun uses ProxySandbox by default, whether it is a single application or multiple sub-applications. SingularProxySandbox will only be used if we display {sandbox: {loose: true}} in the start method.
In the latest version of Qiankun, SingularProxySandbox will be phased out, and ProxySandbox will be used instead.
Because sandbox is used in Qiankun, each sub-application will have its own global variable in the project, and it will not change the native Window object. Therefore, the parent application and multiple sub-applications can ensure that the global variable does not affect each other.
Finally, let’s explain why HTML Entry parsing subapplications of HTML template strings requires all js scripts to be collected and manually triggered. The reason for this is to dynamically modify the global object of js script execution, so that each child application’s global object is a separate fakeWindow, rather than a native window.
CSS isolation
Like JS isolation, Qiankun also implemented CSS isolation through technical means.
There are two ways to do this: strict style isolation and scoped style isolation.
-
Strict style isolation
To enable strictStyleIsolation, we need to add the strictStyleIsolation configuration when using the start method, which is:
import { start } from 'qiankun'; start({ sandbox: { strictStyleIsolation: true } }) Copy the code
Strict style isolation, which is off by default. If this function is enabled, the configuration must be displayed.
Strict style isolation is implemented based on the Web Component’s Shadow Dom. Shadow Dom allows you to attach a hidden, independent Dom to one Dom element after another, keeping the element private without fear of conflict with the rest of the document.
The specific implementation is as follows:
. if (appElement.attachShadow) { shadow = appElement.attachShadow({ mode: 'open' }); } else { // createShadowRoot was proposed in initial spec, which has then been deprecated shadow = (appElement as any).createShadowRoot(); } shadow.innerHTML = innerHTML; .Copy the code
AppElement is the DOM node mounted by the child application, and innerHTML is the Html template string generated by parsing the Html Entry. The Shadow DOM automatically implements style isolation between parent applications and multiple child applications.
-
Scoped style isolation
Scoped style isolation is another way to implement style isolation in Qiankun.
import { start } from 'qiankun'; start({ sandbox: { experimentalStyleIsolation: true } }); Copy the code
Scoped style isolation, implemented based on property selectors, is as follows:
div["data-qiankun=vue"] div { background-color: green; } Copy the code
An HTML entry parsed HTML template string is wrapped in a div before being added to the node specified by the Container. A data-qian attribute is added to the div node with the value of the name attribute of the child application. It then iterates through all the style nodes in the HTML template string and prefixes the styles in the internal style sheet to div[“data-qiankun= XXX “]. The name attribute value of the Qiankun neutron application is unique, so that style isolation can be achieved through the restriction of the attribute selector.
At this point, we can explain why HTML Entry parsers need to convert external stylesheets to internal stylesheets when applying HTML template strings. Without transformation, we would not be able to add attribute selector prefixes to styles in external stylesheets, and thus would not be able to achieve style isolation.
Strict style isolation, even for external stylesheets, can be achieved, so there is no need to convert external stylesheets to internal stylesheets. In addition, strict style isolation and scoped style isolation cannot be used together, and strict style isolation takes precedence when the corresponding configuration items for both are true.
In actual projects, we often encounter situations where style is dynamically added, such as no CSS extraction and the use of Styled components in react application. In general, these dynamically add style through the document. The head. The way the appendChild added to the head node. If qiankun is enabled with strict style isolation or Scoped style isolation, will CSS isolation fail?
The answer is no.
Qiankun added style for dynamic situation, also did the corresponding processing. In order to learn the son application dynamically add style of operation, qiankun of the document. The head. The hijack the appendChild method for operation, specific as follows:
/ / the appendChild method of native const rawHeadAppendChild = document. The head. The appendChild; / / rewrite the native method document. Head. The appendChild = function (newChild) {if (newChild. TagName = = = 'STYLE') {/ / to do STYLE node processing... }... // Find the root DOM node of the HTML fragment corresponding to the child application const mountDOM =.... ; Rawheadappendchild.call (mountDOM, newChild); // add dynamic Style to the appropriate HTML fragment of the child application with native appendChild. }Copy the code
When the application calls the document. The head. The appendChild dynamically add style, will be qiankun hijacked, then add style to the application of its corresponding HTML fragment. If qiankun is configured with strict style isolation, the new style is added to the Shadow DOM, and the CSS isolation automatically takes effect. If scoped isolation is configured in Qiankun, the scoped content will be obtained before adding the style to the subapplication’s corresponding HTML fragment. Then the style content will be prefixed with div[“data-qiankun= XXX “] and CSS isolation will take effect.
Subapplication uninstallation side effects cleaning
Each child application will produce some side effects more or less during the working process, such as the timer generated by setInterval, the event registered by Widnow.addeventListener, the modification of the global variable window, the dynamic addition of DOM nodes, and so on. If these side effects are not addressed when a child application is uninstalled, it can cause a memory leak and even affect the next child application. The treatment of side effects is very important, but in the actual project, if we rely on developers to deal with it, not only time-consuming and laborious, but also can not cover everything.
Fortunately, Qiankun can help us deal with side effects when the child app uninstalls.
The first is the side effects caused by changing global variables. Because The sandbox mechanism is used in Qiankun, each child application has its own global variable in the process of working, and it does not change the window. Therefore, there is no side effect of changing the global variable window, so there is no need to deal with it, 😊.
The second is the side effect caused by dynamically adding DOM nodes. Because qiankun hijacked the document. The head. The appendChild, document. The body. The appendChild, document. Head. The insertBefore, Dom nodes that are added dynamically are automatically added to the HTML fragment corresponding to the child application. When the child application is uninstalled, the HTML fragment corresponding to the child application is automatically removed, along with the dom nodes added dynamically, 😊.
The setInterval method is used to modify the setInterval method. The setInterval method is used to modify the setInterval method.
const rawWindowInterval = window.setInterval; const rawWindowClearInterval = window.clearInterval; Function patch(global: Window) {// Collect timers defined by child applications. Let intervals: number[] = []; ClearInterval = (intervalId: number) => {intervals = intervals. Filter ((id) => id! == intervalId); return rawWindowClearInterval(intervalId); }; SetInterval = (handler: Function, timeout? : number, ... args: any[]) => { const intervalId = rawWindowInterval(handler, timeout, ... args); intervals = [...intervals, intervalId]; return intervalId; }; Return function free() {intervals ((id) => global.clearInterval(id)); return function free() {intervals ((id) => global.clearInterval(id)); global.setInterval = rawWindowInterval; global.clearInterval = rawWindowClearInterval; return noop; }; }Copy the code
By hijacking setInterval, the timer generated by the child application is collected. When the child application is uninstalled, the collected timer is automatically cleared by Qiankun.
As with setInterval, window.addeventListener raises side effects, Qiankun is by taking the native window. The addEventListener, window. The removeEventListener. All events bound to the child application are collected. When the child application is uninstalled, the collected events are automatically unbound by qiankun.
The mounting status of the child application is restored
In a real micro front end project, in addition to removing side effects when the child application is unmounted, we also need to restore the state of the child application when the child application is remounted.
When the child application is reloaded, the following states need to be restored:
- Subapplication modified global variables;
- Dynamically added styles for sub-applications;
In some cases, we need to restore the last child application modification to a global variable when the child application is reloaded. With that in mind, Qiankun’s Sandbox is a great way to make this happen. Each child application has its own fakeWindow, which is always present with the child application (even when the child application is uninstalled). All changes to global variables by the child application actually occur on fakeWindow, and the global variables are automatically restored when the child application is remounted.
In explaining how to restore dynamically added styles, let’s first use a webpack-packed subapplication as an example to explain why the subapplication needs to restore dynamically added styles when remounted.
When webPack builds an application, it turns each component in a child application into an executable function that returns the component’s export to the outside world. When the child application starts to execute the JS script for the first time, it will first execute the executable function of the component, get the export of the component and cache it. The cache of the collection component export will always exist with the child application. When the child application is remounted, the component’s export can be fetched directly from the cache without executing the component’s executable function.
In addition, if the MiniCssExtractPlugin is not used to extract the CSS, then all the CSS in the project will be converted to an executable method after being processed by csS-Loader and styleloader. Execute this method will dynamically create a style, and through the document. The head. The way the appendChild added to the page. This CSS executable is only executed when the component executable is executed, which means that the CSS executable is not triggered when the child application is remounted and the style is not dynamically added.
In summary, we need to restore the style that was dynamically added when the child application is remounted.
About that, Here’s what Qiankun did:
- First, qiankun will hijack the document. The head. The appendChild method. When the child application is mounted for the first time, dynamically adding a style is hijacked and the new style is cached. This cache will always exist with the child application.
- Second, the child application remounts, adding the previously cached style to the HTML fragment corresponding to the child application.
Subapplication communication
In terms of father-son apps and multiple sub-apps communicating with each other, Qiankun also provides a complete solution that doesn’t require developers to implement it themselves.
The usage is as follows:
Import {initGlobalState, MicroAppStateActions} from 'qiankun'; // Initialize state const Actions: MicroAppStateActions = initGlobalState(state); Actions. OnGlobalStateChange ((state, prev) = > {/ / state: changed status; Prev Status before the change console.log(state, prev); }); actions.setGlobalState(state); actions.offGlobalStateChange(); / / child application export function mount (props) {props. OnGlobalStateChange ((state, prev) = > {/ / state: after the change of state; Prev Status before the change console.log(state, prev); }); props.setGlobalState(state); }Copy the code
The communication mechanism provided by Qiankun is based on publish and subscribe model, which is very easy to understand. The master application creates a global globalState using the initGlobalState method and maintains a list of DEPs to collect subscriptions. The onGlobalStateChange method and the setGlobalState method, which modify globalState, are passed to the child application when its lifecycle method executes. The child application first binds the callback via the onGlobalStateChange method, and the callback is added to the List of DePs in golbalState. When we change globalState using the setGlobalState method, Qiankun traverses the list of dePs, triggering the collected callback in turn.
When the child application is uninstalled, the bound callback is uninstalled and removed from the dePs list.
Child application preloading
Qiankun also provides a complete solution for preloading resources. The specific usage is as follows:
import { start } from 'qiankun'; . start({ prefetch: true // prefetch: boolean | 'all' | string[] | function })Copy the code
Prefetch provides different resource preloading policies based on configuration items:
- Boolean: Whether to enable resource preloading. If this parameter is set to true, Qiankun starts to load static resources for other sub-applications after the first one is mounted.
- ‘all’: Qiankun Preloads static resources of all sub-applications immediately after the start method is executed. This parameter is not recommended.
- String []: Qiankun preloads the specified sub-app after the first sub-app is mounted.
- Function: Users can completely customize the loading time of sub-application resources.
The whole process of qiankun’s work
After understanding the usage of Qiankun and the related optimization of Qiankun on the basis of single-SPA, we will make a comprehensive combing of the whole working process of Qiankun, as follows:
conclusion
At this point, The learning of Qiankun is over. This article mainly introduces the usage of Qiankun, the whole workflow and the improvement of single-SPA in detail. If you haven’t used Qiankun or don’t know what it is, you may not be able to read this article, but you can go to the website to do a brief introduction. The length of this article is long, it takes time to read, I hope to give you help, if you have questions or mistakes, welcome to put forward. Learn together, make progress together, 😁.
References are as follows:
- qiankun
- Explore the scene limits of the micro front end
- Probably the most complete micro front end solution you’ve ever seen
- Micro front-end framework qiankun from introduction to source analysis