Single spa technology analysis of micro front end framework

stay Understand the principle of micro front-end technology In, we introduce the concept and core technology principle of micro front end. In this article, we combine the current mainstream micro front-end implementation in the industry single-spa To illustrate how to realize the micro front end in production practice.

The documents of single spa are a little messy and have many concepts. It is easy for students who come into contact with it for the first time to lose focus. Today, we try to sort out a clear context so that interested students can quickly understand it.

In the architecture design of single spa, there are two main roles, main application and sub application, as shown in the figure below.

The main application is simple enough to only be responsible for the scheduling of sub applications, and the business logic is borne by the sub applications.

Core competence

In fact, to sum up, the core of single spa is to define a set of protocols. Through this protocol, the main application can easily know which sub application is activated under what circumstances. This protocol mainly includes two parts: the configuration information of the main application and the life cycle function of the sub application.

Configuration information of main application

In single spa, this configuration information is called Root Config.

The following example shows the structure of configuration information:

{
  name: "subApp1",
  app: () => System.import("/path/to/subApp1/code"),
  activeWhen: "/subApp1",
}

Name is the name of the sub application. The app function tells the main application how to load the code of the sub application, and activeWhen tells the main application when to activate the sub application. It can also be a function that returns a Boolean value.

adopt registerApplication Register the information of the sub application into the main application.

Examples are as follows:

singleSpa.registerApplication({
    name: 'appName',
    app: () => System.import('appName'),
    activeWhen: '/appName',
    customProps: {
        authToken: 'xc67f6as87f7s9d'
    }
})

Lifecycle function of sub application

When managing sub applications, the main application starts and unloads the sub applications through the life cycle functions exposed by the sub applications.

There are mainly the following life cycle functions.

  • bootstrap

    This lifecycle function is executed once before the application is first mounted. That is, after the sub application code is loaded, it is executed before page rendering. The function form is as follows:

    export function bootstrap(props) {
      return Promise
        .resolve()
        .then(() => {
          // You can deploy initialization code that executes only once here
          console.log('bootstrapped!')
        });
    }
    
  • mount

    This lifecycle function will be called when the main application determines that the sub application needs to be activated. This function implements the logic of sub application mounting, page rendering and so on. This function will be executed only once. We can simply understand it as ReactDOM.render operation. The function form is as follows:

    export function mount(props) {
      return Promise.resolve().then(() => {
        // Page rendering logic
        console.log('mounted!');
      });
    }
    
  • unmount

    This lifecycle function will be called when the main application determines that the sub application needs to be unloaded. In this function, the logic of component unloading, cleaning up event listening and so on are implemented. We can simply understand it as ReactDOM.unmountComponentAtNode operation. The function form is as follows:

    export function unmount(props) {
      return Promise.resolve().then(() => {
        // Page unload logic
        console.log('unmounted!');
      });
    }
    

By observing the signature of each lifecycle function, we can find that each function has a props parameter. The main application can pass some additional information to the sub application through this parameter, which will be described later.

In order to facilitate the access of sub applications of various technology stacks, single spa provides many tools, which can be found here List of tools officially maintained.

Other concepts

Classification of sub applications

Single spa divides sub applications into three categories according to different functions:

  • Application
    Represents a common sub application that needs to implement the above-mentioned life cycle function;
  • Parcel
    It can be understood that business units that can be reused across sub applications need to be implemented Corresponding life cycle function
  • Utility
    Represents a reusable logic, such as a function, without page rendering.

It is not difficult to see that both Parcel and Utility are for sharing and reuse, which is also a reuse scheme given by single Spa at the framework level.

Layout Engine

Although the idea of single spa is to make the main application as simple as possible, in practice, the main application is usually responsible for the rendering of general top and bottom columns. At this time, how to determine the rendering location of sub applications has become a problem.

Single spa provides Layout Engine A new scheme. The sample code is as follows, which is quite similar to Vue. You can view the document in detail, which will not be described here.

<html>
  <head>
    <template id="single-spa-layout">
      <single-spa-router>
        <nav class="topnav">
          <application name="@organization/nav"></application>
        </nav>
        <div class="main-content">
          <route path="settings">
            <application name="@organization/settings"></application>
          </route>
          <route path="clients">
            <application name="@organization/clients"></application>
          </route>
        </div>
        <footer>
          <application name="@organization/footer"></application>
        </footer>
      </single-spa-router>
    </template>
  </head>
</html>

About SystemJS

Many people will mention single spa when they mention it SystemJS , I think SystemJS is one of the cores of single spa. In fact, this is a misunderstanding. SystemJS is not necessary for single spa.

As mentioned earlier, the sub application should implement the life cycle function and then export it to the main application for use. The key is the implementation of this "export", which involves Modularization of JavaScript Question.

In some modern browsers, we can import and export by adding type="module" on the < script > tag.

<script type="module" src="module.js"></script>
<script type="module">
  // or an inline script
  import {helperMethod} from './providesHelperMethod.js';
  helperMethod();
</script>

// providesHelperMethod.js
export function helperMethod() {
  console.info(`I'm helping!`);
}

