In 2022, you must learn to build micro front-end projects and deployment methods

Posted by NerdConcepts on Thu, 06 Jan 2022 07:47:47 +0100

1, Introduction to micro front end

Micro front end is an architecture similar to micro services. It applies the concept of micro services to the browser, that is, the Web application changes from a single single application to an application in which multiple small front-end applications are aggregated into one. Each front-end application can run, develop and deploy independently.

Benefits of micro front end

  • Application autonomy. It only needs to follow the unified interface specification or framework to facilitate the system integration, and there is no dependency between them.
  • Single responsibility. Each front-end application can only focus on the functions it needs to complete.
  • Technology stack independent. You can use both React and Vue while using Angular.

Disadvantages of micro front end

  • The split infrastructure of applications depends on the construction of infrastructure. Once a large number of applications depend on the same infrastructure, maintenance becomes a challenge.
  • The smaller the granularity of splitting, it means that the architecture becomes complex and the maintenance cost becomes higher.
  • Once the technology stack is diversified, it means that the technology stack is chaotic

What modules does the micro front end consist of

At present, the micro front-end mainly adopts the combined application routing scheme. The core of the scheme is the "master-slave" idea, that is, it includes a MainApp application and several micro app applications. Most of the base applications are a front-end SPA project, which is mainly responsible for application registration, routing mapping, message distribution, etc., while the micro application is an independent front-end project, These projects are not limited to the development of React, Vue, Angular or JQuery. Each micro application is registered in the base application and managed by the base. However, if it is separated from the base, it can be accessed separately. The basic process is shown in the figure below

Do you want to use the micro front end

The best use scenarios of the micro front-end are some B-end management systems, which can be compatible with the integrated historical system or integrate new systems without affecting the original interactive experience

2, Actual combat of micro front end

The existing landing schemes of micro front end can be divided into three categories: self-organizing mode, base mode and module loading mode

2.1 SingleSpa actual combat

Official website https://zh-hans.single-spa.js...

Applicable scenario: the project is huge, and multiple sub projects are integrated into a large project. Even if the technology stacks used in the subprojects are different, for example, Vue, react and angular have corresponding single spa wheels, which can be integrated

1. Build sub applications

First, create a vue sub application and export the necessary life cycle through single spa vue

vue create spa-vue  
npm install single-spa-vue
// main.js

import singleSpaVue from 'single-spa-vue';

const appOptions = {
   el: '#vue',
   router,
   render: h => h(App)
}

// Mount applications normally in non child applications
if(!window.singleSpaNavigate){
 delete appOptions.el;
 new Vue(appOptions).$mount('#app');
}

const vueLifeCycle = singleSpaVue({
   Vue,
   appOptions
});


// The sub application must export the following life cycles: bootstrap, mount, unmount
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
export default vueLifeCycle;
// router.js

// Configure sub routing base path
const router = new VueRouter({
  mode: 'history',
  base: '/vue',   //Change path configuration
  routes
})

2. Package sub modules into class libraries

//vue.config.js
module.exports = {
    configureWebpack: {
    // Mount the attribute above the window and the parent application will call window singleVue. bootstrap/mount/unmount
        output: {
            library: 'singleVue',
            libraryTarget: 'umd'
        },
        devServer:{
            port:10000
        }
    }
}

3. Main application setup

<div id="nav">
    <router-link to="/vue">vue project<router-link> 
      
    <!--Mount subapplication to id="vue"In label-->
    <div id="vue">div>
div>
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import {registerApplication,start} from 'single-spa'

Vue.config.productionTip = false

async function loadScript(url) {
  return new Promise((resolve,reject)=>{
    let script = document.createElement('script')
    script.src = url 
    script.onload = resolve
    script.onerror = reject
    document.head.appendChild(script)
  })
}

// Register application
registerApplication('myVueApp',
  async ()=>{
    console.info('load')
    // Single spa problem 
    // You need to build your own script tag to load the file, but you don't know how many files the application has
    // Styles are not isolated
    // There is no js sandbox mechanism for global objects. For example, different applications are loaded, and each application uses the same environment
    // Load public first
    await loadScript('http://localhost:10000/js/chunk-vendors.js')
    await loadScript('http://localhost:10000/js/app.js')

    return window.singleVue // bootstrap mount unmount
  },
  // When the user switches to / vue, we need to load the sub application just defined
  location=>location.pathname.startsWith('/vue'),
)

start()

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

4. Dynamically set the publicPath of the sub application

if(window.singleSpaNavigate){
  __webpack_public_path__ = 'http://localhost:10000/'
}

2.2 qiankun actual combat

file https://qiankun.umijs.org/zh/...

  • qiankun is a micro front-end implementation library based on single spa, which aims to help you build a production usable micro front-end architecture system more simply and painlessly.
  • qiankun incubated the unified access platform of cloud products based on micro front-end architecture of ant financial technology. After a number of online applications have been fully tested and polished, we extracted its micro front-end kernel and opened it to the public. We hope to help the community's systems with similar needs to build their own micro front-end systems more conveniently, At the same time, I also hope to polish qiankun more mature and perfect with the help of the community.
  • At present, qiankun has served more than 200 + online applications in ant. It is absolutely trustworthy in terms of ease of use and completeness.

