preface

Recently, I saw some friends in the COMPANY’s UED group talking about micro front-end Qiankun, and I was also curious to get into the pit.

What is a micro front end?

The micro front end is a technical approach and method strategy for multiple teams to jointly build modern Web applications by releasing functions independently, as shown in the figure below.

qiankun

Ant Financial’s single-SPa-based micro front end solution is available for production.

features
  • Based on the single-SPA package, provides a more out-of-the-box API.
  • Technology stack independent, any technology stack applications can use/access, be it React/Vue/Angular/JQuery or other frameworks.
  • HTML Entry access allows you to access micro applications as easily as using iframe.
  • Style isolation ensures that styles do not interfere with each other between microapplications.
  • JS sandbox to ensure no global variable/event conflicts between microapplications.
  • Resource preloading: Preloads unopened micro-application resources in idle time of the browser to speed up the opening of micro-applications.

Main application construction

Choose to initialize the main application with vue-CLI.

$YARN add Qiankun # or NPM I Qiankun -SCopy the code
Sign up for microapps

Define the microapplications that need to be loaded

// src/micro/apps.ts
// We have no microapplications at this point, so it is null for now
const apps: any = [
    
];

export default apps;
Copy the code

Register microapplications and expose methods

// src/micro/index.ts
import {
    registerMicroApps,
    addGlobalUncaughtErrorHandler,
    start,
} from "qiankun";
import NProgress from "nprogress";
import { Message } from 'element-ui';
import 'nprogress/nprogress.css';

NProgress.configure({ parent: '.scrollbar.scroll' });

export default function (apps: []) {
    registerMicroApps(apps, {
        beforeLoad: (a)= > {
            // Load the progress bar before loading the microapplication
            NProgress.start();
            return Promise.resolve();
        },
        afterMount: (a)= > {
            NProgress.done();
            return Promise.resolve(); }}); addGlobalUncaughtErrorHandler((event: any) = > {
        const { msg } = event as any;
        NProgress.done();
        // Load failed
        if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
            Message.error('Micro application failed to load, please check whether the application is running'); }}); start(); }Copy the code

Our microapplication may be generated according to the user’s left menu permission after login. Therefore, exposed methods and input parameters facilitate login to complete the call to register the microapplication.

Launch microapps
import startQiankun from "@/micro"; startQiankun(...) ;// Just call the incoming data where it needs to be started
Copy the code

Here I start at the global routing guard for your reference.


//router
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
import store from "@/store";
import { getToken } from "@/utils/auth";
import startQiankun from "@/micro";
import apps from "@/micro/apps";

Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
  {
    path: '/login'.name: 'login'.component: (a)= > import('@/views/login/index.vue')}, {path: '/'.name: 'main'.component: (a)= > import('@/views/Layout/index.vue'),
    children: [{path: ' '.name: 'Home'.component: (a)= > import('@/views/Home.vue'}]}, {path: The '*'.name: 'redirect'.redirect: '/'}];const createRouter: any = (a)= > new VueRouter({
  mode: "history",
  routes,
});

const router: any = createRouter()

/** * resets the route */
export function restRouter() {
  router.matcher = createRouter().matcher;
}

const whiteList = ['login'];
router.beforeEach((to: any, from: any, next: any) = > {
  const token = getToken('token');
  if (token) {   / / token
    if (to.name === 'login') {     // If login directly jumps to the home page
      return next({ path: '/' });
    }
    if(! store.state.hasInited) {// Prevent repeated addRoutes from preset values
      store.dispatch('addRouters').then((res) = > {
        router.addRoutes(res);
        startQiankun(apps);
        store.state.hasInited = true; next({ ... to,replace: true });
      })
      return;
    }
    next();
  } else if (whiteList.includes(to.name)) {   // Whitelist is allowed directly
    next();
  } else {     / / token does not exist
    next({ path: '/login'.query: { redirect: to.path } }); }});export default router;

Copy the code

Vue sub-application construction

Configure the sub-applications to be added to the primary application

// micro/apps.ts
import app from "./shared";  // Data to share with child applications

/* * name: micro app name - Unique * entry: micro app entry - Load the micro app through this address * container: mount the micro app to the node - After the micro app is loaded, it will be mounted to the node * activeRule: Routing rules triggered by the micro-application - After the routing rule is triggered, the micro-application * props: data shared to the micro-application */
const apps: any = [
    {
        name: "vue-project".entry: "//localhost:10300".container: "#app-qiankun".activeRule: "/vue".props: { app }
    }
];

export default apps;

Copy the code
Configuring subapplications

After the master application has configured the registered micro-application, we need to configure the sub-application so that the sub-application can access the master application. 1, vUE sub-application entry main.js configuration

// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
    // Dynamically set webpack publicPath to prevent resource loading errors
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// main.js
import Vue from 'vue';
import App from './App.vue';
import VueRouter from "vue-router";
import './registerServiceWorker';
import routes from './router';
import store from './store';
import './public-path'

Vue.use(VueRouter)
Vue.config.productionTip = false;

let instance = null;
let router = null;


function render() {
  router = new VueRouter({
    // In the main application, the base routing address is set to /vue
    base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/".mode: "history",
    routes,
  });

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

/** * Can be run separately when there is no main application */
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {}/** * The application calls the mount method every time it enters, and usually we trigger the application's render method here */
export async function mount(props) {
  console.log(props);
  render(props);
}

/** * The method that is called each time the application is cut/unloaded, usually here we unload the application instance of the microapplication */
export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}

Copy the code

2. Configure the WebPack packaging policy


// vue.config.js
const path = require("path");

