1 background

The concept of ‘microfront-end’ first appeared on ThoughtWork in late 2016, extending the concept of ‘microservices’ to the front-end world. Here are two analogies to “microservices” and “microfronts”.

Figure 1: From one overall team → two front-end and back-end teams → one front-end team corresponding to multiple microservices

Figure 2: Shows a vertical organizational structure, with teams responsible for individual parts with the help of a “micro front end.

What is the micro front end?

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. — Micro Frontends

The micro front end is a kind of technical means and method strategy that multiple teams jointly build modern Web applications by releasing functions independently.

Why use a micro front end?

As front-end projects become larger, more complex, and more technical frameworks become incompatible with each other, it is a huge challenge for development and maintenance. Microfront-end technology can solve these problems. The micro front end has the following advantages:

  • Stack independent

The main framework does not limit the technology stack of access applications, and microapplications have full autonomy

  • Independent development and deployment

The microapplication repository is independent, and the front and back ends can be independently developed. After deployment, the main framework can automatically complete synchronous update

  • The incremental upgrade

In the face of a variety of complex scenarios, it is usually difficult to upgrade or reconstruct the existing system completely, and the micro front end is a very good means and strategy to implement the gradual reconstruction

  • Independent run time

State is isolated between each microapplication and run time state is not shared

4 Micro front-end and micro services

  • Microservices are back-end services that run on their own operating systems, manage their own databases, and communicate with each other over the network. The “microfront-end” refers to the microservices that exist in the browser and communicate with each other in memory rather than over the network.
  • They can all be built and deployed independently. Think of the DOM as a shared resource used by the micro front end. The DOM of one microfront should not be touched by other microfronts, and a database similar to a microservice should not be touched by other microservices without permission.
  • Each microfront has its own git repository, package.json, and build tool configuration, and each microfront can be managed by a different team with a choice of frameworks.

What are the microfront-end solutions on the market?

Currently, the most influential programmes are:

  • The granddaddy of single-SPA micro front end solutions, enabling route hijacking and application loading
  • The Qiankun is based on a single-SPA package that provides a more out-of-the-box API
  • Isomorphic Layout Composer – a complete solution that will support THE micro front end components of SSR

6 micro front-end single- SPA main components

6.1 Single-SPA project type

  1. Single-spa Applications: A micro front end for rendering components for a specific set of routes.
  2. Single-spa n-SING: the micro-front-end for rendering components that is not routable and is not recommended.
  3. Utility Modules: Non-rendering components that expose a micro front end to shared javascript logic.

6.2 Single-SPA project structure

  • Root-config Root configuration, which can be understood as the master application, contains the root HTML page shared by each application and registration information of each application, for example, 6.3.1.
  • App-parcel is an independent application that exposes its life cycle and facilitates unified management of active applications, such as 6.3.2 and 6.3.3
  • Util-module public module that exports imported functionality for other micro-front-end applications. Common examples include style guides, authentication assistants, and API assistants. These modules do not need to be registered with single-SPA, but are important for maintaining consistency between several single-SPA applications and parcels.

6.3 How to use single-SPA?

6.3.1 Main application root-config

  • Gm’s HTML
<! 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>Root Config</title>
  <! -- Remove this if you only support browsers that support async/await. This is needed by babel to share largeish helper code for compiling async/await in older browsers. More information at https://github.com/single-spa/create-single-spa/issues/112 -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/runtime.min.js"></script>
  <! -- This CSP allows any SSL-enabled host and for arbitrary eval(), but you should limit these directives further to increase your app's security. Learn more about CSP policies at https://content-security-policy.com/#directive -->
  <meta http-equiv="Content-Security-Policy" content="default-src 'self' https: localhost:*; script-src 'unsafe-inline' 'unsafe-eval' https: localhost:*; connect-src https: *:* ws://*:*; style-src 'unsafe-inline' https:; object-src 'none';">
  <meta name="importmap-type" content="systemjs-importmap" />
  <! -- If you wish to turn off import-map-overrides for specific environments (prod), uncomment the line below -->
  <! -- More info at https://github.com/joeldenning/import-map-overrides/blob/master/docs/configuration.md#domain-list -->
  <! -- <meta name="import-map-overrides-domains" content="denylist:prod.example.com" /> -->

  <! -- Shared dependencies go into this import map. Your shared dependencies must be of one of the following formats: 1. System.register (preferred when possible) - https://github.com/systemjs/systemjs/blob/master/docs/system-register.md 2. UMD - https://github.com/umdjs/umd 3. Global variable More information about shared dependencies can be found at https://single-spa.js.org/docs/recommended-setup#sharing-with-import-maps. -->
  <script type="systemjs-importmap">
    {
      "imports": {
        "single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js"}}</script>
  <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> -->

  <% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "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"."@demo/root-config": "//localhost:9000/demo-root-config.js"."@demo/app-react": "//localhost:8081/demo-app-react.js"."@demo/app-vue": "//localhost:8082/js/app.js"}}</script>The < %} % ><! -- If you need to support Angular applications, uncomment the script tag below to ensure only one instance of ZoneJS is loaded Learn more about why at https://single-spa.js.org/docs/ecosystem-angular/#zonejs -->
  <! -- < script SRC = "https://cdn.jsdelivr.net/npm/[email protected]/dist/zone.min.js" > < / script > -- >

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/import-map-overrides.js"></script>
  <% if (isLocal) { %>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.js"></script>
  <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 < %} % ></head>
<body>
  <noscript>
    You need to enable JavaScript to run this app.
  </noscript>
  <main>
    <a href="/react">The React child application</a>&nbsp;<a href="/vue">Vue child application</a>
  </main>
  <script>
    System.import('@demo/root-config');
  </script>
  <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>
</html>
Copy the code
  • Application of registration
