Chapter 3: Single-SPA micro-front-end framework

zh-hans.single-spa.js.org/

Single-spa: https://single-spa.js.org/ is a framework for implementing a micro-front-end architecture.

There are three types of microfront-end applications in the single-SPA framework:

  1. Single-spa-application/Parcel: a micro application in a micro front-end architecture, using vue, React, Angular, etc.
  2. Single-spa root config: Create a micro front-end container application.
  3. Utility Modules: Public module applications, non-rendering components used for micro applications that share javascript logic across applications.

3-1 Creating a container application

Install single-SPA scaffolding tools: NPM install [email protected] -g

Create a micro front-end container application: create-single-SPA

  1. Application folder Fill in Container

  2. Select single-SPA root Config for application

  3. Enter study for organization name

    The organization name can be understood as the team name. The micro front-end architecture allows multiple teams to develop applications together, and the organization name identifies which team is developing the application.

    Application names are named @ organization name/application name, such as @study/todos

4. Start the application: CD./singletest && NPM start

5. Access the application: localhost:9000

3-2 Container default code parsing

src/xx-root-config.js

// Import two methods from the framework, called below
import { registerApplication, start } from "single-spa";

Name: a string of characters. The micro front-end application name is "@organization name/application name". App: ActiveWhen: Activates the application */ when the route matches
registerApplication({
  name: "@single-spa/welcome".app: () = >
    System.import(
      "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
    ),
  activeWhen: ["/"]});Copy the code
// Examples of microapplications introduced in the current organization
// registerApplication({
// name: "@xl/navbar",
// app: () => System.import("@xl/navbar"),
// activeWhen: ["/"]
// });


// The start method must be called in the configuration file of single SPA
// Before calling start, the application is loaded, but not initialized, mounted, or unloaded.
start({
  // Whether single-SPA routing can be triggered with history.pushState() and history.replacestate () changes
  // true disallow false allow
  urlRerouteOnly: true});Copy the code

index.ejs

<body>
  <main></main>
  <! -- Import the micro front-end container application -->
  <script>
    System.import('@xl/root-config');
  </script>
  <! -- import-map-overrides Can override the import mapping used in the current project with the single-SPA Inspector debugging tool. You can manually override the JavaScript module loading address in your project for debugging.
  <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>
Copy the code
  <! Support Angular apps -->
  <! -- < script SRC = "https://cdn.jsdelivr.net/npm/[email protected]/dist/zone.min.js" > < / script > -- >
  <! -- Override JavaScript module download address set by import-map -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/import-map-overrides.js"></script>

  <! -- Check if it is local -->
  <% if (isLocal) { %>
  <! -- Module loader -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.js"></script>
  <! -- SystemJS plugin for parsing AMD modules -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/amd.js"></script>
  <% } else { %>
  <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>The < %} % >Copy the code
  <script type="systemjs-importmap">
    {
      "imports": {
        "single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js"}}</script>
  <! -- Single-SPA preloading -->
  <link rel="preload" href="https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js" as="script">

  <! -- Add your organization's prod import map URL to this script's src -->
  <! -- <script type="systemjs-importmap" src="/importmap.json"></script> -->
<! Public modules in the micro front-end project can be placed here -->
  <% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@xl/root-config": "//localhost:9000/xl-root-config.js"}}</script>The < %} % >Copy the code

3-3 Creating a React microapplication

Create the React micro application

Create the app: create-single-spa, note the organization and project name, which will be used to register the micro-app later

  1. Apply directory type todos
  2. I’m gonna go to React

Modify the application port && to start the application

{
  "scripts": {
     "start": "webpack serve --port 9002",}}Copy the code

Start the application: NPM start

3-3-2 Registration applications

Register the React project entry file with the base application (container application)

\ container \ SRC \ study – root – config. Js:

// React -- todos 
registerApplication({
  name: "@study/todos".app: () = > System.import("@study/todos"),
  activeWhen: ["/todos"]});Copy the code

Specify the reference address of the micro front-end application module:

(Can directly access the corresponding application server, there is a prompt URL loading address)

<% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@study/root-config": "//localhost:9000/study-root-config.js"."@study/todos": "//localhost:9002/study-todos.js"}}</script>The < %} % >Copy the code