module.exports = {
    // Set the static file host path
    publicPath: 'http://localhost:10300'.devServer: {
        // Listen on the port
        port: 10300.overlay: {
            warnings: false.errors: false
        },
        // Turn off host checking so that microapplications can be fetched
        disableHostCheck: true.// Configure cross-domain request headers to solve cross-domain problems in the development environment
        headers: {
            "Access-Control-Allow-Origin": "*",}},configureWebpack: {
        resolve: {
            alias: {
                "@": path.resolve(__dirname, "src"),}},output: {
            // The package name of the micro-application, which is the same as the registered micro-application name in the main application
            library: "vue-project".// Expose your library in a way that all module definitions work
            libraryTarget: "umd".// Load as needed, set to webPackJsonp_vue-projec
            jsonpFunction: `webpackJsonp_vue-project`,}}};Copy the code

At this point, our micro front end is fully configured, but only one child application is currently connected. According to the code above can be summarized and several key points: 1, the main application of registration application: registerMicroApps addGlobalUncaughtErrorHandler startqiankun three important API collocation in use. 2. The child entry is left to the main application to call the bootstrap mount unmount declaration. And window.__powered_by_qiankun__. 3. Reconfigure the packaging policy for subapplications. Effects of launching the main app and child app:

/vue
/vue/about
/about

Build the React sub-application

Add sub-applications to the primary application

// micro/apps.ts
import app from "./shared";  // Data to share with child applications

/* * name: micro app name - Unique * entry: micro app entry - Load the micro app through this address * container: mount the micro app to the node - After the micro app is loaded, it will be mounted to the node * activeRule: Routing rules triggered by the micro-application - After the routing rule is triggered, the micro-application * props: data shared to the micro-application */
const apps: any = [
    {
        name: "vue-project".entry: "//localhost:10300".container: "#app-qiankun".activeRule: "/vue".props: { app }
    },
    {
        name: "react-project".entry: "//localhost:10100".container: "#app-qiankun".activeRule: "/react".props: { app }
    }
];

export default apps;

Copy the code
Sub-application Configuration

Create a. Env file in the root directory and add the following configuration

PORT=10100
BROWSER=none
Copy the code

1. Configure the React interface

// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
    // Dynamically set webpack publicPath to prevent resource loading errors
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import "./public-path";

let root = document.getElementById("root");

function render() {
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>, root ); } if (! window.__POWERED_BY_QIANKUN__) { render(); } export async function bootstrap() { console.log("ReactMicroApp bootstraped"); } /** * the application calls the mount method every time it enters, */ export async function mount(props) {console.log("ReactMicroApp mount", props); root = document.getElementById("root"); render(props); } /** * export async function unmount() {console.log("ReactMicroApp unmount"); //console.log(ReactDOM); ReactDOM.unmountComponentAtNode(root); root = null; } // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();Copy the code

React-app-rewired is used to modify the webpack configuration.

//config-overrides.js
const path = require("path");

module.exports = {
  webpack: (config) = > {
    // The package name of the micro-application, which is the same as the registered micro-application name in the main application
    config.output.library = `react-project`;
    // Expose your library in a way that all module definitions work
    config.output.libraryTarget = "umd";
    // Load related files as needed, set it to webPackJsonp_react-project
    config.output.jsonpFunction = `webpackJsonp_react-project`; config.resolve.alias = { ... config.resolve.alias,"@": path.resolve(__dirname, "src"),};return config;
  },

  devServer: function (configFunction) {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost);
      // Turn off host checking so that microapplications can be fetched
      config.disableHostCheck = true;
      // Configure cross-domain request headers to solve cross-domain problems in the development environment
      config.headers = {
        "Access-Control-Allow-Origin": "*"};// Configure the history mode
      config.historyApiFallback = true;

      returnconfig; }; }};Copy the code

The React subapplication is now ready to use

Access to apps that do not use WebPack, Angular, etc., will not be mentioned, if you are interested, you can find information.

Nginx deploys packaged files

Nginx installation and use will not be introduced, not familiar with the search engine.

Nginx configuration
#user nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    #tcp_nopush on;

    #keepalive_timeout 0;
    keepalive_timeout  65;

    #gzip on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log logs/host.access.log main;

        location / {
            root   html/dist;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            error_page 404 /index.html;
        }

        #error_page 404 /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       10300;
        server_name  localhost;

        location / {
            root   html/vue;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            add_header 'Access-Control-Allow-Origin' The '*';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        }

        #error_page 404 /404.html;

        # redirect server error pages to the static page /50x.html
        #error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 10100; nginx server_name localhost; location / { root html/react; index index.html index.htm; try_files$uri $uri/ /index.html;
            add_header 'Access-Control-Allow-Origin' The '*';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        }

        #error_page 404 /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       10400;
        server_name  localhost;

        location / {
            root   html/static;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            add_header 'Access-Control-Allow-Origin' The '*';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        }

        #error_page 404 /404.html;

        # redirect server error pages to the static page /50x.html
        #error_page 500 502 503 504 /50x.html; location = /50x.html { root html; }}}Copy the code

For online security, access-Control-allow-origin should not be set to * and should be configured with the specified domain name. Note the configuration of nginx in the figure above

  • html/distPort 80 configured for our main application,html/staticSimple written project without Webpack.
  • Each server is configured with a front-end package for a different framework.
  • Note that the configured access address must be the same as that in the main application app.

Review past

  • Write a Vue version of the Upload component
  • On the basis of menu permissions, the operation platform system further refined to button permissions Management (Vue).
  • Vue version Backtop component

The demo address

Github.com/FoneQinrf/q…

conclusion

All the routing modes used in the above applications are history mode. I have tried the hash mode but failed. What if the two modes are mixed? Have you tried a unified hash model or a successful mixture of the two? Oneself level is limited, move brick not easy, inadequacy place asks for advice!