Single-spa microfront-end architecture

Single – what is spa

Single-spa is a JavaScript micro-front-end framework that aggregates multiple single-page applications into a Single application. Benefits:

  • Use multiple front-end frameworks on the same page without refreshing the page (React, AngularJS, Angular, Ember, frameworks you’re using)
  • Deploy each single-page application independently
  • New features use new frameworks, and old single-page applications can coexist without being rewritten
  • Improved initial loading time, late loading code

What does Single-SPA do

Single-spa is a top-level route. When the route is active, it downloads and executes the relevant code under the route.

Routed code is called an application, and each code can (optionally) have its own Git repository, CI process, and can be deployed independently. These applications can be implemented using either the same or different frameworks.

What single-spa includes:

  • 1. Applications, each application itself is a complete SPA (sort of). Every application can respond to URL-routing events and must know how to initialize, mount, and uninstall itself from the DOM. The main difference between a traditional SPA application and a Single SPA application is thatThey must be able to coexist with other applications, and they do not have separate HTML pages.

For example, React or Vue SPA are applications. When activated, they listen for URL routing events and place content on the DOM. When they are inactive, they do not listen for URL-routing events and are completely removed from the DOM.

  • A single-SPa-config configuration, which is the HTML page and JavaScript to register the application with Single SPA. Each application registers three things
    • A Name (Application identifier)
    • A function (code to load the application)
    • A function (Determine when the application is active/inactive)

Use of single-SPA

Single-spa works with ES5, ES6 +, TypeScript, Webpack, SystemJS, Gulp, Grunt, Bower, EMber-CLI, or any available build system. You can install it by NPM, install it by JSPM, and even use <script> tags if you like.

Single-spa is used in the new project

1. Create a fairly simple create-single-spa CLI

Github.com/single-spa/…

# global install
npm install --global create-single-spa
# or
yarn global add create-single-spa
Execute after #
create-single-spa


# Local install
npm init single-spa
# or
npx create-single-spa
# or 
yarn create single-spa


Copy the code

Recommended Settings

We recommend using the ES module in the browser + import Maps (or SystemJS if you need better browser support) for these Settings. This setup has several advantages:

    1. Public modules are easy to manage and download only once. If you use SystemJS, you can also preload them for speed.
    1. Sharing code/functions/variables is as simple as importing/exporting as it is set up in one whole.
    1. Lazy-loading applications is easy, which allows you to speed up initial load times.
    1. Each application (aka microservices, aka ES modules) can be developed and deployed independently. Teams can work at their own pace, experiment (within reasonable limits defined by the organization), QA, and deploy. This also usually means that release cycles can be shortened to days, rather than weeks or months.
    1. Great developer experience (DX): Go to the Dev environment and add an import map that points the application’s URL to your localhost. See the sections below for more information.

Types of microfronts in single-SPA

    1. Single-spa Applications: A micro front end for rendering components for a specific set of routes.
    1. Single-spa sing: Micro-front-end for rendering components that is not routing-controlled.
    1. Utility Modules: Non-rendering components that expose a micro front end to shared javascript logic.
Container Root The application The sandbox Public module
The main road by There are multiple routes No routing No routing
API Statement of the API The necessary API No single – spa API
To render the UI To render the UI To render the UI Do not render the UI directly
The life cycle Single-spa management lifecycle Customize the management life cycle No life cycle
When to use it Core building Blocks Only needed in multiple frameworks Used when sharing common logic

The application

Single-spa provides the registerApplication API to register applications

The sandbox

The main idea is to let you reuse the UI between applications when you write them in multiple frameworks. Manage the life cycle of the system by which mountParcel or mountRootParcel is installed and returned immediately. Unmount a parcel that needs to be manually called.

The system is best suited for sharing the UI between frameworks.

For example, Application1 is written in Vue and contains all the UI and logic for creating a user. Application2 is written in React and requires the creation of a user. Single-spa SING allows you to package application2Vue components. Although the framework is different, it can run inside ‘Application2’. The system will be the single-SPa-specific implementation of the Web component.

Public module

Share common logic, which can be a common JS object such as login authorization, fetch data 1. Each application accesses the server, which creates repetitive work in each application. 2. Using the public module, create a module that implements the authorization logic and use the authorization through export/import

