Preliminary study on single spa of micro front end framework

Time:2021-12-3

preface

A recent company adopted the micro front-end framework of single spa, so I taught myself this framework.

Although single spa is a micro front-end frameworkChinese documents, but it is scattered and obscure.

So I want to write a blog to flatten the learning curve in my spare time.

What is a micro front end?

The inspiration of the micro front end comes from the concept of micro service at the service end.

It can be simply understood that when developing a complex front-end application, it is divided into a series of smaller and simpler front-end applications.

These front-end applications can be developed, tested and deployed separately, with loose coupling and strong maintainability. They can also enable the front-end code to realize incremental upgrade and use different frameworks.

Its lazy loading can also make the loading speed of the whole complex application faster.

Common micro front-end playing methods and single Spa

In my previous company, iframe was used to implement the micro front end, but the communication between sub applications is often troublesome and inflexible.

The latest micro front-end concept is realized by the module joint feature in webpack 5, which will not be discussed here.

Single spa is a popular micro front-end framework. It does not use iframe to implement the micro front-end, nor does it combine modules, but loads different sub applications on the DOM through routing paths.

Import maps and systemjs

Before explaining single spa in detail, we need to understand one thing:Import maps

This feature is only supported by chrome 89.

As the name suggests, it is a mapping process for import, which allows you to control which URL to get these libraries from when using import in JS.

For example, we usually introduce modules in JS in the following way:

import moment from "moment"

Under normal circumstances, it must benode_modulesBut now we add the following code to HTML:

{
        "imports": {
            "moment": "/moment/src/moment.js"
        }
    }

here/moment/src/moment.jsIt is also possible to change this address to a CDN resource. The final effect is:

import moment from "/moment/src/moment.js"

YesImport maps, the import syntax can be used directly in the browser, without the need for webpack to help us deal with itnode_modulesTo load the library.

Import mapsThere is even a way to play:

"imports": {
        "jquery": [
            "HTTPS: // a CDN / jQuery. Min.js",
            "/node_modules/jquery/dist/jquery.js"
        ]
    }

When the CDN is invalid, the content is obtained from the local library.

It has many functions, I won’t list them one by one, just need to have a certain understanding of this.

althoughImport mapsVery powerful, but after all, the browser compatibility is not very good, so we have our polifill scheme:SystemJS

SystemJSIt is also a module loader, compatible with ie11, and also supports import mapping, but its syntax is slightly different:

{
        "imports": {
            "lodash": "https://unpkg.com/[email protected]/lodash.js"
        }
    }

Import in browsersystem.jsAfter, it will resolve the type assystemjs-importmapImport mapping under script.

It andImport mapsThe final effect is consistent.

single-spa

The reason why we have to talk firstImport mapsandSystemJS, becausesingle-spaThe micro front end often needs to be combinedSystemJSTo achieve.

single-spaIn the frame,PedestalIt will detect the change of browser URL. When the change occurs, it often passes theSystemJSImport mapping to load different sub applications JS.

But note,single-spaYou don’t have to rely onSystemJS

We just mentioned a concept:Pedestal, let’s talk about it nowsingle-spaTwo concepts of:Pedestalandapplication

You can simply understandapplicationIs a single page application, andPedestalIs an application manager used to load different applications according to routesapplication

Generally inPedestalIn, we need to register an application as follows:

import { registerApplication, start } from 'single-spa';

//Register app 1
registerApplication({
    name:'app1',
    app:() => import('./app1.js'),
    activeWhen: '/app1',
    Customprops: {mytitle: "value of custom parameter passed to app"}
});

//Register app 2
registerApplication({
    name:'app2',
    app:() => import('./app2.js'),
    activeWhen: '/app2'
});

start();

In the above code, we registered app1 and app2 applications to match the routes / app1 and / app2 respectively.

That is, when the route is / app1 or / app1 / home, the app app1 will be loaded directly.

After registering the application, you need tostart()To start mounting the application, otherwise it will only download the application and will not mount the application.

So how should the application be set?

We refer to the above code./app1.jsA real single page application is not exported, but is generally as follows:

Console.info ('first step download application stage ')
export function bootstrap(props) {
    const {
        Name, // application name
        Singlespa, // singlespa instance
        Mountparcel, // manually mounted functions
        Mytitle // the attribute we passed to customprops when registering the application
    } = props;     //  Props is passed to each lifecycle function

    //This lifecycle function is executed once before the application is first mounted.
    return Promise.resolve().then(() => {
        Console.info ('step 2 initialization ', mytitle)
    })
}
export function mount(props) {
    //Whenever the application route matches successfully, but the application is not mounted, the mounted lifecycle function will be called. When called, the function will determine the currently activated route according to the URL, create DOM elements, listen to DOM events, etc. to present the rendered content to the user. Any sub route change (such as hashchange or pop state) will not trigger mount again, and each application needs to handle it by itself.
    return Promise.resolve().then(() => {
        Console.info ('step 3: mount application ', props. Name)
        Document. Getelementbyid ('root '). InnerHTML = "I'm app1"
    })
}

export function unmount(props) {
    //Whenever the application route matching is unsuccessful, but the application is mounted, the unloaded lifecycle function will be called. When the uninstall function is called, the DOM elements, event listeners, memory, global variables and message subscriptions created when the application is mounted will be cleaned up.
    return Promise.resolve().then(() => {
        Console.info ('step 4 uninstall application ', props. Name)
        document.getElementById('root').innerHTML = ""
    })
}