1. Main application setup

<template>
  <!--Be careful not to write here app Otherwise, it conflicts with the loading of sub applications
  <div id="app">-->
  <div>
    <el-menu :router="true" mode="horizontal">
      <!-- You can put your own route in the base -->
      <el-menu-item index="/">Home</el-menu-item>

      <!-- Reference other subapplications -->
      <el-menu-item index="/vue">vue application</el-menu-item>
      <el-menu-item index="/react">react application</el-menu-item>
    </el-menu>
    <router-view />

    <!-- Mount nodes of other sub applications -->
    <div id="vue" />
    <div id="react" />
  </div>
</template>

2. Register sub applications

import { registerMicroApps,start } from 'qiankun'
// Pedestal writing
const apps = [
  {
    name: 'vueApp', // name
    // The HTML will be loaded by default, and the js in the parsing will be executed dynamically (the sub application must support cross domain)
    entry: '//localhost:10000',  
    container: '#vue ', / / container
    activeRule: '/vue', // Activate path access / Vue to mount the application to #vue
    props: { // Pass attributes to subapplication to receive
      a: 1,
    }
  },
  {
    name: 'reactApp',
    // The HTML will be loaded by default, and the js in the parsing will be executed dynamically (the sub application must support cross domain)
    entry: '//localhost:20000',  
    container: '#react',
    activeRule: '/react' // Access / react to mount the application to #react
  },
]

// register
registerMicroApps(apps)
// open
start({
  prefetch: false // Cancel preload
})

3. Subvue application

// src/router.js

const router = new VueRouter({
  mode: 'history',
  // Keep consistent with those registered in the main application in the base
  base: '/vue',
  routes
})

Don't forget to apply the hook export.

// main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

let instance = null
function render() {
  instance = new Vue({
    router,
    render: h => h(App)
  }).$mount('#app ') / / this is where you mount your HTML. The base will get the mounted HTML and insert it
}

// Running micro applications independently
// https://qiankun.umijs.org/zh/faq#%E5%A6%82%E4%BD%95%E7%8B%AC%E7%AB%8B%E8%BF%90%E8%A1%8C%E5%BE%AE%E5%BA%94%E7%94%A8%EF%BC%9F
if(!window.__POWERED_BY_QIANKUN__) {
  render()
}

// If used by qiankun, the path will be dynamically injected
if(window.__POWERED_BY_QIANKUN__) {
  // qiankun will inject a runtime publicPath variable before the micro application bootstrap. What you need to do is to add the following code at the top of the micro application entry js:
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

// The protocol export of the child application for the parent application to call must export promise
export async function bootstrap(props) {} // You can start without writing the export method
export async function mount(props) {
  render()
}
export async function unmount(props) {
  instance.$destroy()
}

4. Configure Vue config. js

// vue.config.js

module.exports = {
    devServer:{
        port:10000,
        headers:{
            'Access-Control-Allow-Origin':'*' //Allow access across domains
        }
    },
    configureWebpack:{
        // Pack umd bags
        output:{
            library:'vueApp',
            libraryTarget:'umd'
        }
    }
}

5. Sub React application

Use react as a subapplication

// app.js

import logo from './logo.svg';
import './App.css';
import {BrowserRouter,Route,Link} from 'react-router-dom'

function App() {
  return (
    // /react is consistent with the main application configuration
    <BrowserRouter basename="/react">
      <Link to="/">home page</Link>
      <Link to="/about">about</Link>

      <Route path="/" exact render={()=>(
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>
              Edit <code>src/App.js</code> and save to reload.
            </p>
            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
            </a>
          </header>
        </div>
      )} />
      
      <Route path="/about" exact render={()=>(
        <h1>About Page</h1>
      )}></Route>
    </BrowserRouter>
  );
}

export default App;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

function render() {
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root')
  );
}

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

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

// Sub application protocol
export async function bootstrap() {}
export async function mount() {
  render()
}
export async function unmount() {
  ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}

Rewrite the webpack configuration file (config overrides. JS) in react

yarn add react-app-rewired --save-dev

Modify package JSON file

// Change react scripts to react app rewired
"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },

Create a new profile in the root directory

// Profile override
touch config-overrides.js
// config-overrides.js

module.exports = {
  webpack: (config) => {
    // The name is the same as the base configuration
    config.output.library = 'reactApp';
    config.output.libraryTarget = "umd";
    config.output.publicPath = 'http://localhost:20000/'
    return config
  },
  devServer: function (configFunction) {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost);

      // Configure cross domain
      config.headers = {
        "Access-Control-Allow-Origin": "*",
      };
      return config;
    };
  },
};

to configure. env file

New root directory env