Root Config

Two configurations in the root directory to launch the single-SPA application

  • Root Html page shared by all micro-front-end applications [index.ejs]
  • callsingleSpa.registerApplication()Js [study-root-config.js]
// single-spa-config.js
import { registerApplication, start } from 'single-spa';

// Param1: identifies an application
// param2: Function Specifies the code to be executed by the application
// Param3: Function When to activate these applications: active route
// Param4: Optional extended parameter
registerApplication(
  'app2'.() = > import('src/app2/main.js'), 
  (location) = > location.pathname.startsWith('/app2'), 
  { some: 'value'}); registerApplication({name: 'app1'.app: () = > import('src/app1/main.js'),
  activeWhen: '/app1'.customProps: {
    some: 'value'}); start();Copy the code

Parameters that

  • Name: application identifier, which must Sting

  • Loading Function or Application registerApplication can be a Promise type Loading Function, or it can be an Application that has been resolved.

    const application = {
      bootstrap: () = > Promise.resolve(), //bootstrap function
      mount: () = > Promise.resolve(), //mount function
      unmount: () = > Promise.resolve(), //unmount function
    }
    registerApplication('applicationName', application, activityFunction)
    Copy the code
  • The second argument to the loading function registerApplication must be a function (or “async function” method) that returns a promise. This function has no input parameters and is called the first time the application is downloaded. The result returned after Promise Resolve must be an application that can be resolved. The common implementation is to use import loading :() => import(‘/path/to/application.js’)

  • The third parameter of the activation function is a pure function (only dependent on parameters, no side effects) that determines which application is activated based on location.path. Single-spa finds applications based on top-level routing, and each application handles its own child routes. Wildcard configuration: ‘/users/:userId/profile’ Multipathing configuration: [‘/ pathName /#/hash’, ‘/app1’]

    Includes the following

    1. When hashchange or popState is triggered

    PushState or replaceState is called; manually call [triggerAppChange] on single-SPA; checkActivityFunctions is called

  • Lifecycle is passed to single-spa’s lifecycle function

    singleSpa.registerApplication({
      name: 'myApp'.app: () = > import('src/myApp/main.js'),
      activeWhen: ['/myApp'.(location) = > location.pathname.startsWith('/some/other/path')].customProps: {
        some: 'value',}}); singleSpa.registerApplication({name: 'myApp'.app: () = > import('src/myApp/main.js'),
      activeWhen: ['/myApp'.(location) = > location.pathname.startsWith('/some/other/path')].// Function, parameter 1: application name: myapp, parameter 2: window.location
      customProps: (name, location) = > ({
        some: 'value',})});Copy the code
  • The final call to the singlespa.start () start() method must be called by the single-spa configuration file for the application to actually be mounted. Before start is called, the application is downloaded, but not initialized/mounted/unmounted.

    import { start } from 'single-spa';
    /* Calling start before registering the app means that single-SPA can install the app immediately without waiting for any initial setup for the single-page app. * /
    start();
    // Register the application...
    Copy the code
  • Register two routes at the same time

One path change and both applications are activated? You can.

You need an ID that starts with the prefix single-SPa-Application followed by the name of your application. For example, if your app is named app-name, create a div with id single-SPa-Application :app-name.


<div id="single-spa-application:app-name"></div>
<div id="single-spa-application:other-app"></div>

Copy the code

Building an

A single-SPA application is the same as a normal single-page application, except it doesn’t have an HTML page. In a single SPA, there are N more registered applications that can be framed differently, maintain their own routing, and only need to be mounted to render their own pages and functions. Mounted refers to whether the registered application content is displayed in the DOM. We can tell if an application is mounted by its activity function. Hibernates until it is not mounted.

Creating and Registering an application To add an application, you first need to register the application. Once an application is registered, it must implement the lifecycle functions described below in its entry point file.

The life cycle

  • Loaded: The registered application is downloaded at the first time of the activity. During the download process, as few operations as possible are performed. If you need to perform the operations during the download, you can put them in the child application entry file.
  • Initialization (bootstrap/ Initialized) : Required The initialization is performed once before the first mount
  • Mounted required. The application activates the main route based on the url, creates the DOM, listens for events, and render. Child route changes (e.g. Hashchange or popState) are no longer triggered and must be handled by the application itself
  • Unmounted Required. Triggered when an application changes from active to deactivated, the dom,event, memory, global variables, and message subscriptions of the mounted application are cleared
  • Applications do not need to be removed. Removed applications are re-initialized the next time they are unloaded. Hot downloads can be achieved.

Note:

Bootstrap, mount, and unmount is mandatory, and unload is optional. 2. Lifecycle functions must return values, either Promise or async functions. 4. If single-SPA is not started, each application will be downloaded, but not initialized, mounted, or unmounted.

Millis: the number of milliseconds of warning output by the final console warningMillis: The number of milliseconds in which the warning is output

Switch application transition in the life cycle function to achieve their own filtering effect demo: github.com/frehner/sin… Github.com/reactjs/rea…

The old project was moved to Single-SPA

Break up the application

Front-end system application

  • 1. One repository, one build package Advantages: easy to deploy, advantages of single version control (MonorePO) disadvantages: the larger the project, the slower the packaging; Build deployments are bundled together and cannot be released AD hoc
  • 2. Advantages of NPM package: Familiar with development and easy to implement; It can be packaged separately before publishing to NPM: the parent application must be reinstalled and the child application rebuilt for deployment
  • 2. Dynamic loading of modulesAdvantages: flexible, independent code weakness: slightly difficult to build
    1. Web server, create dynamic scripts to load the correct version of the child application;
    2. Use module loading such as systemJs to dynamically download and execute JS in the browser

Migrating the current application

Three steps

1. Create a single-SPA configuration 2. Convert the SPA application to a registered application 3

The single-SPA ecosystem includes single-SPA support for most frameworks single-spa.js.org/docs/ecosys… To implement it yourself, you need to be able to clean up its DOM nodes in unmount, listen for DOM events (all of them, especially hashchange and popState), and free up memory.

2. Solve the dependency problems of CSS, font and script after the existing SPA application is converted to non-HTML application, these resource dependency problems need to be solved: one solution is all packaged into JS; What about the other options?

The sandbox Parcels

An advanced feature of single-SPA that is framework independent and has the same API as the registered application, except that the Parcel component needs to be mounted manually rather than passively activated through the activity method. Try not to use it until you are familiar with it.

The sample

// The implementation of a parcel
const parcelConfig = {
  bootstrap() {
    / / initialization
    return Promise.resolve()
  },
  mount() {
    // Use a framework to create and initialize the DOM
    return Promise.resolve()
  },
  unmount() {
    // Use a framework to unload the DOM and do other cleanup
    return Promise.resolve()
  }
}
// How do I mount a parcel
const domElement = document.getElementById('place-in-dom-to-mount-parcel')
const parcelProps = {domElement, customProp1: 'foo'}
const parcel = singleSpa.mountRootParcel(parcelConfig, parcelProps)
// The parcel is mounted and ends the mount in mountPromise
parcel.mountPromise.then(() = > {
  console.log('finished mounting parcel! ')
  // If we want to re-render the parcel, we call the Update lifecycle method, which returns a Promise
  parcelProps.customProp1 = 'bar'
  return parcel.update(parcelProps)
})
.then(() = > {
  // Call the unmount lifecycle method here to unload the parcel. Return to the promise
  return parcel.unmount()
})
Copy the code

Pacel configures a parcel as an object consisting of three or four methods. Each method returns a PRMISE. The life cycle is basically the same as the application.

  • Bootstrap is called once before a parcel is first mounted
  • When mount is triggered when the mountParcel method is called and the parcel is not mounted, DOM elements are created, event listeners are initialized, and display content is provided to the user.
  • A parcel has been mounted and meets one of the following conditions: 1. Unmount () has been called. 2. The parent parcel or application is uninstalled
  • This parameter is optional when a user calls parcel. Update () to ensure that a parcel is implemented

Single – spa API

Refer to zh-hans.single-spa.js.org/docs/api

Single – the expansion of the spa

In general, the problems that the micro front end needs to solve fall into two categories:

1. Application loading and switching 2. Application isolation and communication

The problems to be solved in application loading and switching include routing, application entry, and application loading. Application isolation and communication problems to be solved include: JS isolation, CSS style isolation, communication between applications.

Single-spa does a good job of solving both routing and application entry issues, but it does not solve the problem of application loading. Instead, it exposes the problem to the user (usually with system.js or native script tags). On this basis, Qiankun encapsulated an application loading solution (import-HTML-Entry), provided solutions to JS isolation, CSS style isolation and inter-application communication, and also provided preloading functions.

Single – spa principle

Application entry Single-SPA uses a protocol entry, that is, as long as the single-SPA entry protocol specification is implemented, it is loadable application. Single-spa’s specification requires that the application entry must expose the following three lifecycle hook functions, and that it must return a Promise to ensure that single-SPA can register callback functions:

The application is loaded

<script type="systemjs-importmap">
  {
    "imports": {
      "app1": "http://localhost:8080/app1.js"."app2": "http://localhost:8081/app2.js"."single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js"
    }
  }
</script>
... // Dependencies for system.js

<script>
(function(){
  / / load single - spa
  System.import('single-spa').then((res) = >{
    var singleSpa = res;
    // Register child applications
    singleSpa.registerApplication('app1'.() = > System.import('app1'),
      location= > location.hash.startsWith(`#/app1`); ; singleSpa.registerApplication('app2'.() = > System.import('app2'),
      location= > location.hash.startsWith(`#/app2`); ;/ / start single - spa
    singleSpa.start();
  })
})()


</script>

// Single-spa's start method
export function start(opts) {
  started = true;
  if (opts && opts.urlRerouteOnly) {
    setUrlRerouteOnly(opts.urlRerouteOnly);
  }
  if(isInBrowser) { reroute(); }}Copy the code

The downside of single-SPA is that you have to manually implement the application loading logic to list the resources that need to be loaded for each sub-application, which can be very difficult in large projects (especially if file name hashes are used). In addition, it can only use JS files as entry points, not HTML directly, which makes it difficult to embed child applications, which is why single-SPA cannot load jQuery applications directly. Single-spa just loads apps onto a page, and it’s hard to guarantee that apps will work together

Qiankun Solution

Github.com/umijs/qiank…

1. NPM plug-in import-HTml-entry is used for application loading

Main method: importHTML(URL, opts = {})

To put it simply, importHtml fetches the contents of remote scripts and style files through fetch. Then, regular expressions are used to extract JS and CSS into their respective arrays. Js can be eval and exported for other modules to call

2, CSS, JS isolation

  • Style isolation is achieved by loading HTML through importHtml and converting external styles to internal styles (using a shandow DOM or Vue scope style)
  • ExecScripts method: generate a window proxy object for the application, passed in as a parameter, to ensure that the global window is not affected; Isolation is implemented using snapshots in Internet Explorer 11
// Implement JS isolation normally
(function(window.arguments){
  // do something}) (window)

// execScripts
(function(window.arguments){
  // do something}) (window.proxy)

Copy the code

4. Application communication

/ / base
import { initGlobalState, MicroAppStateActions } from 'qiankun';

const initialState = {};
const actions: MicroAppStateActions = initGlobalState(initialState);

export default actions;


// Listen in the child application
actions.onGlobalStateChange (globalState, oldGlobalState) {
  ...
}

// Change in subapplicationactions.setGlobalState(...) ;Copy the code

Webpack5 module federation VS single-spa

Module federation: Webpack is limited by packaging tools and ecology, single-SPA: there are already some mature solutions: Qiankun & JD’s MicroApp

Jingdong (micro MicroApp the front frame and ground practice mp.weixin.qq.com/s/6A6TqQpWg… QA:

How do I share state between applications

1. It is recommended to avoid application sharing state as far as possible. If it occurs, re-demarcation of application boundaries can be given priority.

  1. Create a shared API request library that can cache requests and their responses. If the same API is hit repeatedly by multiple applications, cache data is used.
  2. Expose the shared state as an export that other libraries can import. Observable values (such as RxJS) are useful here because they are able to stream new values to the subscriber.
  3. Use Custom Browser Events to communicate.
  4. Use cookies, local/ Session storage, or other tools that can access state.

Single-spa.js.org/ single-spa.js.org/docs/exampl…

Github.com/systemjs/sy… SystemJS >=3 Polyfill implemented in IE11 is currently up to 6.10.1