However, if we want to implement import axios from 'axios', we need to use importmap.

<script type="importmap">
    {
       "imports": {
          "axios": "https://cdn.jsdelivr.net/npm/axios@0.20.0/dist/axios.min.js"
       }
    }
</script>
<script type="module">
  import axios from 'axios'
</script>

In low version browsers, we need to use some "Polyfill" to realize modularization. SystemJS solves this problem. Therefore, SystemJS is widely used in the single spa sample to load applications.

In fact, SystemJS can also be used. webpack can also achieve similar capabilities, but it will deepen the engineering coupling between main applications and sub applications.

quarantine

stay Understand the principle of micro front-end technology In, we spent a long time on Mingzi's idea of applying isolation. So, how to achieve isolation in single spa?

Style isolation

The style isolation in single spa can be divided into two parts.

The first is the loading and unloading of sub application styles. Single spa provides single-spa-css This tool is implemented.

import singleSpaCss from 'single-spa-css';

const cssLifecycles = singleSpaCss({
  // List of css to load
  cssUrls: ['https://example.com/main.css'],

  // Whether it is a css exported by webpack. If it is, additional processing is required (the file name exported by webpack usually has a hash)
  webpackExtractedCss: false,

  // When the child application unmount, do you need to delete the css together
  shouldUnmount: true,
});

const reactLifecycles = singleSpaReact({...})

// Add to the bootstrap of the sub application
export const bootstrap = [
  cssLifecycles.bootstrap,
  reactLifecycles.bootstrap
]

export const mount = [
  // Add css to the mount of the sub application and put it in front, otherwise there will be a problem of style flicker (FOUC) after the mount
  cssLifecycles.mount,
  reactLifecycles.mount
]

export const unmount = [
  // After unloading css, prevent the style from flashing
  reactLifecycles.unmount,
  cssLifecycles.unmount
]

If the style is exported by webpack, the list of style files will be updated after each build. Single spa has prepared a plug-in to solve this problem. Just add the following plug-ins to the configuration file of webpack.

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ExposeRuntimeCssAssetsPlugin = require("single-spa-css/ExposeRuntimeCssAssetsPlugin.cjs");

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    new ExposeRuntimeCssAssetsPlugin({
      // The filename must correspond to the filename in MiniCssExtractPlugin one by one
      filename: "[name].css",
    }),
  ],
};

After solving the problem of sub application style loading and unloading, let's look at the problem of sub application style isolation.

Single spa gives some suggestions, such as using Scoped CSS , each sub application has a fixed prefix, similar to the following:

/*
<div class="app1__settings-67f89dd87sf89ds"></div>
*/
.app1__settings-67f89dd87sf89ds {
  color: blue;
}

/*
<div data-df65s76dfs class="settings"></div>
*/
.settings[data-df65s76dfs] {
  color: blue;
}

/*
<div id="single-spa-application:@org-name/project-name">
    <div class="settings"></div>
  </div>
*/
#single-spa-application\:\@org-name\/project-name .settings {
  color: blue;
}

There are many tools to implement Scoped CSS, such as CSS Modules Wait.

Finally, we can realize the automation through webpack.

const prefixer = require('postcss-prefix-selector');

module.exports = {
  plugins: [
    prefixer({
      prefix: "#single-spa-application\\:\\@org-name\\/project-name"
    })
  ]
}

Single spa also mentioned Shadow DOM, which we analyzed in the previous article, and will not be repeated here.

JS isolation

The single spa uses a method similar to snapshot pattern Isolation mechanism through single-spa-leaked-globals To achieve.

The usage is as follows:

import singleSpaLeakedGlobals from 'single-spa-leaked-globals';

// Other life cycle functions provided by single spa XXX
const frameworkLifecycles = ...

// Newly added global variable
const leakedGlobalsLifecycles = singleSpaLeakedGlobals({
  globalVariableNames: ['$', 'jQuery', '_'],
})

export const bootstrap = [
  leakedGlobalsLifecycles.bootstrap, // Put first
  frameworkLifecycles.bootstrap,
]

export const mount = [
  leakedGlobalsLifecycles.mount, // Add global variables during mount. If there are previously recorded variables, restore them directly
  frameworkLifecycles.mount,
]

export const unmount = [
  leakedGlobalsLifecycles.unmount, // Delete the newly added global variable
  frameworkLifecycles.unmount,
]

As mentioned earlier, one disadvantage of snapshot mode is that it can not ensure the effective isolation of multiple sub applications running at the same time.

Summary

Generally speaking, single spa basically realizes various functions that a micro front-end framework needs to have, but it is not completely implemented, leaving many problems to be solved. Although the government has provided many examples and best practices, they are always too thin, giving people a feeling of "the problem has been solved, but it has not been completely solved".

qiankun is developed based on single spa, which solves many problems that single spa does not solve to a certain extent. We'll explain it in detail in the next article.

Common interview knowledge points, technical solutions analysis, tutorials, you can sweep the code to pay attention to the official account "thousands of search" to get, or come here. https://everfind.github.io/posts/ .

Added by twistedmando on Fri, 03 Dec 2021 03:59:15 +0200