// single-spa-config.js
import { registerApplication, start } from 'single-spa';
// Simple usage
registerApplication(
  'app2'.() = > import('src/app2/main.js'),
  (location) = > location.pathname.startsWith('/app2'),
  { some: 'value'});// Config with more expressive API
registerApplication({
  name: 'app1'.app: () = > import('src/app1/main.js'),
  activeWhen: '/app1'.customProps: {
    some: 'value'}); start();Copy the code

6.3.2 React Sub-application

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) {
    // Customize the root error boundary for your microfrontend here.
    return null; }});export const { bootstrap, mount, unmount } = lifecycles;
Copy the code

6.3.3 Vue sub-applications

import { h, createApp } from 'vue';
import singleSpaVue from 'single-spa-vue';

import App from './App.vue';

const vueLifecycles = singleSpaVue({
  createApp,
  appOptions: {
    render() {
      returnh(App, { }); ,}}});export const { bootstrap, mount, unmount } = vueLifecycles;
Copy the code

6.3.4 Transferring Data from a Parent Application to a child Application

registerApplication({
  name: "@demo/app-react".app: () = > System.import("@demo/app-react"),
  activeWhen: ["/react"].customProps: { authToken: "d83jD63UdZ6RS6f70D0" }  // Pass the authToken to the child application
});
registerApplication({
  name: "@demo/app-vue".app: () = > System.import("@demo/app-vue"),
  activeWhen: ["/vue"].customProps: { authToken: "d83jD63UdZ6RS6f70D0" } // Pass the authToken to the child application
});
Copy the code
// React receives the parent application authToken. The props of the root component can be obtained directly
// root.component.js
export default function Root(props) {
  return <section>{props.name} is mounted! authToken:{props.authToken}</section>;
}

Vue receives the parent application's authToken, which needs to be passed in the root component
const vueLifecycles = singleSpaVue({
  createApp,
  appOptions: {
    render() {
      return h(App, {
        authToken: this.authToken }); ,}}});Copy the code

6.3.5 Utility Modules Common modules

  • Generic module instance code
export function authenticatedFetch(url, init) {
  return fetch(url, init).then(r= > {
    // Maybe do some auth stuff here
    return r.json()
  })
}
Copy the code
  • Example code referenced in the single-SPA application
import React from 'react'
import { authenticatedFetch } from '@org-name/api';
export function Foo(props) {
  React.useEffect(() = > {
    const abortController = new AbortController()
    authenticatedFetch(`/api/clients/${props.clientId}`, {signal: abortController.signal})
    .then(client= > {
      console.log(client)
    })
    return () = > {
      abortController.abort()
    }
  }, [props.clientId])
  return null
}
Copy the code

7. Project Composition of Qiankun

Qiankun is a single-SPa-based microfront-end implementation library that aims to make it easier and painless to build a production-ready microfront-end architecture system

7.1 Types of Qiankun project

Compared to single-SPA, Qiankun is much simpler and less complex. There are only two concepts to understand:

  • Master application, responsible for micro application registration, route takeover
  • Bootstrap, mount and unmount life cycle hooks are added to the entry file of microapplication, and exported as umD module

7.2 How is Qiankun used

7.2.1 Registering microapplications with the main Application

import { registerMicroApps, start } from 'qiankun'; registerMicroApps([ { name: 'reactApp', entry: '//localhost:3000', container: '#container', activeRule: '/app-react', }, { name: 'vueApp', entry: '//localhost:8080', container: '#container', activeRule: '/app-vue', }, { name: 'angularApp', entry: '//localhost:4200', container: '#container', activeRule: '/app-angular', }, ]); // Start qiankun start();Copy the code

7.2.2 React Micro Application

  1. Add public-path.js to SRC directory:
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Copy the code
  1. Set the base of the route in history mode:
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'} >Copy the code
  1. Modify the entry file index.js to export the life cycle
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

function render(props) {
  const { container } = props;
  ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}

export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}

export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}

export async function unmount(props) {
  const { container } = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
Copy the code
  1. Modify the WebPack configuration
  • Resolve cross-domain problems
// webpack-dev-server
headers: {
            'Access-Control-Allow-Origin': The '*'
        }
Copy the code
  • Set the export to the UMD module
output: {
        path: projectRoot,
        filename: 'js/[name].js'.chunkFilename: 'chunk/[name].chunk.js'.publicPath: '/',
+       library: `${name}-[name]`,
+       libraryTarget: 'umd',
+       jsonpFunction: `webpackJsonp_${name}`,},Copy the code

7.2.3 Vue microapplication

  1. Create public-path with React
  2. Example Modify the import file export life cycle
import './public-path';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';

Vue.config.productionTip = false;

let router = null;
let instance = null;
function render(props = {}) {
  const { container } = props;
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/'.mode: 'history',
    routes,
  });

  instance = new Vue({
    router,
    store,
    render: (h) = > h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// Independent runtime
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = ' ';
  instance = null;
  router = null;
}
Copy the code
  1. Package configuration changes (vue.config.js) :
const { name } = require('./package');
module.exports = {
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': The '*',}},configureWebpack: {
    output: {
      library: `${name}-[name]`.libraryTarget: 'umd'.// Package microapplications into umD library format
      jsonpFunction: `webpackJsonp_${name}`,}}};Copy the code

8 Project Deployment

Recommendation: Master and microapplications are developed and deployed independently, meaning they belong to different repositories and services. Each application (aka microservices, aka ES modules) can be developed and deployed independently. Teams can work at their own pace, developing, testing, and deploying independently

Read more:

  • A Step-by-step Guide to Single-spa
  • Core Values of the Micro Front End
  • A 10 Minute Primer to JavaScript Modules, Module Formats, Module loaders and Module Bundlers
  • Javascript Import Maps