By default, react and react-dom are not packaged by Webpack. Single-spa considers them public libraries and should not be packaged separately.

<script type="systemjs-importmap">
    {
      "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"}}</script>
Copy the code

Modify the default application code to display application content on an independent page

container\src\study-root-config.js

// registerApplication({
// name: "@single-spa/welcome",
// app: () =>
// System.import(
// "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
/ /),
// activeWhen: ["/"],
// });

// Change the default application registration mode to display application content on a separate page
registerApplication(
  "@single-spa/welcome".() = > System.import(
    "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
  ),
  location= >location.pathname === '/'
);

Copy the code

3-3-3 specifies the application render location

micro\container\src\index.ejs

<body>
  <main></main>

  <h2>
    <! -- Specify application display location -->
    <div id="myreact"></div>
  </h2>

  <script>
    System.import('@study/root-config');
  </script>
  <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>
Copy the code

micro\todos\src\study-todos.js

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  // Render the root component
  rootComponent: Root,
  // Error boundary function
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    return null;
  },
  // Specify the render location of the root component
  domElementGetter:() = >document.getElementById('myreact')});Copy the code

3-3-4 React applies code parsing

micro\todos\src\study-todos.js

import React from "react";
import ReactDOM from "react-dom";
Single-spa-react is used to create a micro-front-end application implemented using the React framework
import singleSpaReact from "single-spa-react";
// The root component used to render the page is the app.js file of the traditional React App
import Root from "./root.component";


const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  // Render the root component
  rootComponent: Root,
  // Error boundary function
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    // return null;
    return () = > <div>This content will be rendered if an error occurs</div>
  },
  // Specify the render location of the root component
  domElementGetter:() = >document.getElementById('myreact')});// Expose the necessary lifecycle functions
export const { bootstrap, mount, unmount } = lifecycles;

Copy the code

3-3-5 React micro – front-end routing configuration

Prepare two routing components

micro\todos\src\home.js && micro\todos\src\about.js

import React, { Component } from 'react'

export default class home extends Component {
    render() {
        return (
            <div>
                <h2>What is happy Planet</h2>
            </div>)}} ========= I am a beautiful and colorful dividing line between two components =============import React from 'react'

function about() {
    return (
        <div>
            <h2>Happy Planet is learning about the micro front end</h2>
        </div>)}export default about

Copy the code

micro\todos\src\root.component.js

import React from "react";
// Import routing-related components
import {BrowserRouter, Switch, Route, Redirect, Link} from "react-router-dom"
// Import components
import Home from './home'
import About from './about'


