preface

Hello, I’m a sea monster. Those of you who are familiar with the micro front end should be familiar with the single-SPA framework, but I looked through the Chinese community and found too few articles about the single-SPA Demo practice.

There are some articles about it, but they are mainly based on the code, only about what, not why. It’s not a great experience for the reader. Let’s take a closer look at the React version of Single-SPA. Let the reader know what it is and what it is.

Introduction to the

In fact, the single-SPA website has many examples:

People should just focus on Actively maintained. Here I pick the first React Microfrontends to illustrate.

architecture

One point into the project, found that this thing has several projects:

The README. Md of root-config is so confusing that I believe it can persuade a lot of people.

However, I have them all down, and there are seven of them:

│ ├─ API # API Utils Call Function │ ├─ NavBar # NavBar │ ├─ People # People │ ├─ Plants │ plants │ ├ ─ ─ Shared - dependencies # import - map json file └ ─ ─ styleguide # Button component + global CSS stylesCopy the code

Their architecture looks like this:

The locations of these “small items” are shown below:

We can categorize the above “small projects” (other than root-config) as follows:

type project
The main application root-config
Page components people.plants
Common components navbar
Public suite api.styleguide
JSON import-map shared-dependencies

Everything except root-config is microapplications, whether page components or common suites. Let’s go over these projects now.

root-config

Let’s start with root-Config. This project is the main application, or the “head” of the entire App, and its work is very clean:

  • Introduce JS for common libraries and microapplications
  • Define the page routing of the microapplication
  • Load individual microapplications (pages/components/common functions/common styles)

Introduce JS for common libraries and microapplications

The introduction of common library, microapplication JS is implemented through SystemJS ‘import-map feature, such as this import-map.json:

{
  "imports": {
    "react": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"."react-dom": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js"."single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js"."@react-mf/root-config": "https://react.microfrontends.app/root-config/55aa0b39bcca3e3d0844b2c0aa2f658a2aa1b94f/react-mf-root-config.js"."@react-mf/navbar": "https://react.microfrontends.app/navbar/c1a777c770ee187cebedd0724653c771495f2af9/react-mf-navbar.js"."@react-mf/styleguide": "https://react.microfrontends.app/styleguide/504c8516e30274fc0e3221a719d5355b14af9500/react-mf-styleguide.js"."@react-mf/people": "https://react.microfrontends.app/people/4d0af0a673764fa9b311f9f98163c88be9af9426/react-mf-people.js"."@react-mf/api": "https://react.microfrontends.app/api/849cde43d1bf1a072c1b71b4de504fc7120d4629/react-mf-api.js"."@react-mf/planets": "https://react.microfrontends.app/planets/010c38f36fc578c406f1aa44d16f6aad9062f2f2/react-mf-planets.js"."@react-mf/things": "https://react.microfrontends.app/things/7f209a1ed9ac9690835c57a3a8eb59c17114bb1d/react-mf-things.js"."rxjs": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/es2015/rxjs.min.js"."rxjs/operators": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/es2015/rxjs-operators.min.js"."@react-mf/api/": "https://react.microfrontends.app/api/849cde43d1bf1a072c1b71b4de504fc7120d4629/"."@react-mf/people/": "https://react.microfrontends.app/people/4d0af0a673764fa9b311f9f98163c88be9af9426/"."@react-mf/navbar/": "https://react.microfrontends.app/navbar/c1a777c770ee187cebedd0724653c771495f2af9/"."@react-mf/planets/": "https://react.microfrontends.app/planets/010c38f36fc578c406f1aa44d16f6aad9062f2f2/"."@react-mf/styleguide/": "https://react.microfrontends.app/styleguide/504c8516e30274fc0e3221a719d5355b14af9500/"."@react-mf/root-config/": "https://react.microfrontends.app/root-config/55aa0b39bcca3e3d0844b2c0aa2f658a2aa1b94f/"}}Copy the code

This can be introduced in index.html with the

<script type="systemjs-importmap" src="https://storage.googleapis.com/react.microfrontends.app/importmap.json"></script>
Copy the code

Import React from ‘React’ in SystemJS. Here the react just from the above JSON in https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js.

Define routes & load microapplications

Single-spa uses custom tags to control different routes to specify corresponding page components:

// index.html
<template id="single-spa-layout">
  <single-spa-router>
    <nav class="topnav">
      <application name="@react-mf/navbar" loader="topNav" error="topNav"></application>
    </nav>
    <div class="main-content mt-16">
      <route path="people">
        <application name="@react-mf/people"></application>
      </route>
      <route path="planets">
        <application name="@react-mf/planets"></application>
      </route>
      <redirect from="/earth" to="/planets"></redirect>
      <route default>
        <h1 class="flex flex-row justify-center p-16">
          <p class="max-w-md">This example project shows independently built and deployed microfrontends that use React and single-spa. Each nav link above takes you to a different microfrontend.</p>
        </h1>
      </route>
    </div>
  </single-spa-router>
</template>
Copy the code

Note: template is not a template in Vue, but a custom tag, which is written in HTML.

To parse these labels, single-SPA created a library called Single-SPa-Layout:

// react-mf-root-config.js
import {
  constructRoutes,
  constructApplications,
  constructLayoutEngine,
} from "single-spa-layout";
import { registerApplication, start } from "single-spa";

const routes = constructRoutes(document.querySelector("#single-spa-layout"), {
  loaders: {
    topNav: "<h1>Loading topnav</h1>",},errors: {
    topNav: "<h1>Failed to load topnav</h1>",}});const applications = constructApplications({
  routes,
  loadApp: ({ name }) = > System.import(name),
});
// Delay starting the layout engine until the styleguide CSS is loaded
const layoutEngine = constructLayoutEngine({
  routes,
  applications,
  active: false}); applications.forEach(registerApplication); System.import("@react-mf/styleguide").then(() = > {
  // Activate the layout engine once the styleguide CSS is loaded
  layoutEngine.activate();
  start();
});
Copy the code

@react-MF/styleGuide, @react-MF /people, @react-MF /plants, @react-MF /navbar, These libraries are all from the.js in import-map.json defined above.

These @react-MF/XXX are both libraries and microapplications. This import-map library is a feature of SystemJS asynchronous JS, which is what Single-SPA has been emphasizing with JS Entry — loading microapplications through JS.

Webpack packaging

Although the entry JS is written very simply, we still need to package it as SystemJS to dynamically import it in index.html:

// index.html
<script type="systemjs-importmap">
  {
    "imports": {
      "@react-mf/root-config": "//localhost:9000/react-mf-root-config.js"}}</script>.<script>
  System.import('@react-mf/root-config');
  System.import('@react-mf/styleguide');
</script>
Copy the code

Therefore, we need to think about how to configure Webpack as well. Alas, when it comes to Webpack, some brothers have begun to wear pain masks. Single-spa has also thought of this, so it has introduced the webpack-config-single-spa library to mindlessly configure Webpack:

const { merge } = require("webpack-merge");
const singleSpaDefaults = require("webpack-config-single-spa");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = (webpackConfigEnv, argv) = > {
  const orgName = "react-mf";
  const defaultConfig = singleSpaDefaults({
    orgName,
    projectName: "root-config",
    webpackConfigEnv,
    argv,
    disableHtmlGeneration: true});return merge(
    defaultConfig,
    {
      plugins: [
        new HtmlWebpackPlugin({
          inject: false.template: "src/index.ejs".templateParameters: {
            isLocal: webpackConfigEnv && webpackConfigEnv.isLocal,
            orgName,
          },
        }),
      ],
    },
    {
      // modify the webpack config however you'd like to by adding to this object}); };Copy the code

Page components

There are two main components: people and plants. React implements both of these components.

// root.component.js Root component
import React from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import PeoplePage from "./people-page/people-page.component.js";

export default function Root(props) {
  return (
    <BrowserRouter>
      <Route path="/people/:personId" component={PeoplePage} />
      <Route path="/people" component={PeoplePage} exact />
    </BrowserRouter>
  );
}
Copy the code

Single-spa provides a single-spa-React library to help export life cycles:

/ / the react - mf - people. JSX entrance
import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  errorBoundary() {
    return <div>Error</div>;
  },
  loadRootComponent: () = >
    import(
      /* webpackChunkName: "people-root-component" */ "./root.component.js"
    ).then((mod) = > mod.default),
});

export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;

export function getFilmsComponent() {
  return import(
    /* webpackChunkName: "films-component" */ "./films/films.component.js"
  );
}
Copy the code

Seeing this, I believe you will have a question: Single-SPA JS Entry should have high requirements for packaging, right? Alas, single-Spa thought of this too, so they also provided us with a webpack-config-single-spa-React library to help us better configure WebPack:

// webpack.config.js
const { merge } = require("webpack-merge");
const singleSpaDefaults = require("webpack-config-single-spa-react");

module.exports = (webpackConfigEnv = {}) = > {
  const defaultConfig = singleSpaDefaults({
    orgName: "react-mf".projectName: "people",
    webpackConfigEnv,
  });

  const config = merge(defaultConfig, {
    // customizations go here
  });

  return config;
};
Copy the code

Similarly, if you use vue, there is the equivalent of Webpack-config-single-spa-vue.

Common components

People and Plants are just components, but they are page-level components. So Navbar still uses single-spa-React to export the life cycle:

// react-mf-navbar.js
import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./root.component";

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  errorBoundary(err, info, props) {
    return (
      <div className="h-16 flex items-center justify-between px-6 bg-primary text-white">
        Error
      </div>); }});export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;