PORT=30000
# socket send port
WDS_SOCKET_PORT=30000

Routing configuration

import { BrowserRouter, Route, Link } from "react-router-dom"

const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/react" : "";

function App() {
  return (
    <BrowserRouter basename={BASE_NAME}><Link to="/">home page Link><Link to="/about">about Link><Route path="/" exact render={() => <h1>hello homeh1>}>Route><Route path="/about" render={() => <h1>hello abouth1>}>Route>BrowserRouter>
  );
}

2.3 actual combat of flying ice micro front end

Official access guide https://micro-frontends.ice.w...

  • ICESTAR is a micro front-end solution for large-scale systems, which is applicable to the following business scenarios:
  • The background is scattered and the experience varies greatly, because frequent jumps lead to low operation efficiency. I hope it can be unified in a system
  • Single page applications are very large, multi person collaboration costs are high, development / construction time is long, and the cost of relying on upgrading and regression is high
  • The system requires two-party / three-party access

On the basis of ensuring the operating experience of a system, ICESTAR realizes the independent development and release of each micro application. The main application manages the registration and rendering of micro applications through ICESTAR, which completely decouples the whole system.

1. Write react main application

$ npm init ice icestark-layout @icedesign/stark-layout-scaffold
$ cd icestark-layout
$ npm install
$ npm start
// src/app. Add in JSX

const appConfig: IAppConfig = {

  ...
  
  icestark: {
    type: 'framework',
    Layout: FrameworkLayout,
    getApps: async () => {
      const apps = [
      {
        path: '/vue',
        title: 'vue Micro application test',
        sandbox: false,
        url: [
          // testing environment
          // Request the service under the sub application port and the Vue of the sub application config. JS needs to configure headers cross domain request headers
          "http://localhost:3001/js/chunk-vendors.js",
          "http://localhost:3001/js/app.js",
        ],
      },
      {
        path: '/react',
        title: 'react Micro application test',
        sandbox: true,
        url: [
          // testing environment
          // Request the service under the sub application port, and the webpackdevserver of the sub application config. JS needs to configure headers cross domain request headers
          "http://localhost:3000/static/js/bundle.js",
        ],
      }
    ];
      return apps;
    },
    appRouter: {
      LoadingComponent: PageLoading,
    },
  },
};
// Sidebar menu
// src/layouts/menuConfig.ts transformation

const asideMenuConfig = [
  {
    name: 'vue Micro application test',
    icon: 'set',
    path: '/vue' 
  },
  {
    name: 'React Micro application test',
    icon: 'set',
    path: '/react'
  },
]

2. vue sub application access

# Create a sub application
vue create vue-child
// Modify Vue config. js

module.exports = {
  devServer: {
    open: true, // Set the browser to automatically open the project
    port: 3001, // Set port
    // Support cross domain to facilitate the main application to request sub application resources
    headers: {
      'Access-Control-Allow-Origin' : '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
      'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
    }
  },
  configureWebpack: {
    // Package into lib package umd format
    output: {
      library: 'icestark-vue',
      libraryTarget: 'umd',
    },
  }
}

src/main.js transformation

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import {
  isInIcestark,
  getMountNode,
  registerAppEnter,
  registerAppLeave,
  setLibraryName
} from '@ice/stark-app'

let vue = createApp(App)
vue.use(store)
vue.use(router)

// Note: the input parameter of 'setLibraryName' needs to be consistent with the output configured by the webpack project Library consistency
//  Important without effect and Vue config. JS
setLibraryName('icestark-vue')

export function mount({ container }) {
  // ![](http://img-repo.poetries.top/images/20210731130030.png)
  console.log(container,'container')
  vue.mount(container);
}

export function unmount() {
  vue.unmount();
}
  
if (!isInIcestark()) {
  vue.mount('#app')
}

router transformation

import { getBasename } from '@ice/stark-app';


const router = createRouter({
  // Important benchmark routing in main application
  base: getBasename(),
  routes
})

export default router

3. react sub application access

create-react-app react-child
// src/app.js

import { isInIcestark, getMountNode, registerAppEnter, registerAppLeave } from '@ice/stark-app';

export function mount(props) {
  ReactDOM.render(<App />, props.container);
}

export function unmount(props) {
  ReactDOM.unmountComponentAtNode(props.container);
}

if (!isInIcestark()) {
  ReactDOM.render(<App />, document.getElementById('root'));
}

if (isInIcestark()) {
  registerAppEnter(() => {
    ReactDOM.render(<App />, getMountNode());
  })
  registerAppLeave(() => {
    ReactDOM.unmountComponentAtNode(getMountNode());
  })
} else {
  ReactDOM.render(<App />, document.getElementById('root'));
}

After npm run eject, modify config / webpackdevserver config. js

hot: '',
port: '',
...

// Support cross domain
headers: {
  'Access-Control-Allow-Origin' : '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
  'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
},

Micro front end deployment

Summary of micro front end deployment practice

More dry cargo sharing in the official account of "front-end journey"