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