Copy the code

Public suite

The common suite is more like Utils, with the API and StyleGuide microapps.

The entry file for the API is very simple:

// react-mf-api.js
export { fetchWithCache } from "./fetchWithCache.js";
Copy the code

Import functions directly on people and Plants using es6-like syntax via SystemJS import-map loading:

// utils/api.js
import { fetchWithCache } from "@react-mf/api";

export function getPeople(pageNum = 1) {
  return fetchWithCache(`people/? page=${pageNum}`);
}
Copy the code

Styleguide’s most important actions are the introduction of global CSS and the export of a Button:

// react-mf-styleguide.js
import "./global.css? modules=false";

// You can also export React components from this file and import them into your microfrontends
export { default as Button } from "./button.component.js";
Copy the code

Seeing this, you might say: why not use the NPM import method? Seems to have nothing to do with the micro front end?

Yes, the common suite here really has nothing to do with a micro front end, except that Single-SPA treats this common library as a micro application. You can say it’s a micro app, it’s a micro app, but introduced in SystemJS style, it’s kind of like taking off your pants. And to dynamically import JS in index. HTML, you can only import third-party libraries in SystemJS mode.

import-map

Import-map is just a JSON file that holds some common resources:

{
  "imports": {
    "single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js"."react": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"."react-dom": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js"."rxjs": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/es2015/rxjs.min.js"."rxjs/operators": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/es2015/rxjs-operators.min.js"}}Copy the code

This project is not directly use the library at present, just quote this address storage.googleapis.com/react.micro… JSON file.

The import-map method of importing JS libraries was originally implemented in Chrome. The purpose of this method is to solve the problem of importing JS dynamically and writing it as ES6: Import React from ‘React’, and can dynamically load JS in index. HTML:

<script>
  System.import('@react-mf/root-config');
  System.import('@react-mf/styleguide');
</script>
Copy the code

P.S. SystemJS has been a way of packaging for a long time, so don’t worry: why not?

Note: Import-map here is not a proprietary feature of SystemJS and can be used on older browsers. However, SystemJS can handle some of the compatibility issues when platforms use import-Map.

conclusion

So, here’s a quick summary of single-SPA’s React-Microfrontends Example:

  • Root-config is the “brain” of the entire application, which can also be understood as the main application, but the application is very simple, there is only oneindex.htmlAnd amain.jsEntry JS composition
  • All main and micro apps need to be packaged in SystemJS, and if you don’t know how to configure them, Single-SPA offers a number of Webpack widgets:webpack-config-single-spa.webpack-config-single-spa-react, feel free to use
  • Microapplications include framework components, plain JS, and CSS. Component needs tosingle-spa-xxxTo export components, ordinary JS is used to export ES6, CSS is usedimport 'xxx.css'It is enough

I believe that after reading this article, you can also have enough knowledge to see other examples.

In this Example, we can see the drawbacks of the single-SPA framework:

  • Too intrusive, not only to export the life cycle in the portal, but also to change the Webpack configuration
  • JS Entry is a simple way to access micro applications. We prefer to access micro applications through an index.html URL
  • Nothing, such as JS, CSS isolation, main micro application communication, etc. These need to be handled by other small libraries

In general, it is not recommended to use single-SPA for micro front end, but the MicroApp of QIANKKun or JINGdong is recommended.