preface

When it comes to micro front frames, many people’s first reaction is single-SPA. But dig a little deeper: what does it do, what does it do, and the answer may be lost.

On the one hand, not many people are studying and using micro fronts. The company would have gone out of business before it had a chance to extend the project with a micro front end.

On the other hand, Chinese blogs have little research on the micro front end. Many articles are simply translated official documents, read a few apis, and put an official Demo. There has been little in-depth research into exactly what single-SPA is all about.

Another aspect is that the single-SPA documentation is very unreadable and just as conceptual as the Redux documentation. When we talk about one thing, we always bring other kula together, and make a simple thing very complicated. The most interesting thing is that the official sample code is all snippets, which can not be put together to create a Demo. However, the Github Demo is very complicated, without any explanation, and it has to clone several Repo after watching it.

Finally, as for yourself, just finished source code and then just a document.

This article will not talk about How to build a Demo, but will talk about the content of the official document from the perspective of “Why” and “How”. I believe you can understand after reading this articleThe official Demo.

A demand

Let’s start with a minimum requirement. One day, the product manager suddenly said: we need to make A page A. I saw that the next group had already made this page A. Could you put it into our project? Let’s go online tomorrow.

At this point, the product manager thinks: Should I just fill in a URL? No more, copy and paste is also very fast. The programmer thinks: It’s shit again. It’s refactoring again. It’s time for a new alliance. Do you have the test data? Wait a minute. Who’s the back end of the link?

** This is probably a common requirement for large projects: moving an existing page. ** I think most people would copy and paste someone else’s code into their own project, refactor it a little bit, test the environment, tweak it, and go live.

However, this will be more than a code, if other people’s page changed, then their own project to follow the synchronous modification, and then the tune, and then online, very troublesome.

So the programmer says, can I fill in a URL, and then the page goes to the project? So, comes in.

The disadvantages of the iframe

Iframe is like opening a window to load another page, but it has a number of disadvantages:

  • You have to load it every time you come in. The state cannot be preserved
  • DOM structures are not shared. So if you have a Modal in your child application, you can only display it in that little area, you can’t display it in full screen
  • Unable to follow the browser forward or backward
  • Natural hard isolation, unable to share resources with the main application, communication is difficult

SPA can solve these problems:

  • Switching routing is switching page components, which can be mounted and unmounted very quickly
  • Single-page applications definitely share the DOM
  • The front end controls the route, think before before, think after after
  • React communication has Redux, Vue Communication has Vuex, can share resources with App components, communication is very cool

This gives us an idea: is it possible to have a giant SPA framework and assemble an existing SPA as a Page Component into a new SPA? This is where the micro front end comes in.

What is a micro front end

The micro front end should have the following features:

  • Technology stack independent, the main framework does not limit access to the application of the technology stack, micro applications have complete autonomy
  • Independent development, independent deployment, independent micro application warehouse, before and after the end can be independent development, deployment is completed after the main framework automatically complete synchronous update
  • Incremental upgrade, in the face of a variety of complex scenarios, it is usually difficult for us to do a full amount of technology stack upgrade or reconstruction of an existing system, and the micro front end is a very good means and strategy to implement incremental reconstruction
  • When running independently, the state is isolated between each microapplication and the runtime state is not shared

Wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait.

The single-SPA framework does not implement any of these features.

Single-spa is exactly what it is

** Single-SPA is simply a scheduler for the life cycle of a child application. ** Single-spa defines boostrap, Load, mount, and unmount lifecycle callbacks for the application:

As anyone who has written about SPA can understand, it’s birth, aging, illness and death. But there are a few caveats:

  • Register is not a lifecycle, it’s an invocationregisterApplicationThe function step
  • Load is to start loading the child application, and it’s up to the developer to decide how to Load it (more on that later).
  • The Unload hook can only be calledunloadApplicationThe function is called

OK, the callback order for the above four lifecycle is controlled by single-SPA, I understand, so when should this set of lifecycle start? There should be an opportunity to start the whole process, or certain processes.

The chance is that when window.location.href matches the URL, it starts the lifecycle of the corresponding child App. Therefore, single-SPA also listens for url changes and then executes the lifecycle process of the subapp.