export default function Root(props) {
  // return 
      
{props.name} is mounted! &&pull big front
;
return ( // Design the base routing path using routing components <BrowserRouter basename="/todos"> <div>{props.name}</div>{/* Set click link, jump route */}<div> <Link to="/home">Home</Link> | <Link to="/about">About</Link> </div>{/* Route display */}<Switch> <Route path="/home"> <Home /> </Route> <Route path="/about"> <About></About> </Route> <Route path="/">{/* Redirection */}<Redirect to="/home"></Redirect> </Route> </Switch> </BrowserRouter>)}Copy the code

The routing file has been introduced in the public module, \ Micro \container\ SRC \index.ejs

<script type="systemjs-importmap">
    {
      "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"."react-router-dom": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react-router-dom.min.js"}}</script>
Copy the code

Modify the webpack configuration file to exclude routing module packaging, micro\todos\webpack.config.js

return merge(defaultConfig, {
    // modify the webpack config however you'd like to by adding to this object
    externals: ["react-router-dom"]});Copy the code

3-4 Create vUe-based microapplications

3-4-1 Creating an application

Create an app: create-single-SPA

  1. Fill in the project folder realWorld
  2. Select Vue for the framework
  3. Generate the Vue 2 project

Vue && Vue-Router needs to be packaged by a public module, so it needs to be configured not to be packaged within the application

micro\realworld\vue.config.js

module.exports = {
    chainWebpack:config= >{
        // Configure not to package Vue and vue-router
        config.externals(["vue"."vue-router"])}}Copy the code

Modify the project startup command: micro\ realWorld \package.json

"scripts": {
    "serve": "vue-cli-service serve"."start": "vue-cli-service serve --port 9003"."build": "vue-cli-service build"."lint": "vue-cli-service lint"."serve:standalone": "vue-cli-service serve --mode standalone"
  },
Copy the code

Register application: micro\container\ SRC \study-root-config.js

// Vue -- todos
registerApplication({
  name: "@study/realworld".app: () = > System.import("@study/realworld"),
  activeWhen: ["/realworld"]});Copy the code

micro\container\src\index.ejs

Load vue && Vue-router

  <script type="systemjs-importmap">
    {
      "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"."react-router-dom": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react-router-dom.min.js"."vue": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"."vue-router": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js"}}</script>
Copy the code

Import applications. The application address can be directly accessed and obtained from a browser prompt.

<% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@study/root-config": "//localhost:9000/study-root-config.js"."@study/todos": "//localhost:9002/study-todos.js"."@study/realworld": "//localhost:9003/js/app.js"}}</script>The < %} % >Copy the code

3-4-2 applies the routing configuration

\micro\realworld\src\main.js

import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
import App from './App.vue';

import VueRouter from 'vue-router'
Vue.use(VueRouter)

// Routing component
const Foo = { template: "<div>Foooooo</div>" }
const Bar = { template: "<div>Barrrrr</div>" }

// Routing rules
const routes = [
  { path: '/foo'.component: Foo },
  { path: '/bar'.component: Bar },
]

// Route instance
const router = new VueRouter({ routes, mode: "history".base: "/realworld" })


Vue.config.productionTip = false;

const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: {
    // Register the route
    router,
    render(h) {
      return h(App, {
        props: {
          // Pass parameters to the component}}); ,}}});export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;

Copy the code

micro\realworld\src\App.vue

<template>
  <div id="app">
    <div>
      <router-link to="/foo">Goto Foo</router-link> |
      <router-link to="/bar">Goto Bar</router-link>
    </div>
    <div>
      <router-view></router-view>
    </div>
  </div>
</template>
Copy the code

3-5 Create Utility Modules

3-5-1 Utility independent application created

Used to place JavaScript logic shared across applications, it is also a standalone application that needs to be built and launched separately.

  1. Create an app: create-single-SPA

    1. Folder Fill in tools
    2. Application of choicein-browser utility module (styleguide, api cache, etc)
  2. Modify port, start application, \micro\tools\package.json

    "scripts": {
       "start": "webpack serve --port 9005",}Copy the code

Export public method: micro\tools\ SRC \study-tools.js

export function happyStar(who){
    console.log(`${who} hahahhahahhah`)
    return 'The Source of Happiness of Happy Star'
}
Copy the code

Declare the application module access address in the template file: micro\container\ SRC \index.ejs

 <% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@study/root-config": "//localhost:9000/study-root-config.js"."@study/todos": "//localhost:9002/study-todos.js"."@study/realworld": "//localhost:9003/js/app.js"."@study/tools": "//localhost:9005/study-tools.js"}}</script>The < %} % >Copy the code

3-5-2 uses this method in React applications

MicroFrontends\micro\todos\src\about.js

import React, { useEffect, useState } from "react";

// Custom hook functions
function useToolsModule() {
  const [toolsModule, setToolsModule] = useState();
  useEffect(() = > {
    // Import, asynchronous promise returns
    System.import("@study/tools").then(setToolsModule); } []);return toolsModule;
}

function about() {
  var back = "";
  // Call the hook function
  const toolsModule = useToolsModule();
  if (toolsModule) {
    // Call a method of shared logic
    back = toolsModule.happyStar("React todo");
  }

  return (
    <div>
      <h2>Happy Planet is learning the microfront --{back}</h2>
    </div>
  );
}

export default about;

Copy the code

3-5-3 The method is used in Vue applications

micro\realworld\src\main.js

// Routing component
// const Foo = {template: "
      
"};
import Foo from './components/Foo' const Bar = { template: "
or happy planet lol
"
}; Copy the code

micro\realworld\src\components\Foo.vue

</button> </div> </template> <script> export {{MSG}}</h2> </button @click="getHappy"  default { data() { return { msg: "", }; }, methods: { async getHappy() { let toolsModule = await window.System.import("@study/tools"); this.msg = toolsModule.happyStar("Vue"); ,}}}; </script> <style> </style>Copy the code