The microfront-end was first proposed by ThoughtWorks in 2016. Its main idea is to introduce a concept similar to back-end microservice architecture in the front end and split the huge boulder application into multiple independent applications (hereinafter referred to as microapplications). Each individual application can be developed, tested, and deployed independently, and then combined to present a single user through a container application (hereinafter referred to as the host application).

There are many ways to realize the micro front end. Iframe, which we are familiar with, actually belongs to the micro front end. It has its own sandbox isolation, development separation and other advantages, but at the same time, there are many additional problems, such as repeated loading scripts, poor SEO, multiple scroll bars and so on, leading to its widespread application. The rise of modern SPA framework has brought a new Gospel to the micro front end, combined with the SPA routing form to load the specified sub-applications, so that a micro front end architecture can be realized.

However, the following problems need to be solved to achieve the micro front-end architecture:

  • How to implement route distribution application.
  • How to control microapplication loading and unloading.
  • How the microapplication is delivered to the master application.
  • How data is shared between microapplications.
  • How do microapplications not affect each other?

1. Distribute applications through routes

In SPA, routes are distributed through the framework, which is specified to a specific component for display through route distribution. In the micro-front-end architecture, the micro-front-end takes over the work of the framework, matches the corresponding applications through routing, and distributes them to the corresponding components. In terms of implementation, the route design of the micro front-end is not significantly different from front-end routing solutions such as react-Router and Vue-Router, which are implemented by hijacking routes and simple codes:

/* Handle the update microapplication method */
function reroute () {
 // TODO:
}
/* Listen for route changes, trigger update microapplication method */
window.addEventListener('hashchange', reroute) window.addEventListener("popstate", reroute); window.history.pushState = patchedUpdateState(window.history.pushState) window.history.replaceState = patchedUpdateState(window.history.replaceState)  /* Enhance pushState and replaceState */ function patchedUpdateState (updateState) {  return function (. args) {  / / the current url  const urlBefore = window.location.href;  // pushState or replaceState result  const result = Reflect.apply(updateState, this, args)  // The url after executing updateState  const urlAfter = window.location.href  if(urlBefore ! == urlAfter) { reroute()  }  return result  } }  Copy the code

As you can see above, we listen for changes in hashChange and popState, while adding logic to the pushState and replaceState methods via decorator mode to ensure that reRoute is executed to update the micro-application state when the route changes. So how do you handle microapplication state?

2. Control application loading and unloading through the life cycle

Let’s first look at the registration structure for microapplications, which makes it easier to understand the implementation backwards.

// Micro application registry structure
{
    // The name of the micro application
    name: 'app1'.    // The micro application load function is a promise
 app: loadApp('http://localhost:8081'.'app1'),  // When the route meets the condition, mount the child application  activeWhen: location= > location.pathname.startsWith('/app1'),  // The object passed to the microapplication  customProps: {} } Copy the code

According to the SPA framework, we can find that the life cycle is used to control the loading and unloading of components in the framework. Single-spa is also inspired by this, and uses the life cycle to control micro-applications. According to the states, it can be divided into the following states:

// The initial state of the child application after registration
const NOT_LOADED = 'NOT_LOADED'
// Indicates that the child application source code is being loaded
const LOADING_SOURCE_CODE = 'LOADING_SOURCE_CODE'
// App.loadapp is the state after the child application is loaded
const NOT_BOOTSTRAPPED = 'NOT_BOOTSTRAPPED' // Initializing const BOOTSTRAPPING = 'BOOTSTRAPPING' // The table is initialized and unmounted after executing app. Bootstrap const NOT_MOUNTED = 'NOT_MOUNTED' // Mounting const MOUNTING = 'MOUNTING' // App. mount completes const MOUNTED = 'MOUNTED' const UPDATING = 'UPDATING' // Uninstalling const UNMOUNTING = 'UNMOUNTING' .Copy the code

A status parameter will be uniformly added to mark the current application state when micro-applications are registered, and the initial value is NOT_LOADED. Then different states are distinguished according to different loading opportunities and micro-applications’ states, which can be divided into three categories (there will be more actual cases) : To be loaded, to be mounted, and to be unmounted perform different operations according to the three states.

In addition, in order to be able to associate the micro-front-end life cycle with the micro-application declaration cycle, we need to obtain the micro-application declaration cycle for our use. Therefore, we use umD, a package module format with better compatibility, to package the sub-application. In the sub-application, export life cycle is mounted on global. Then we can get the exposed life cycle of the micro-application according to the APP parameters of the micro-application registration structure, and add the life cycle of the micro-application together to the micro-application configuration file to meet all the operable conditions. Let’s look at how to deal with the micro-application in different states:

function reroute(){
 // Divide microapplications into three categories based on state
  const { appsToLoad, appsToMount, appsToUnmount } =   getAppChanges()
  if (isStarted) {
    performAppChanges()
 } else {  loadApps()  }  function loadApps () {  appsToLoad.map(toLoad)  }  function performAppChanges () {  / / unloading  appsToUnmount.map(toUnmount)  // Initialize + mount  appsToMount.map(tryToBoostrapAndMount)  } } Copy the code

The getAppChanges method distinguishes the three state microapplications through the cycle, and then determines whether the microapplications have been loaded according to isStarted. The unloaded microapplications are loaded, and the loaded sub-applications perform the life cycle loading and unloading in the corresponding microapplication configuration file according to the state. At this point, we have the basic form of a microfront end.

In what form do microapplications provide rendering portals

To achieve technology independence and independent deployment in a micro front-end architecture, runtime import is certainly the best solution, but what form of resources should a micro application provide as an entry point to the main application? At present, there are two schemes: one is js introduction, which is adopted by Single-SPA, and the other is HTML introduction, which is adopted by Qiankun. Here’s a comparison of the two options:

Introduction through JS usually requires microapplications to type resources into an entry script. In this case, there will be many limitations, such as the large size of a single JS package and the failure to use features such as parallel loading of resources. At the same time, in the JS introduction scheme, the main framework needs to build the corresponding container node before loading the micro-application, and then inject the acquired micro-application into the constructed node to complete rendering.

The method is more flexible through THE introduction of HTML. The main framework obtains the static resources of the sub-application through the fetch HTML method, and at the same time, the HTML document is stuffed into the container of the main framework as a child node. In this way, not only can the access cost of the main application be greatly reduced, the development mode and packaging mode of the sub-application basically do not need to be adjusted, but also can naturally solve the problem of style isolation between the sub-applications.

Data sharing between microapplications

Single-sap does not recommend large amounts of data sharing between microapplications. If there is too much coupling between two applications, then we need to consider whether they should be split, but it does provide a way to share data, as you can see here.

In qiankun, data sharing was carried out in two ways. The main application and micro-application were transmitted to sub-applications in the way of one-way data flow based on props. The browser – based native events are used for data communication between different microapplications.

In addition, we can also use some global storage methods for data sharing, such as localStorage, sessionStorage and so on.

5. Application isolation

1. JS isolation

The concrete implementation of the sandbox can be found in the article, which is also very interesting. It mainly talks about the sandbox in qiankun. There are two kinds of sandbox, one is for the snapshotSandbox that does not support proxy. ProxySandbox is a proxy-based proxy sandbox.

Snapshot sandbox, the whole idea is to copy the Window object to a new object and save it, then all the application operations are based on the new object, all the changes are made in the new object, and then compare with the Window before the instance is destroyed, keep the changes, and then enter the instance next time. Restore the changed parts with retained data to look like before exit.

Proxy sandbox mainly adopts the proxy feature of ES6, through proxy we can obtain all changes on the object, the main idea remains the same, through proxy global object, monitor all changes, and then cache all changes, etc. The instance will be restored according to the data when entering. However, a proxy implementation can support multiple instances running simultaneously.

2. CSS isolation

There are many ways to achieve CSS isolation. For example, the simplest and most direct way is to avoid style conflicts through the convention of CSS prefixes. Each micro-application has a specific prefix.

The method of Fetch HTML was used to introduce micro-application resources, which could solve the style conflict naturally. It would analyze and classify all the resources of micro-application, and then remove the CSS tree as a whole when the micro-application was destroyed, so that they could not affect each other. If you’re interested, take a look at the import-HTML-Entry library, which isn’t very much code, it’s very concise.

Six, summarized

According to the above, it can be found that the micro front end actually does two things, one is to deal with loading micro applications; The second is to manage each microapplication through its lifecycle. The focus is on the various configurations and entrances provided by the main application, which are then manipulated by the micro-front-end framework to perform the micro-application lifecycle functions at specific time nodes.

A link to the

  • single-spa
  • qiankun
  • Zhihu is probably the most complete micro front-end solution you have ever seen
  • Single-spa microfront-end framework from beginner to master
  • Sandbox implementation of Ali Cloud Open Platform Micro front end solution – Ali Cloud developer community

Time is fair, because it gives everyone 24 hours.

This article is formatted using MDNICE