At this point, we have the broad framework of a single-SPA, which consists of two things:

  • Implement a set of life cycle, load the child app in the load, by the developer to play, other life cycle to do, or by the developer built the child app to play
  • Listen for changes in the URL, and when the URL changes, it makes a subapp become active, and then it goes through the entire lifecycle

Make a sketch as follows:

Does it feel single? – Spa is chicken? Although single-SPA describes itself as a micro front end framework, none of the micro front end features are implemented. They are all implemented by developers when loading their own sub-apps, or through some third party tools.

Registering a subapplication

With that in mind, let’s look at the most important API in single-SPA: registerApplication, which means to register a child application. Use the following:

singleSpa.registerApplication({
    name: 'taobao'.// The child application name
    app: () = > System.import('taobao'), // How to load your child application
    activeWhen: '/appName'.// The url matches the rule that indicates when the subapplication lifecycle begins
    customProps: { // Customizes props from the bootstrap, mount, and unmount callbacks of the child application
        authToken: 'xc67f6as87f7s9d'
    }
})

singleSpa.start() // Start the main application
Copy the code

It registered a child app ‘Taobao’. We implemented our own method of loading the child application, telling single-SPA when to mount the child application via activeWhen, as if we could start the code.

Can be a ghost! Please tell me what the hell System. Import is. Oh, it’s SystemJS. Oh, I’ve heard of SystemJS. What is it? Why use SystemJS? Why use SystemJS?

SystemJS

I believe many of you have read some micro front-end blogs and they will say that single-SPA is based on SystemJS. Wrong! Single-spa has nothing to do with SystemJS! Here’s a diagram of the main and sub-applications:

The idea behind single-SPA is that the main application can be very, very simple and lightweight, so simple that a single index.html + main.js micro front-end can be done without even Webpack. Directly in the browser to perform singleSpa. RegisterApplication to call it a day, this also is in the way of carrying – browser.

However, if THE browser implements JS, let alone import XXX from ‘https://taobao.com’, I can’t even implement ES6 import/export in the browser: import axios from ‘axios’.