export function unload(props) {
    //The implementation of the "remove" lifecycle function is optional and will only be triggered when the unloadapplication is called. If a registered application does not implement this lifecycle function, it is assumed that the application does not need to be removed.
    //The purpose of removal is that each application executes some logic before removal. Once the application is removed, its state will become not_ Loaded, it will be reinitialized the next time it is activated.
    //The design motivation of removing the function is to realize "hot download" for all registered applications, but it is also very useful in other scenarios, such as when you want to reinitialize an application and perform some logical operations before reinitialization.
    return Promise.resolve().then(() => {
        Console.info ('step 5: remove application ', props. Name)
    })
}

You can see our app1.jsapplicationThe code in does not export a single page application component, but several life cycle functions, which are used to control the initialization, loading and unloading of components.

This operation is inspired by the component life cycle of our react or Vue frameworks, and applies the life cycle to the whole application.

Implementation of micro front end with single spa and systemjs

After reading the above code, you may be a little confused. Your thing is useless. Isn’t it just a lazy load?

Where’s the micro front end?

I use a react. Lazy (() = > Import (‘. / app1. JS’)) to load lazily. What’s the matter? Don’t say a lot and make me dizzy.

In fact, the micro front end is not implemented in the above, but you can imagine in combination with the micro front end we talked about earlier:

What if. / app1.js is not the code in this project, but another project?

We put this app1.js in a separate project, which uses react to write a single page application.

Then put app2.js in another separate project, which uses Vue to write a single page application.

Through our currentPedestalWhat about the project dealing with these two applications?

oursPedestalCan the project be written as follows:

import { registerApplication, start } from 'single-spa';

//Register app 1
registerApplication({
    name:'app1',
    App: () = > Import ('@ org / app1'),
    activeWhen: '/app1',
    Customprops: {mytitle: "value of custom parameter passed to app"}
});

//Register app 2
registerApplication({
    name:'app2',
    App: () = > Import ('@ org / app2'),
    activeWhen: '/app2'
});

start();

Then you can introduce a mapping in the template page of the base project:

{
        "imports": {
            "@ organization / app1": "// / a website / app1. JS",
            "@ organization / app2": "// / another website / app2. JS"
        }
    }

In the future, we will do the part of the app1 module. We only need to maintain it in the app1 project without interfering with other applications.

In the future, react20 and react30 will come out, or some projects will be upgraded to webpack, or a project will be greatly adjusted. We can try to upgrade and modify small applications one by one without adjusting all projects at the same time. The risk is not only much smaller, but also more controllable.

Above is the combinationSystemJSThe implemented micro front-end actually uses NPM package and single project, but it is not recommended. If you are interested, please refer to this article on the official website:Split application

single-spa-react

Friends who see here will think, this thing is good, how to combine it with react’s single page application?

Let me write, load and unload react’s single page application myself? That’s frustrating.

Of course not,single-spaOur ecology is very good.

single-spa-reactIs an auxiliary library, which can help react applications implement the life cycle functions (bootstrap, mount and unmount) required by single spa.

import React from 'react';
import ReactDOM from 'react-dom';
import rootComponent from './path-to-root-component.js';

import singleSpaReact from 'single-spa-react';
const reactLifecycles = singleSpaReact({
    React,
    ReactDOM,
    rootComponent,
    errorBoundary(err, info, props) {
        // https://reactjs.org/docs/error-boundaries.html
        return (
            This renders when a catastrophic error occurs
        );
    },
});
export const bootstrap = reactLifecycles.bootstrap;
export const mount = reactLifecycles.mount;
export const unmount = reactLifecycles.unmount;

This is the sample code provided on the official website. The rootcomponent is our top-level react component.

There is no need to say more about others. After all, they are easy to understand. For details, please see:details

Other languages, such as Vue and angular, have their own auxiliary libraries. For details, please refer to:Auxiliary library list

Parcels of single Spa

Parcels is an advanced feature of single spa. It is a framework independent component. The difference between parcels and applications is that parcels components need to be manually mounted rather than activated through the activity method of matching routes.

The official website said: we need to consider the use of parcel only when it involves component calls between cross framework applications.

At the thought that our company only uses react, so excuse me, bye.

CLI tools: create single spa and customizable webpack configuration

Many of the above are talking about principles. Now it’s time for practical application.

single-spaThe related configuration of is a little cumbersome, so I recommend relying on the existing cli to create a new project, which can be transformed, rather than building it from scratch.

npx create-single-spa

After running the above command, you will be asked to make a series of choices. Those choices will not be mentioned, but only the most critical ones.

create-single-spaYou can create three types of projects:

  • Single spa application / parcel: application and parcel.
  • Single spa root config: base.
  • In browser utility module: general components, tool functions, and style guidelines.

You can create different projects as needed.

single-spaSome recommended are also providedWebpack configuration library, you don’t have to worry about setting the webpack configuration yourself.

However, I suggest that you print out the final output to get the webpack configuration, and then you can make corresponding modifications according to its configuration.

Demo sharing

Just look, don’t make fake moves. Here’s my own basissingle-spaTwo simple demos built by cli tools:

It can be run at the same time. The commands are:

yarn start

If you really want to get started, you can learn more details by comparing some differences between what I wrote and the initialization of the official cli tool.

summary

in general,single-spaIt is a very excellent micro front-end framework.

The recent trend in the field of micro front end is to implement it with the module Federation feature in webpack 5, which is different fromsingle-spaNo conflict,single-spaThere are also examples of implementation combined with module joint characteristics.

However, this is beyond the scope of this article. Maybe I will write down this content in the future.

This is the end of this blog.

I hope this article can bring you some help. If there are omissions, please don’t hesitate to comment.