preface

Our team is working on a XX system with React technology stack. At present, the system is getting bigger and bigger, the cost of development and maintenance is increasing, and the whole project must be packaged together each time, which is time-consuming and laborious. After consideration, it was decided to split it into several projects and combine them into a complete system. The micro front-end architecture was a good choice.

The micro front end has the following advantages:

  1. Single project maintenance: e.gThe commodity moduleSingle pull to form a project that can be maintained by a team individually, achieving good decoupling
  2. Reduced complexity: there is no need to develop in the whole integrated huge system, avoiding huge amount of code, fast compilation speed during development, improving development efficiency
  3. Fault tolerance: Failure in a single project does not affect the entire system
  4. Flexible technology stack: VUE, React, Angular, and other front-end technology stacks can be used. People who know VUE don’t need to learn React

The biggest benefit for us is single item maintenance.

show

The UI figure

We divide the whole micro front end into two parts:

  1. Main project: the part in red box, as the parent of the whole project, is responsible for displaying the menu module and the header module
  2. Sub-apps: The blue box. Sub-apps are specific business presentations

Dynamic graph display

Notice the change in the address bar, which includes /app1/ XXX and /app2/ XXX. At first glance, this appears to be a switch between two pages in one project, but in fact comes from two separate projects, app1 and app2 from different Git repositories.

Micro front-end architecture diagram

The whole process is roughly as follows: the user accesses index.html, and then runs module loader Js. The loader will register each project according to the configuration file (project.config) of the entire system. The system will load the Main project first, and then dynamically load the corresponding sub-projects according to the route prefix

We also refer to many good articles on the Internet for this architecture, among which the core articles can be referred to alili. Tech/Archive /110…

About the project. The config

About the following

[{isBase: false.name: 'app1'.version: '1.0.0'.// Load the current entry file by matching the route prefix
    hashPrefix: '/app1'.// Import file
    entry: 'http://www.xxxx.com/app1/dist/singleSpaEntry.js'./ / top Store
    store: 'http://www.xxxx.com/main/dist/store.js'}... ]Copy the code

In this case, project.config is used in the production environment. We upload the packaged file to OSS(or CDN) and synchronize the currently packaged project configuration to the server. The server consolidates the configuration of all projects into project.config, which is retrieved when the user accesses index.html, and then single-SPA registers the project based on these configurations and loads the project based on the route.

The technical details

single-spa

We found a few warehouses that implemented microfronds and compared them and decided to use single-SPA.

Our technology stack is REACT, and we need to use single-SPa-React in the sub-project entry to build it. The key code is as follows:

import singleSpaReact from 'single-spa-react';

const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  domElementGetter
});

export function bootstrap(props) {
  return reactLifecycles.bootstrap(props);
}

export function mount(props) {
  return reactLifecycles.mount(props);
}

export function unmount(props) {
  return reactLifecycles.unmount(props);
}
Copy the code

If you use VUE, use single-SPa-vue

Then in the system entry file, register all the projects:

import * as singleSpa from 'single-spa';

singleSpa.registerApplication(
    'app1',
    () => SystemJS.import('app1-entry.js'),
    () => location.hash.startsWith(`#/app1`),
    props
  );
Copy the code

Single-spa website single-spa.js.org single-spa.js.org has many examples

Webpack and SystemJs

We use Lerna to manage the dependency packages of all projects in a unified way, and all the dependency packages have the same version, which is very easy to maintain.

Use webPack DLL functionality to remove common dependencies from all projects, such as React, react-dom, react- Router, MOBx, etc

In order to facilitate the dynamic loading of projects, we also used SystemJS, which we used version 0.20.19, as a reference to the Internet giants. With SystemJS, we need to change the libraryTarget in Webpack:

output: {
    publicPath: 'http://www.xxxxx.com/'.filename: '[name].js'.chunkFilename: '[name].[chunkhash:8].js'.path: path.resolve(__dirname, 'release'),
    libraryTarget: 'amd'.// Note that amd specifications are used here
    library: 'app1'
  },
Copy the code

Instead of using the UMD specification, we used the AMD specification, and instead of using the Import Maps feature in SystemJS, we dynamically loaded the module entry directly through project.config.

App to App communication

I have also seen some big guys’ schemes about this, which is that there is a store in all projects, all stores are put into queues when registering entry, and all stores are synchronized by calling Dispatch when the status of stores needs to be updated.

My approach is the same as that of traditional single-page applications. A system should have only one top-level Store. Since top-level Store stores the public state of the whole system, such as menus and user information, I put it in the Main project, but the Store is separately removed when packaging:

entry: {
    singleSpaEntry: './src/singleSpaEntry.js'.store: './src/store' // Separate entry
  },
Copy the code

When registering, pass this Store into each project:

/ / top Store
const mainStore = await SystemJS.import(storeURL);

singleSpa.registerApplication(
    'app1',
    () => SystemJS.import('http://www.x.com/app1/entry.js'),
    hashPrefix('/app1'),
    { mainStore }
);
singleSpa.registerApplication(
    'app2',
    () => SystemJS.import('http://www.x.com/app2/entry.js'),
    hashPrefix('/app1'),
    { mainStore }
);
Copy the code

So you can manage only one Store, which is very convenient. Note: I’m using Mobx for state management

The front-end deployment

The way we deployed it was very simple. I wrote a Webpack plug-in to upload the packaged DIST to OSS and then send the project configuration to the server. The server (NodeJs) organized it into project.config based on the incoming project configuration. Then when the user accesses index.html, he gets project.config, at which point single-SPA registers all projects according to the configuration, and pulls the corresponding project entry file JS file according to the route.

Place the mount DOM of the subproject in the Main project

Our requirement is that Main be used as the Layout of the entire project, and the Dom of the subproject be mounted in the Main project, so the subproject cannot be mounted until the Main project is completely rendered. I borrowed the domElementGetter method from some of the micro-front-end implementations on the web:

function domElementGetter() {
  let el = document.getElementById('sub-module-wrap');
  if(! el) { el =document.createElement('div');
    el.id = 'sub-module-wrap';
  }
  let timer = null;
  timer = setInterval((a)= > {
    if (document.querySelector('#content-wrap')) {
      document.querySelector('#content-wrap').appendChild(el); clearInterval(timer); }},100);

  return el;
}
Copy the code

demo

Demo address: github.com/Vibing/micr… This demo only provides a reference to the micro front end, the actual development and deployment depends on the situation of each company

conclusion

This is our first time to play micro front end, there may be a lot of places not perfect, but also hope you big guy a lot of forgiveness