<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! `); }Copy the code

However, when importing module dependencies, such as import Axios from ‘axios’, you need to importMap:

<script type="importmap">
    {
       "imports": {
          "vue": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js"}}</script>

<div id="container">I am a: {{name}}</div>

<script type="module">
  import Vue from 'vue'

  new Vue({
    el: '#container'.data: {
      name: 'Jack'}})</script>
Copy the code

The function of importMap is to tell ‘vue’ that this thing needs to start from “cdn.jsdelivr.net/npm/[email protected]…” It’s from here. However, ImportMap is now supported only by Chrome.

So, SystemJS completes this. Of course, in addition to importMap, it has many functions, such as getting all the currently loaded modules, the URL of the current module, can import HTML, import CSS, import WASM.

Wait, can’t you do that in Webpack? Webpack can import less, import SCSS? That’s not as good as SystemJS, right? Yes, no one would use SystemJS if it weren’t for import/export in the browser. SystemJS has one and only one advantage: the use of ES6 import/export in the browser.

Because SystemJS can use ES6 import/export in the browser and support dynamic import, it is exactly in line with the in-browser implementation idea advocated by single-SPA. The Github Demo still uses The ImportMap mechanism of SystemJS to introduce different modules:

<script type="systemjs-importmap">
    {
      "imports": {
        "@react-mf/root-config": "//localhost:9000/react-mf-root-config.js"}}</script>

<script>
  singleSpa.registerApplication({
    name: 'taobao'.// The child application name
    app: () = > System.import('@react-mf/root-config'), // How to load your child application
    activeWhen: '/appName'.// The url matches the rule that indicates when the subapplication lifecycle begins
    customProps: { // Customizes props from the bootstrap, mount, and unmount callbacks of the child application
        authToken: 'xc67f6as87f7s9d'}})</script>
Copy the code

The public relies on

Another benefit of SystemJS is that common dependencies can be introduced through ImportMap.

If we have three subapplications that all have the common dependency ANTD, then each subapplication will be packaged with a copy of ANTD code, which is redundant.

One solution is to import ANTD code directly into the main application via ImportMap, and subapplications to exclude antD when Webpack sets external to package it. The subapplication packaging doesn’t pack antD into it, and it gets smaller.

Some people will say: I use CDN to introduce not? No, because the child application code is import {Button} from ‘antd’, how can the browser directly recognize ES6 import/export? Not to mention SystemJS.

Isn’t there a way Webpack can implement the effects of ImportMap? Webpack 5 comes up with the Module Federation Module which can do the effect of ImportMap very well. This is a new feature of Webpack 5 and works much the same as ImportMap. For more information on what module federation is, see this article.

As for whether to use ImportMap or Webpack Module Federation, singles- SPA recommends using Importmap, but there is no reason in the document against using Webpack Module Federation. OK if it works.

SystemJS vs Webpack ES

One might think: How can you write JS in a browser after 1202 years? Not the last Webpack is embarrassed to say that they are front-end development.

Yes, Webpack is very powerful, and you can take advantage of Webpack’s many capabilities to make your main application more flexible. For example, write less, SCSS, Prefetch for Webpack, etc. Then you can take advantage of Webpack’s dynamic introduction when registering your subapplications:

singleSpa.registerApplication({
    name: 'taobao'.// The child application name
    app: () = > import('taobao'), // How to load your child application
    activeWhen: '/appName'.// The url matches the rule that indicates when the subapplication lifecycle begins
    customProps: { // Customizes props from the bootstrap, mount, and unmount callbacks of the child application
        authToken: 'xc67f6as87f7s9d'}})Copy the code

So why does single-SPA recommend SystemJS? My guess is that single-SPA wants the main application to be a shell, just where the content is, and all functionality and interaction to be managed by index.html.

Of course, this is just an idea that can be completely disobeyed. Personally, I prefer to use Webpack a bit more, but SystemJS is still a bit redundant and feels a bit altman. However, in order to keep pace with the documentation, it is assumed that the main application will be implemented using SystemJS.

Root Config

Since single-SPA emphasizes the in-browser approach to the main application, index.html acts as a static resource, path declaration for the child application.

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Polyglot Microfrontends</title>
  <meta name="importmap-type" content="systemjs-importmap" />
  <script type="systemjs-importmap" src="https://storage.googleapis.com/polyglot.microfrontends.app/importmap.json"></script>
  <% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@polyglot-mf/root-config": "//localhost:9000/polyglot-mf-root-config.js"}}</script>The < %} % ><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/import-map-overrides.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/amd.min.js"></script>
</head>
<body>
  <script>
    System.import('@polyglot-mf/root-config');
    System.import('@polyglot-mf/styleguide');
  </script>
  <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>
</html>
Copy the code

Main.js, on the other hand, implements sub-application registration and main application startup.

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

registerApplication({
  name: "@polyglot-mf/navbar".app: () = > System.import("@polyglot-mf/navbar"),
  activeWhen: "/"}); registerApplication({name: "@polyglot-mf/clients".app: () = > System.import("@polyglot-mf/clients"),
  activeWhen: "/clients"}); registerApplication({name: "@polyglot-mf/account-settings".app: () = > loadWithoutAmd("@polyglot-mf/account-settings"),
  activeWhen: "/settings"}); start();// A lot of angularjs libs are compiled to UMD, and if you don't process them with webpack
// the UMD calls to window.define() can be problematic.
function loadWithoutAmd(name) {
  return Promise.resolve().then(() = > {
    let globalDefine = window.define;
    delete window.define;
    return System.import(name).then((module) = > {
      window.define = globalDefine;
      return module;
    });
  });
}
Copy the code

Such a resource declaration + component loaded by the master application, single-SPA is called Root Config. It’s not a new concept, it’s just two things:

  • A main application index.html
  • An implementationregisterApplicationFunction JS file

single-spa-layout

While an index.html is a perfect lightweight micro front end for the main application, even if you want to compress the interactions of the main application, you have to tell the sub-applications where to put them. As much trouble?

To solve this problem, single-spa said: “Ok, I will do it for you, and then created a single-spa-layout. See the code:

<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>
Copy the code

The Vue Router is the same as the Vue Router. Of course, this is straightforward, but browsers don’t recognize these elements, so single-spa-Layout wraps the logic of recognizing these elements into functions and exposes them to the developer. The developer needs only to call this function to recognize the appName and other information:

import { registerApplication, start } from 'single-spa';
import {
  constructApplications,
  constructRoutes,
  constructLayoutEngine,
} from 'single-spa-layout';

/ / access routes
const routes = constructRoutes(document.querySelector('#single-spa-layout'));

// Get all the child applications
const applications = constructApplications({
  routes,
  loadApp({ name }) {
    return System.import(name); // SystemJS introduces the entry JS}});/ / generated layoutEngine
const layoutEngine = constructLayoutEngine({ routes, applications });

// Batch register sub-applications
applications.forEach(registerApplication);

// Start the main application
start();
Copy the code

ConstrcutRoutes, constructApplication, and constructLayoutEngine are essentially identifying the element tag defined by a single-spa-layout and retrieving its attributes. Register them one by one through the registerApplication function.

Reformer application

So that’s all about the main app, now let’s focus on the instant app.

The most critical step for a child application is to export the bootstrap, mount, and unmount lifecycle hooks.

import SubApp from './index.tsx'

export const bootstrap = () = > {}
export const mount = () = > {
  // Use React to render the root component of the child application
  ReactDOM.render(<SubApp/>.document.getElementById('root'));
}
export const unmount = () = > {}
Copy the code

single-spa-react, single-spa-vue, single-spa-angular, single-spa-xxx, …

Emmmm, what do you say? The above three exports are not very attractive. Is there a more direct way to achieve the export of the three life cycles?

Single-spa said: Sure, fuck! Hence the single-SPA-react:

import React from 'react';
import ReactDOM from 'react-dom';
import SubApp from './index.tsx';
import singleSpaReact, {SingleSpaContext} from 'single-spa-react';

const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: SubApp,
  errorBoundary(err, info, props) {
    return (
      <div>Error!</div>); }});export const bootstrap = reactLifecycles.bootstrap;
export const mount = reactLifecycles.mount;
export const unmount = reactLifecycles.unmount;
Copy the code

“I can’t just do it with React,” says Single-Spa. “I have to do it with all the other frames as well, so here are the ones:

Can not help feeling: these small wheels are really able to build ah.

Import the CSS of the child application

Do you notice that we only imported a JS file with system.import in the subapplication registration? Maybe system.import (‘xxx.css’).

However, there is a problem: how do you remove the existing CSS when unmount when switching applications? Here’s the official answer:

const style = document.createElement('style');
style.textContent = `.settings {color: blue; } `;

export const mount = [
  async() = > {document.head.appendChild(styleElement);
  },
  reactLifecycles.mount,
]

export const unmount = [
  reactLifecycles.unmount,
  async() => { styleElement.remove(); }]Copy the code

Me: single-spa, please do a personal, build a Demo, and I will deal with CSS? Single-spa said, ok, I’ll build another wheel. Hence single-SPA-CSS. The usage is as follows:

import singleSpaCss from 'single-spa-css';

const cssLifecycles = singleSpaCss({
  // Put your exported CSS here. If webpackExtractedCss is true, you can leave it unspecified
  cssUrls: ['https://example.com/main.css'].// Whether to use the exported CSS from Webpack. The default is false
  webpackExtractedCss: false.// Whether unmount is removed. Default is true
  shouldUnmount: true.// Timeout, no nonsense, you understand
  timeout: 5000
})

constreactLifecycles = singleSpaReact({... })// Add it to the bootstrap of the child application
export const bootstrap = [
  cssLifecycles.bootstrap,
  reactLifecycles.bootstrap
]

export const mount = [
  // Add the mount to the child application. It must be in front of the mount, otherwise the style will flash after the mount
  cssLifecycles.mount,
  reactLifecycles.mount
]

export const unmount = [
  // Same as mount
  reactLifecycles.unmount,
  cssLifecycles.unmount
]
Copy the code

Note here that example.com/main.css above is not as easy to use as it seems.

If you use Webpack, you will most likely use subcontract or contenthash to name the CSS file, such as filename: “[name].[Contenthash].css”. How do I write cssUrls? Do I change the cssUrls parameter every time? It’s too much trouble.

Single-spa-css says: I can get the actual exported CSS file name using the __webpack_require__.cssAssetFileName exported from Webpack. ExposeRuntimeCssAssetsPlugin this plug-in can solve this problem. In this way, you can put the real NAME of the CSS exported from Webpack into the cssUrls without specifying it.

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 here must match the filename for the MiniCssExtractPlugin
      filename: "[name].css",})]};Copy the code

Subapplication CSS style isolation

While single-SPA-CSS solves the problem of introducing and removing CSS for sub-applications, it raises another problem: how do you ensure that the styles of sub-applications don’t interfere with each other? The official advice:

The first method is to use Scoped CSS, which means to prefix the CSS selectors for the child application, like this:

.app1__settings-67f89dd87sf89ds {
  color: blue;
}
Copy the code

If you don’t want to bother, you can use PostCSS Prefix Selector to automatically Prefix styles in Webpack:

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

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

Another way to achieve perfect style isolation is to mount the child application to the Shadow DOM in the function that loaded the child application. What is Shadow DOM and how to play visible MDN here.

What about common CSS styles

So with all of these subapplications having their own CSS styles, what about sharing CSS between subapplications? For example, if you have two child applications that use antd, you will need to import antD.min.css twice.

This problem is similar to the problem of dealing with “public dependencies” mentioned above. Officials have two suggestions:

  1. Add the common CSS to importMap, which can also be understood as adding a link to the index.html to get the ANTD CSS
  2. Import all public UI libraries into utility, export all antD contents, put utility packages into importMap, and thenimport { Button } from '@your-org-name/utility';To introduce the components inside

In fact, the above two methods are much the same, the idea is to introduce a wave of main application, only a unified introduction of CSS, the other unified introduction of UI library.

JS isolation for child applications

Let’s think about the nature of the JS isolation of the application. The essence is to use the variables in the window global object in the B subapplication, and not to be polluted by the A subapplication.

A simple solution is to add global variables such as $in jQuery or _ in lodash. When unmount A child application, use an object to record the global variables that were added to window, and remove all variables that were added to window from A child application. The next time you mount A, just add back the recorded global variables.

Single-spa comes forward again: this one doesn’t require you to manually record window changes yourself. Single-spa-leaked -globals has been implemented, so use it directly:

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

// Other life cycle functions provided by single-SPA-xxx
const frameworkLifecycles = ...

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

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

export const mount = [
  leakedGlobalsLifecycles.mount, // Add global variables to mount, and restore them if they were previously recorded
  frameworkLifecycles.mount,
]

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

However, the limitation of this library is: each URL can only add one subapp, if multiple subapps will still access the same Window object, and therefore will interfere with each other, and can not achieve a perfect JS sandbox.

For example, if the navigation bar of a page is using jQuery 3.0 and the body of the page is using jQuery 5.0, there will be a conflict.

Therefore, the scenario of this library is limited to: the home page using 3.0 jQuery, order details page using 5.0 jQuery such as the scenario.

Classification of subapplications

As mentioned above, when the URL matches the activeWhen parameter, the lifecycle of the corresponding sub-application is executed. The child application is now bound to the URL.

Single-spa-leaked -globals, single-SPa-CSS, and other libraries have export lifecyls, but these lifecyls have little to do with page rendering and URL changes.

The only difference between them and a normal application is that the life cycle of a normal application is automatically scheduled via a single-SPA, whereas these libraries are scheduled manually. We just choose to call them manually during the lifecycle of the child application.

This URL-independent “app” also plays an important role in the micro front end, typically providing functionality during the life cycle of the child application, such as single-SPA-CSS, which is added at mount time<link/>The label. Single-spa calls such a “class subapp” a Parcel.

Single-spa also separates another class: Utility Modules. Many subapplications use ANTD, DAYJS, and AXIos, so you can make a utility collection of these public libraries, and then do the unified export, and then import unified in importMap. Child applications don’t need to add antD, DayJS, and AXIos dependencies to their package.json.

To summarize, single-SPA divides micro fronts into three broad categories:

classification function export Whether it’s related to the URL
Application The child application bootstrap, mount, unmount is
Parcel Patch the lifecycle of functional components, such as subapplications bootstrap, mount, unmount, update no
Utility Module Public resources All public resources no

create-single-spa

With a bunch of subapplication-related libraries introduced above, it would be difficult to configure your subapplication slowly from zero. So, single-SPA says: no trouble, there are scaffolding tools, one-line command generation sub-apps, all set up for you.

npm install --global create-single-spa

# or
yarn global add create-single-spa
Copy the code

then

create-single-spa
Copy the code

Attention! Create-single-spa here means create child applications!

conclusion

That’s all you have in the Singles – SPA document (except for SSR and Dev Tools, which you don’t use very much, and Dev Tools for yourself). Since this article goes from problem finding to problem solving, it’s a little messy from beginning to end. Here’s a summary:

Micro front concept

Features:

  • Technology stack independence
  • Independent development, independent deployment
  • The incremental upgrade
  • Stand-alone runtime

single-spa

Just do two things:

  • Provides the lifecycle concept and is responsible for scheduling the lifecycle of sub-applications
  • Hostage URL change event and function, URL change match corresponding sub-application, and execute the lifecycle process

Three categories:

  • Application: Subapplication, strongly related to URL, subject to single-SPA call lifecycle
  • Parcel: component, independent of URL, that calls the life cycle manually
  • Utility Module: A Module that exports common resources

“Important” concepts

  • Root Config: indicates the index. HTML + main.js of the primary application. HTML is responsible for declaring resource paths, and JS is responsible for registering sub-applications and starting the main application
  • Application: To expose bootstrap, mount, and umount lifecycle, generally start rendering sub-SPA applications at mount
  • Parcel: Also expose the bootstrap, mount, and unmount lifecycle, and can expose the Update lifecycle. Parcels can be as large as an Application or as small as a functional component. Unlike Application, Parcel needs to be manually invoked by development

SystemJS

You can use ES6 import/export syntax in the browser to specify the address of the dependent library with importMap.

It has nothing to do with single-SPA, but in-browser import/export is consistent with in-browser run time advocated by single-SPA, so single-SPA uses it as the main import/export tool.

There is no good or bad way to use Webpack for dynamic introduction, no good way to use Webpack, and probably even better than SystemJS.

single-spa-layout

Similar to the Vue Router, the main feature is that you can specify where to render which sub-applications in the index.html.

single-spa-react, single-spa-xxx….

Bootstrap, mount, and unmount life cycle functions for child applications.

single-spa-css

Isolate the CSS styles applied by the two children.

The CSS of a child application is added when the child application is mounted and deleted when the child application is unmounted. Child applications use Webpack export CSS file, want to cooperate with ExposeRuntimeCssAssetsPlugin plugin to get the final export CSS file name.

Half of the CSS sandbox is implemented.

If you want to isolate styles in multiple child applications, you can do this in two ways:

  • Shadow DOM, style isolation is a good method, but penetration is more troublesome
  • Scoped CSS, which prefixes the CSS selectors for subapplications, can be usedpostcss-prefix-selectorThis package is used to quickly add prefixes

single-spa-leaked-globals

Restore/add some global variables to the window object, such as $in jQuery or _ in lodash, and remove the variables from the window object when unmount is applied.

Implementation of “if the main application url only one page” JS sandbox.

The public relies on

There are two ways to deal with this:

  • Create a Utility Module package that exports all common resource content and declare it in the main application’s index.html with the ImportMap of SystemJS
  • Use the Webpack 5 Module Federation feature to import common dependencies

Which is better? Will do.

The last

Single-spa documentation is that all? Yeah, that’s it. The documentation seems to give a lot of “best practices,” but there are very few that actually put all the “best practices” together and implement them.

For example, the document says that Shadow CSS is used for style isolation between sub-applications, but single-spa-leaked-globals does not allow anyone to mount multiple sub-applications on the same URL. It feels so unreliable: here it works, there it doesn’t.

Back to Shadow CSS for style isolation, but there’s no detail on how to do it. There are many examples like this: documentation often only tells one way, and it is up to the developer to follow it. This gives the impression that you have only solved half the problem.

If you really want to play a small Demo with single-SPA, you can build a micro front end with the libraries mentioned above, but it’s not that easy to use in a production environment.

So, in order to fill in the hole left by single-Spa, Ali built the Qiankun micro front frame based on single-SPA, which really implemented all the features of the micro front, but that’s a story for another day.