Recently, the focus of my work is on the platform, and I happened to do some micro front end transformation. Here I summarize some challenges and pits encountered.
origin
The project I am currently in charge of is this classic page layout, which opens one page after another through the left menu and can be switched at any time
Previous implementations were basically implemented through iframe technology, opening a TAB is actually loading a separate url.
Using iframe to open tabs is already a perfect solution. Iframe inherently provides hard isolation without any concern for contamination or interaction.
But iframe still falls short. In our project scenario, there are three points of dissatisfaction:
1. If multiple resources are loaded, opening multiple iframe labels means that large third-party libraries such as React are loaded for many times, and the configuration of the computer will be slow
2. Each closing and opening of the TAB is a process of rebuilding browser window resources, with an obvious blank screen
3. For multi-route subapplications, the two IFrame tabs open different routes of the same subapplication and cannot be optimized
After internal discussion, it was decided to use the micro front end solution.
The micro-front-end framework chose Qiankun as the foundation. Emp requires sub-applications to upgrade Webpack5. In the current scenario, there are a large number of r&d efforts by other departments, which is extremely difficult to achieve coordination.
Qiankun qiankun.umijs.org/zh/guide website address
By default, the following transformation process has some knowledge of the Qiankun and microfront end
Train of thought
1. When switching back and forth between multiple tabs, the contents of corresponding tabs should be opened in seconds, at least keeping up with the speed of Keep-Alive, which requires us to cache certain sub-applications or directly do not uninstall them. Currently, we do not uninstall existing tabs first. If multiple sub-applications coexist, we can use the manual loading microapplication method of qiankun2.0. When opening a TAB, we can manually load a sub-application, and when closing, we can unload it.
2. If the base and many sub-applications have their own routes and listen to the address bar, it will be very confusing. In this case, all the routes in the sub-application project need to be transformed into virtual routes
The vUE router uses abstract mode
React routers use memoryRouters
Base reconstruction
The existing base, a React project, was relatively easy to retrofit
-
Introducing qiankun library
-
Create a new micro front-end component container (as shown below)
import { MicroApp, loadMicroApp } from 'qiankun';
import React from 'react';
export default class MicroAppWapper extends React.Component<any> {
private containerRef: React.RefObject<HTMLDivElement> = React.createRef();
private microApp: MicroApp = null;
componentDidMount() {
const{ name, entry, ... props } =this.props;
this.microApp = loadMicroApp({ name, entry, container: this.containerRef.current, props }, {
fetch(url, ... args) {
// enable cross-domain requests for the specified micro-application entry
if (url === 'http://app.alipay.com/entry.html') {
return window.fetch(url, { ... args,mode: 'cors'.credentials: 'include'}); }return window.fetch(url, ... args); },sandbox: {
// Enabling Shadowdom will lead to adaptation costs, please be careful, we will explain in detail below
strictStyleIsolation: true}}); }componentWillUnmount() {
this.microApp.unmount();
}
componentDidUpdate() {
// You can do some custom operations
}
render() {
return <div ref={this.containerRef}></div>; }}Copy the code
- Replace the original iframe(as shown below)
if (type === "microApp") {
return <MicroAppWapper
name={title}
entry={domain}
initialUrl={domain}
/>
}
if (type === "service") {
return <Iframe url={url} />
}
Copy the code
Subapplication modification
The basic modification
Packaging tools
The following configuration needs to be added for the microapplication packaging tool:
// Package. json name should not be popular, otherwise it will not be easy to distinguish
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`.libraryTarget: 'umd'.jsonpFunction: `webpackJsonp_${packageName}`,}};Copy the code
Microapplication entry
Microapplications need to export the bootstrap, mount, and unmount lifecycle hooks in their own entry JS (usually the entry JS of your webpack configuration) for the host application to call when appropriate.
/** * Bootstrap will only be called once during the micro-application initialization. The next time the micro-application re-enters, the mount hook will be called directly. Bootstrap will not be triggered again. * 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() {
// What do you need to do
}
/** * The application calls the mount method every time it enters, usually we walk through a portion of the entry code and trigger the application's render method */
export async function mount(props) {
// react: 👇
ReactDOM.render(<App/>, props.container.querySelector('#root'));
// Vue sample code 👇
const { container } = props;
instance = new Vue({
render: h= > h(App)
}).$mount(container ? container.querySelector('#root') : '#root')}/** * The method that is called each time the application is cut/unloaded, usually here we unload the application instance of the microapplication */
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container.querySelector('#root')); }export async function update(props) {
// What do you need to do now
}
if (!window.__POWERED_BY_QIANKUN__) {
// The previous entry boilerplate ⤵️ should be wrapped with the above if logic ⤴️ so that the application does not load twice
ReactDOM.render(<App/>, props.container.querySelector('#root'));
}
Copy the code
The webpack situation
The webpack applications built see 👉 🏻 qiankun.umijs.org/zh/guide/tu…
Routing modification
Currently, due to the multi-tab mode, many subapplications are loaded, and the subapplication routes will inevitably cause confusion if they all listen to the address bar for responses. So we need to modify the sub-application to simulate routing
Transparent transmission parameters during the mount phase
InitialUrl will be passed in the mount, we just need to pass through the App component
ReactDOM.render(<App
initialUrl={props.initialUrl}
/>, props.container.querySelector('#root'));
Copy the code
Sample code for the APP phase
The vUE router uses abstract mode
React routers use memoryRouters
import { MemoryRouter, withRouter, Route, Switch } from 'react-router'
const App = (props) = > {
let initialEntries;
if (props.initialUrl) {
// Handle its hash value
const { hash } = new URL(props.initialUrl)
initialEntries = [hash.replace(/^#/g.' ')]}else {
// This code is designed to be opened independently
const hash = window.location.hash;
const rehash = hash.replace(/^#/g.' ');
if(rehash && rehash ! = ='/') {
initialEntries = [rehash]
} else {
initialEntries = ['/']}}// Replace the original Router with a MemoryRouter
return <MemoryRouter
initialEntries={initialEntries}
>
<Root />
</MemoryRouter>
}
export default App
Copy the code
Split chunk processing
If multiple chunk.js are packaged, chunk.js may fail to be pulled. You need to configure public_path of Webpack. Add the following code at the top of entry. Js:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Copy the code
Shadow_DOM adaptation
This may be a big job, if most of the pages of the sub-application are in their own department or the whole project is just a middle background internal project, I think there is no need to shadowdom
Because so many other departments are involved, the base wraps a shadow DOM around the child application nodes to avoid style loading clutter
Developer.mozilla.org/zh-CN/docs/…
Shadow_DOM requires a lot of adaptation, but the shadow_DOM specification is on the right track and is likely to become a standard in the future, so it’s worth the effort.
React upgrade above 17
Shadow_DOM proxies document. React synthesized events under version 17 will not take effect
The easiest way is to upgrade above 17
CSS adaptation
This needs to check the overall style change after loading the child application
The native method reported an error
Methods like document.querySelector or document.getElementById will fail
Solution:
1. Dom manipulation is not recommended in mainstream frameworks. Please modify it in the framework way
2. When we mount the container, we will take the child application container, and we can put it on the Window to make it compatible with all places used
/** * The application calls the mount method every time it enters, and usually we trigger the application's render method here */
export async function mount(props) {
// Mount the container to your own window (this will not pollute the base's window)
window.MicroAppContainer = props.container
....
}
Copy the code
Then global searching the document., found querySelector or getElementById method such as replace (window. MicroAppContainer | | document).
Third-party library runtime sharing
If the base is loaded with react/ Vue libraries, we can use it directly in sub-applications, which is the most direct optimization method for the micro front end
But I have to say that this shared runtime is not very good and may cause some problems
The base loads the CDN resource and configures the Webpack externals so that the window has the corresponding library
Configure the same externals for the sub-application
(Libraries for runtime singletons such as Axios must not include…)
expand
1. Monitor memory
The coexistence of multiple applications is still a drag on memory, we need to listen to the browser memory, uninstall the least frequently used child applications (LRU algorithm)
2. Cross domain
The back-end interface of the sub-application request may cross domains because host is the host of the base, which may require backend coordination or setup of mid-tier forwarding
3. Deploy and debug
If iframe is used, the deployment mode remains unchanged. Other departments commissioning sub-applications may require a complete set of scaffolding from here
4. The microapplication and the original address coexist
There are no special cases that support coexistence, special requirements can be done by configuring multiple HTML through Webpack
Some other considerations
1. The qankun method of start is not needed in the project. It mainly does some single-SPA routing processing, which is not involved here
2. Module Federation support will make component-level reuse easier (expect)
3. If any error occurs, you can go to qiankun.umijs.org/zh/faq
conclusion
To summarize the transformation process above, three points need to be noted
1. For multiple tabs, manually control the loading and unloading of subapplications (loadMicroApp), not based on routes
2. If there are routes in the sub-application project, transform them into virtual routes
3. The current cost of shadowdom style hard isolation is high, depending on the scene
As of the end of this article, it has been a small trial, if you have any questions or suggestions, please feel free to comment! If it is difficult, I will analyze the principle of Qiankun in the next chapter, focusing on several points I am concerned about