My first contact with microfront-end was in July 2020, and I heard about it from my colleague. Although I was not an early contact, I did promote and follow up the development and implementation of a large internal project. I also hope to share with you some of the pits I have walked through and some thoughts. All applications mentioned in this article are PC web applications. This article does not discuss mobile terminal M page.
At that time, there was not a lot of information on the Internet, baidu search results of the micro front end were very few, the universe, feibing occupied most of the space, and meituan’s internal technology salon. Who would think after half a year micro front end of the word more and more intense, a variety of solutions and technology to achieve such as bamboo shoots after a spring rain, also gradually formed a hundred schools of thought contend.
What is a micro front end
Numerous articles about microfrontends have been written online, mostly from this article on Micro Frontends. The idea of micro front end is introduced in detail. Also promoted a micro front-end implementation scheme (Web Components), with the passage of time and the practice of the micro front-end, the author has some own cognition of the concept of the micro front-end, hoping to help everyone better understand the micro front-end.
What is the micro front end, divide and rule but also sit on the same stage. Micro, scattered also. In detail, namely the micro front end is a kind of architecture, is to apply the whole rock is divided into several independent development, deployment, online, running a small application (application), exposed a console application (parent application) to the operation of the unified management of the application, more child non-inductive on reciprocating switching between applications.
The term microfront first appeared in ThoughtWorks’ Technology Radar in 2016. However, the author believes that the idea of micro-front-end has been frequently applied to megalithic applications much earlier. Looking back at the time when the front and back end was not separated, when JSP was used to mix Java and HTML, a page was an HTML document, with index.html file as the entrance, through the anchor tag () to connect the page, the page to function as indicators, respectively belong to different projects, between projects do not affect each other, Can be distributed independently online. I think this is the earliest micro front end. With the rise of SPA applications, this early micro front end idea has been slowly buried.
Why use a micro front end
Nowadays, SPA applications can no longer meet the logical complexity and functional diversity of modern web applications. Imagine an entry file of several megabytes, requesting numerous images and CSS files at the beginning, and the network time and browser rendering time are far less than users expect.
Although there are also numerous solutions such as lazy loading, sprit, file compression, CDN and so on, the essence is still belonging to the same boulder application, which also leads to any changes to the application have to repackage the whole project, even if happypack multithreaded packaging, its engineering efficiency is not to praise. I believe that readers who have experienced the maintenance of large projects can also feel the feeling of powerlessness when the heart is flying and the body is not moving.
On the other hand, with the explosion of the front-end tech stack this year, it’s no longer the case that you can go anywhere with jQuery. Vue is 3.0, react is 17. Angular is one of the top three frameworks, although it is not widely used in China. Even if all versions are backward compatible, how many people can guarantee that the project’s basic framework will not be abandoned by The Times? Long-term stable operation. A few of these people have gone through angular2 rewrite upgrades and React hooks refactoring (although react is not recommended).
So in the long run, break up a megalithic app into smaller apps. It is feasible to achieve stable upgrade iterations of the project. The basic principle flow is as follows:
Modern application pain points:
- There will be more and more components and functional modules in the project, resulting in a slow packaging speed of the whole project;
- Because the number of folders increases with the number of functional modules, finding code becomes slower and slower;
- If only one of the modules is changed, the whole project needs to be packaged back online;
- The directory level and module level is too deep and there are many files, locating files will be slower and slower;
- All projects can only use the same technical framework, such as React and Vue.
Advantages of micro front end:
- Technology stack independent: the main framework does not limit access to the application stack, microapplications have full autonomy;
- Independent development and deployment: The git repository of the micro-application is independent, and the page opened by the parent application is updated synchronously after the micro-application deployment is completed;
- Incremental upgrade: In the face of a variety of complex scenarios, it is usually difficult for us to do a full stack upgrade or reconstruction of an existing system, while the micro front end allows us to achieve a good incremental reconstruction;
- Run independently: Each project can be run as a complete individual project, which may be just a function module in a large background project, and then all the microapplications combined are the complete functionality the PM wants;
Micro front end technical solution
Routing distribution micro front end
Just as its name implies is through routing (e.g., nginx configuration) to distribute different business to different applications, it is also used more and do simple “front-end” method, but this way is actually more like applying multiple polymerization, the application of the front end of different pieced together to make them look like as a whole.
iFrame
An iframe is an HTML tag provided by the browser, and the iframe element creates an inline frame (that is, an inline frame) that contains another page. This tag can be used effectively to complete the “micro front end”, but there are two things to consider when using it:
- Application loading issues: when the parent app loads and unloads the micro-app, what effects or animations to use when switching pages to make the whole page look more natural and acceptable;
- Application problems of communication: through HTMLIFrameElement contentWindow to obtain the iFrame element of the window object is a simpler method, but it will need to define a set of communication specification: What form should the event key take and how to name it, and when to start monitoring the time and so on?
Web Components
Web Components are a different set of technologies that allow you to create reusable custom elements whose functionality is encapsulated outside of your code and use them in your Web applications. It consists of three main techniques that can be used together to create custom elements that encapsulate functionality and can be reused anywhere you like without worrying about code conflicts.
- Custom Elements: A set of JavaScript apis that allow you to define Custom elements and their behavior, and then use them as needed in your user interface.
- Shadow DOM: A set of JavaScript apis for attaching encapsulated “Shadow” DOM trees to elements (rendered separately from the main document DOM) and controlling their associated functionality. This way, you can keep the functionality of elements private so that they can be scripted and styled without fear of running afoul of the rest of the document.
- HTML templates:
<template>
和<slot>
The element lets you write tag templates that are not displayed on rendered pages. They can then be reused many times as a basis for custom element structures.
Micro front-end framework selection
Mooa
Mooa is a micro-front-end framework for Angular. It is a single-SPa-based micro-front-end solution, but its project for Angular directly passes the framework. Interested students can explore it on their own.
Fly ice
Flying Ice is a micro-front-end solution for large systems. It can ensure the operation experience of a system and realize the independent development and release of each micro-application. Icestark manages the registration and rendering of micro-applications and completely decoupled the whole system. (This is one of our candidates)
Fly ice official website address:Ice. Work/docs/icesta…
qiankun
The design concept of Qiankun is one of simplicity and the other is decoupling/stack independence:
- Simple: As the main application and micro application can be stack independent, Qiankun is just a library like jQuery for users. You need to call several apis of Qiankun to complete the micro front end transformation of the application. At the same time, thanks to the DESIGN of HTML entry and sandbox, access to micro applications is as simple as using IFrame.
- Decoupling/stack independence: The core goal of the micro front end is to break up the boulder application into several loosely coupled micro-applications that can be autonomous. Many of the designs of Qiankun are based on this principle, such as HTML Entry, sandbox and inter-application communication. Only in this way can we ensure the independent development and operation of microapplications.
Because his introduction and concept are very prominent two words, simple so he became our leading role, but it still has to go through a comparison.
Website address: qiankunqiankun.umijs.org/zh
Comparison of Qiankun with flying ice
Below is a rough comparison of them, as of 2020-12-03
Fly ice
- Git: number of stars 943;
- Js isolation: sandbox;
- Style isolation: Use CSS Modules schemes to manage styles; (Normally try using Shadow Dom scheme)
- Use the ice.js framework to package applications using react.
- Update period: 1 week to 1 month;
qiankun
- Git: star count 7.8K;
- Js isolation: sandbox;
- Style isolation: Shadow Dom;
- Related to packaging: Relying on Qiankun, parcel is officially recommended for packaging, but WebPack can be used.
- Update period: 1 day to 1 week;
After sorting and reviewing the above, we decided to use Qiankun internally for many reasons. Because our project had requirements for packing results, parcel could not meet our requirements, so we had to use WebPack to pack the results we wanted. Compared with Flying Ice, the style isolation was more mature and the number of stars was higher.
Introduction of practical use of Qiankun and record of problems
After selecting the framework, we started to build the project. We built two different parent applications (also known as base applications), one is React and the other is VUE. Later, I tried using the micro application provided by demo, and decided to unify the code. Finally, I decided to write the background project with React, so I switched to react project at last.
During the study, we got a message that the result of our online packaging was limited, which must be in the form of XXX before we could go online. Then there were a lot of configurations. Due to the tight time, we had to use the company’s ZZ-react-CLI to generate react project. So the parent application and micro-application are generated using zz-react-cli. (This is our internal problem, not to introduce), the following to introduce you to use and encountered problems and how to solve.
The basic project structure looks like this, with master and child applications, as shown below:
Basic use of Qiankun
The project framework
The React scaffolding is used to generate both the parent and micro-apps, which are modified and configured below.
Modification of the parent application
The parent app will install two dependency packages, one history and one qiankun, and then modify index.js:
import { registerMicroApps, start } from 'qiankun';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
registerMicroApps(
[
{
name: 'app1',
entry: '//localhost:3002/',
container: '#container',
activeRule: '/app1',
props: {
name: 'kuitos',
}
}
],
{
beforeLoad: app => console.log('before load', app.name),
beforeMount: [
app => console.log('before mount', app.name),
],
},
);
start();
reportWebVitals();
Copy the code
Then there are the changes to app.jsx:
import { createBrowserHistory } from 'history'; const history = createBrowserHistory(); Function App() {const onMenuClick = (path) => {history.push(path); }; return ( <div className="layout"> <header className="layout-header"> <img src={logo} className="layout-logo" alt="logo" /> <div className="layout-link" onClick={() => { onMenuClick('/app1'); </div> </header> <div className="layout-main" id="container"> App1 </div> </div> }Copy the code
The transformation of microapplications
No additional NPM packages need to be installed for microapplications. Here are the changes to index.js:
const initAPP = container => { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, container? Container. QuerySelector ('#root'):document.querySelector('#root'))} Window.__powered_by_qiankun__){initAPP()} /** * Bootstrap is only called once during microapplication initialization, and the next time the microapplication re-enters, the mount hook will be called directly. Bootstrap will no longer be triggered repeatedly. * This is usually where we can initialize global variables, such as application-level caches that will not be destroyed during the unmount phase. */ export async function bootstrap() { console.log('react app bootstraped'); } /** * the application calls the mount method every time it enters, */ export async function mount(props) {const {container}=props initAPP(container)} /** * Each time the application is cut/unloaded Invokes the method, usually here we will unload the application of micro application example * / export async function unmount to (props) {ReactDOM. UnmountComponentAtNode (props. The container? props.container.querySelector('#root') : document.getElementById('root')); } /** * Optional lifecycle hooks, */ export async function update(props) {console.log('update props', props); } reportWebVitals();Copy the code
This is the basic configuration, but this configuration is not completely ok, you will encounter a variety of problems in the middle, and then encounter problems please don’t worry, please see the following problem record to find the corresponding solution.
At this point, you may ask, the Internet similar information is not very much, just build a demo should be easy to run out. However, here are the problems we encounter based on this architecture. Real dry goods, take a closer look!
Qiankun problem Records
The lifecycle hook problem could not be recognized
An Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in XXX entry error, this problem is their own project or official project introduction will throw error, Add the following code to the webpack part of the microapplication:
const packageName = require('.. /package.json').name; Library: 'brokenSubApp', libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${packageName}`, },Copy the code
The reason why Qiankun threw this error was that its exported lifecycle hooks could not be identified from the entry JS of the microapplication. (Official introduction)
Requesting resources across issues
At the initial stage of the project, there is no fixed domain name, but the development needs to allow cross-domain. How to solve this problem?
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
}
}
Copy the code
Add ‘access-Control-allow-Origin ‘: ‘*’ to the webpack devServer in the microapplication. The parent application does not need to add, because it is the parent application that loads the microapplication’s resources.
Another option is if you have a proxy tool, such as whistle, which we use.
127.0.0.1:8085 a.zhuanzhuan.com/manager
127.0.0.1:8084 a.zhuanzhuan.com
Copy the code
It is also possible to broker the parent application and micro-application resources under the same domain name.
Microapplications refresh 404 problems
Switch the routing mode of the parent application and micro-application to the same routing mode. Micro-application uses browserHistory as an example:
<! Import {createBrowserHistory} from 'history'; const history = createBrowserHistory(); <Provider store={store}> <ConnectedRouter history={history}> <div className="rooter-wrap"> <Route routeList={routeList}/> </div> </ConnectedRouter> </Provider> <! <BrowserRouter basename = {window.__POWERED_BY_QIANKUN__? '/qc_xxx' : '/hunter_qc_xxx'} forceRefresh = {! window.__POWERED_BY_QIANKUN__} >Copy the code
Each person or company’s project is different, and it depends on their project code to update something. (If you use it, you should check it, otherwise it will cause 404 problems after refreshing.)
Parent and microapp menu issues
Since microapps can run independently, they have their own menu navigation logic and then integrate all the menus into the parent app. So this raises the question of how to integrate all the microapp menus. We came up with three ideas:
- Configure a set of menu navigation in the configuration center, and then the parent application asks the configuration center to get the navigation and render it. Micro-application internal maintenance routing:
- Advantages: convenient local development;
- Disadvantages: maintenance of two sets by, configuration center and a set of local;
- Both the parent application and micro-application are in the configuration center, which may affect the development efficiency if the application is independently developed depending on external resources:
- Advantages: maintenance of a set by, with the current configuration center interface, do not need to develop separately;
- Disadvantages: Microapplications are inconvenient to develop separately and need to maintain different routes for different environments. Both the micro-application and the parent application need to pull the entire route configuration, and then the child application needs to filter and filter its own routes.
- When microapplications are packaged, pack the navigation into a separate package
.json
The file is then configured to an address via nginx to access the file’s contents. The parent application requests the file and then consolidates it, as shown below:
- Advantages: Convenient local development, do not need too much configuration;
- Cons: Need to develop this functionality separately; The parent app pulls multiple child apps but affects the first screen rendering speed;
Combined with the above three schemes, we decided to use the third-party way after internal discussion, and found it completely feasible through development and actual online operation
Here’s how to pack:
/** * need a NPM package: generate-json-webpack-plugin * write configuration in webpack */
const GenerateJsonPlugin = require('generate-json-webpack-plugin');
const menuList = require('.. /src/router/menuData.js'); / / the routing table
// The format of the encapsulation request needs to be met
const menuConfig = {
code: 0.msg: ' '.data: menuList
};
/** * omit intermediate code... * /
/** * Add plugins to webpack builds: * Generate the specified JSON file */
plugins: [
// Generates the specified JSON file
new GenerateJsonPlugin('webserver/config/menu.json', menuConfig),
],
// Then configure a separate configuration via nginx to point to this file
Copy the code
This solves the menu problem of the parent application and the microapplication.
Synchronize data between sub-applications
In a real development scenario, complex and unexpected requirements are always encountered. Synchronizing data between sub-applications is one of them. In theory, if the sub-applications are independent enough, the business logic is separated enough. There is no sharing of data between sub-applications. The idea behind Qiankun is that all data will be distributed by the parent app. See issue412. But in practice, there is such a need. At the same time, however, I recommend minimizing these requirements because they can lead to messy data flow and bugs that are difficult to resolve. The following describes the detailed solution
The parent application uses proxy to create a global store. The complete code is as follows.
const GlobalStore = (function () {
if (window.__hunterMfeBase__) {
return window.__hunterMfeBase__;
}
// Initialize the store
const store = Object.assign(Object.create(null), {
/** * assign variable *@param Prop property name *@param Value Indicates the attribute value *@param options* {* readOnly: Boolean readOnly *} */
setValue(prop, value, options = {}) {
const { readOnly } = options;
// Non-read-only attribute - Direct assignment
if(! (readOnly ===true)) {
this[prop] = value;
return;
}
// Read-only attribute - proxies the value to __value__
this[prop] = {
value,
readOnly
};
},
/** * Initializes global variables (used to initialize global variables on the parent application) *@param global* /
init(global = {}) {
Object.keys(global)
.forEach(item= > (this[item] = global[item])); }});// Initialize the interceptor
const handler = {
// Intercepts the value operation
get(target, prop) {
// const { activeApp = 'base' } = store;
// console.log(`[${activeApp}] get`, { activeApp, target, prop });
const primitive = target[prop]; // Get the original value
// Read-only property - Returns the proxy's __value__
if ({}.toString.call(primitive) === '[object Object]' && primitive.readOnly === true) {
return primitive.__value__;
}
// Non-read-only property - Returns the original value directly
return primitive;
},
// Intercepts assignment
set(target, prop, value) {
// const { activeApp = 'base' } = store;
// console.log(`[${activeApp}] set`, { activeApp, target, prop, value });
const oldVal = target[prop]; // Read the original value
// Old value exists and is read-only - cannot be changed value operation
if ({}.toString.call(oldVal) === '[object Object]' && oldVal.readOnly === true) {
console.error(`GlobalStore.${prop}Is a read-only property and cannot be assigned repeatedly);
return true;
}
// The new value has a read-only attribute - data proxy to __value__
if ({}.toString.call(value) === '[object Object]' && value.readOnly === true) {
delete value.readOnly;
const proxyVal = {
__value__: value.value,
readOnly: true}; target[prop] = proxyVal;return true;
}
// Non-read-only attribute - Direct assignment
target[prop] = value;
return true; }};window.__hunterMfeBase__ = new Proxy(store, handler);
return window.__hunterMfeBase__; } ());export default GlobalStore;
Copy the code
Global Store can be assigned at parent application initialization
import GlobalStore from '@store';
GlobalStore.init({
// General attributes
userInfo: {},// Read-only attribute
permissionInfo: {value: {},
readOnly: true}});// Add attributes manually
GlobalStore.other = ' ';
// Manually add the read-only attribute
GlobalStore.setValue({
value: ' '.readOnly: true
})
Copy the code
Child applications reference global stores
const GlobalStore = (function () {
return window.__hunterMfeBase__ || {}; } ());export default GlobalStore;
Copy the code
Child applications read data from the global Store
import GlobalStore from '@/store/globalStore';
console.log(GlobalStore.userInfo);
Copy the code
Set the data in the global Store in the child application
import GlobalStore from '@/store/globalStore';
/ /... Res is the result of the interface request
const { userInfo = {}, permissionInfo = {} } = res;
GlobalStore.userInfo = userInfo;
Copy the code
The userInfo retrieved in all child applications is now updated.
So far this period of the micro front end of all the content has been shared, you can have any questions under the message oh. We will continue to share as appropriate.