Let’s start with the summary of the local environment stepping on the pit:

In the original project, iframe was used to embed different micro-applications. Although iframe had many problems, it was actually quite easy to use. I wanted to change it because the post-80s Version of Chrome would not send Cookies across sites. It can also test practical microfronds in real projects, but Cookies also exist.

First, let’s talk about the risks. Here are some of the existing risks summarized after the successful attempt at Qiankun:

  • First, after the microapplication was mounted once, Qiankun would cache relevant JS and CSS resources in memory. In other words, when the microapplication was mounted again after uninstallation, it did not need to request these resources. There are pros and cons to this approach: the pros are that remounting into microapps is much faster, and the cons are that if the microapps are too heavy, they can take up too much memory. (Hopefully, the Qiankun will provide a Destroy interface that allows users to decide whether to destroy or cache.)

  • Memory leak: The browser refreshes the page, and the microapplication resources cached in the memory are not cleaned up, causing memory leak. For example, this part of the microapplication JS resource is 5M. Refresh once and reload the 5M, but the 5M cached in memory is not cleaned up. Now it’s 5 times 2 is 10 meters.

    For reproduction, please refer to my issues1073. I later found a temporary solution to this problem: introducing zone.js into the main project entry file temporarily fixed the problem.

  • A problem with not sending Cookies across sites. Chrome after 80 blocks cookies from third parties by default, The default value of SameSite changed from None(sending cookies across sites or not) to Lax(allowing some third party requests to carry cookies), which means that the main application after Chrome80 cannot send cookies to microapplications. But there are solutions to this problem. For a detailed description of the problem and solutions, see these two articles.

Project Background

Both the master app and the micro app are vUE based (if you are not, the following may not be useful for you). Microapplications are mounted to the child route /service of the active application. For example, microapplication 1 is /service/app1, and microapplication 2 is /service/app2. The main application TAB is switched, and the status of the micro-application component remains.

Microapplication Access Guide:

1. Static resources and interfaces for microapplicationsMust beAllow master applications to cross domains by setting headers, for example:

Access-Control-Allow-Origin: *
Copy the code

Here are the go backend Settings for reference:

beego.InsertFilter("*", beego.BeforeStatic, cors.Allow(&cors.Options{
	AllowOrigins:     []string{"http://localhost:9931"."localhost:9931"},
	AllowCredentials: true,
}))
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
	AllowOrigins:     []string{"http://localhost:9931"."localhost:9931"},
	AllowCredentials: true,}))Copy the code

Note: There is a point to understand here. Why does the microapplication interface need to allow the master application to cross domains, even if the microapplication is not separated from the front end? The front-end resources of micro application were fetched by Qiankun through fetch, but they actually ran in the main application. Therefore, for example, your main application is Localhost :9931, and the micro application is Localhost :8080. A back-end interface was adjusted in the front end of the micro application. The actual request is made from localhost:9931, so there is a cross-domain problem, which is why the microapplication interface needs to allow the main application to cross domains.

2. Front-end access of micro application

1) Export the corresponding lifecycle hooks:

Microapplications need to export three life cycle hooks bootstrap, mount, and unmount in their own entry js, so that the main application can call them when appropriate. Take Vue as an example:

if (window.__POWERED_BY_QIANKUN__) {
  // Dynamically set webpack publicPath to prevent static resource loading errors
  // eslint-disable-next-line
  const path = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__.includes('page/jump/')?window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__.split('page/jump/') [0]
    : window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  // eslint-disable-next-line
  __webpack_public_path__ = `${path}static/`;  // Set the static resource path correctly.
}
import Vue from 'vue';
import Router from 'vue-router';
import appRouter from './router.js';
import store from './store';

import App from './App';

Vue.use(Component);
Vue.use(Router);

let instance = null;
let router = null;

function render() {
  router = new Router(appRouter);

  router.beforeEach((to, from, next) = > {
    // In-app route switching does not trigger micro-app routes
    if (window.__POWERED_BY_QIANKUN__ && ! location.pathname.startsWith('/service/appname')) {
      return;
    }
    if(! to.matched || to.matched.length ===0) {
      next('/ 404');
      return;
    }
    next();
  });

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

// Mount the application directly when running independently
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {}export async function mount(props) {
  render(props);
}

export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}

Copy the code

2) Configure the microapplication packaging tool, webpack is as follows:

module.exports = {
  // ...
  output: {
    library: 'appname'.libraryTarget: 'umd'.jsonpFunction: 'webpackJsonp_appname',}}Copy the code

3) Since the master application in the project mounts the microapplication under the child route /service, the base path needs to be configured for the microapplication route, taking VUE as an example:

const router = {
  base: window.__POWERED_BY_QIANKUN__ ? '/service/appname/' : '/'.routes: [...]. };Copy the code

3. If you want to send cookies with login information to the micro application backend, you need to do the following two steps:

1) Origin cannot be set to *, should be set to the specified address and allow cross-domain cookie, such as:

Access-Control-Allow-Origin: http://localhost:9931
Access-Control-Allow-Credentials: true
Copy the code

2) The front-end call interface is set with cookies, for example:

// Example 1: axios
axios.create({
  withCredentials: true
});
// Example 2: Native fetch
fetch('https://example.com', {
  credentials: 'include'  
})
Copy the code

4. After the above steps, the micro-application has been embedded into the main application. However, if you want to realize the TAB switch of the main application and return to the micro-application, the original state of the micro-application component remains, it needs to:

1) Join in router-view

<keep-alive>
  <router-view></router-view></keep-alive> <! If you don't want to cache component state while the microapplication is running independently, you can write:<keep-alive v-if="isQianKun">
  <router-view></router-view>
</keep-alive>
<router-view v-else></router-view>
export default {
  computed: {
    isQianKun() {
      return window.__POWERED_BY_QIANKUN__; }}}Copy the code

2) The route switching within the micro-application does not cache the component state. If you define the name attribute in all the components of the micro-application according to the specification, you can implement it according to the [Method 1], namely the include attribute provided by vUE official. However, if the micro-application has many routes, the name attribute was not added to each component before. Instead of adding the name attribute one by one, you can use [method 2] to determine whether to use the cached component state by conditional judgment before entering the component.

Method 1: The basic idea is to cache the child route first, and then decide whether to remove the cache according to the situation when leaving the next route.

<keep-alive :include="keepAlive">
  <router-view></router-view>
</keep-alive>
import {componentList} from './router'; // An array of component names for all routes
export default {
  watch: {
    $route(to, from) {
      const isFromAppRoute = componentList.includes(from.name) && from.matched.length ! = =0, isToAppRoute = componentList.includes(to.name) && to.matched.length ! = =0;
      if (window.__POWERED_BY_QIANKUN__) { // Components are not cached for in-app jumps or non-embedded microapplications
        if (!this.keepAlive.includes(to.name) && isToAppRoute) {
          this.keepAlive.push(to.name);
        }
        if(! isFromAppRoute || ! isToAppRoute)return;
        // 
        const fromIndex = this.keepAlive.findIndex(i= > i === from.name);
        if (fromIndex === -1) return;
        this.keepAlive.splice(fromIndex, 1); }}},data() {
    return {
      keepAlive: []}},}Copy the code

Law 2: the basic idea is the same as law 1.

The route guard is registered in the entry main.js and called when leaving the component.

import qiankunMixin from './qiankun-cache';
Vue.mixin(qiankunMixin);
Copy the code
// qiankun-cache
export default {
  beforeRouteLeave: function(to, from, next) {
    if (!window.__POWERED_BY_QIANKUN__) {
      next();
      return;
    }
    // Depending on your business change the judgment logic here, decide whether to destroy this layer cache or not. The default is to cache only TAB - switched components of the Qiankun microapplication
    const isFromAppRoute = from.path.startsWith('/basepath/') && from.matched.length ! = =0,
      isToAppRoute = to.path.startsWith('/basepath/') && to.matched.length ! = =0;
    if (isFromAppRoute && isToAppRoute) {
      if (this.$vnode && this.$vnode.data.keepAlive) {
        if (this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache) {
          if (this.$vnode.componentOptions) {
            const key = this.$vnode.key === null
              ? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? ` : :The ${this.$vnode.componentOptions.tag}` : ' ')
              : this.$vnode.key,
              cache = this.$vnode.parent.componentInstance.cache,
              keys  = this.$vnode.parent.componentInstance.keys;
            if (cache[key]) {
              if (keys.length) {
                const index = keys.indexOf(key);
                if (index > -1) {
                  keys.splice(index, 1); }}deletecache[key]; }}}}this.$destroy(); } next(); }};Copy the code

Potholes and solutions stepped by the front end in microapplications:

1. Babel repeats the question

Babel6 Only one instance of babel-polyfill is allowed

Solution:

Step 1) Delete the import ‘babel-polyfill’ from the js entry of the main and micro-apps;

Step 2) Extract polyfill as JS resource and load it as JS resource in index.html. Main application: Micro-application:

* Note: Ignore is a custom attribute of the qiankun microapplication, which means that the sub-project needs to reuse the js dependency of the main project. With this attribute, the Sub-project will not load the JS, and the sub-project will run independently. *

2. The request browser at the backend automatically initiates an OPTIONS request

Problem description: The micro-application invocation interface is cross-domain. When a cross-domain request is initiated, due to security reasons, when certain conditions are triggered, the browser will automatically initiate OPTIONS request before formal request, that is, CORS pre-check request. If the server accepts the cross-domain request, the browser will continue to initiate formal request.

The reason:

1) OPTIONS MDN mentioned:

The specification requires that HTTP request methods (especially HTTP requests other than GET, or POST requests paired with certain MIME types) that may have adverse effects on server data, The browser must first issue a preflight request using the OPTIONS method to know whether the server will allow the cross-domain request.

2) In the case of cross-domain request, OPTIONS request triggers the following conditions:

  • The following any HTTP method of use: PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH;
  • Accept/ accept-language/content-language/content-type /DPR/Downlink/ save-data/viewport-width /Width;
  • Content-type values do not belong to one of the following: Application/X-www-form-urlencoded, multipart/form-data, text/plain

Solution:

Method 1: Check [trigger condition of Cause 2] to adjust and try not to trigger OPTIONS request.

Method 2: The back-end interface processes OPTIONS requests and prechecks the request results using access-Control-max-age caching.

3. Avoid using wildcards to configure routes 404 in Qiankun

Problem Description: In order to achieve keep-alive status of the micro-application component, after mounting the micro-application through loadMicroApp, the micro-application will not be unloaded unless the TAB is closed. However, the problem is that when cutting from the micro-application to the main application, if the route of the micro-application uses the wildcard * to process 404, The last page the microapplication leaves is actually 404.

Solution: Register the 404 component path as /404, register a global front-guard beforeEach to jump to /404 when no match is found.

4. When the micro-application is unmounted and mounted again, intra-micro-application route redirecting errors occur.

The Render function should create a new route object to ensure that the route object is correctly loaded each time it is remounted.

import appRouter from './router.js';
function render() {
  // Solution
  router = new Router(appRouter);

  instance = new Vue({
    router,
    store,
    render: h= > h(App),
  }).$mount('#appName-app');
}
Copy the code

Potholes and solutions for the front end in the main application:

1. Internet Explorer 11 compatibility problems

1) Add dependencies [email protected],[email protected], and [email protected];

2) The main application entry file is introduced as follows:

// The following are some plug-ins compatible with IE11
import 'whatwg-fetch';
import 'custom-event-polyfill';
import 'core-js/stable/promise';
import 'core-js/stable/symbol';
import 'core-js/stable/string/starts-with';
import 'core-js/web/url';
Copy the code

2. Change the VUE route tohistoryAfter the mode, you need to adjust it as follows:

(1) If the URL does not match any static resources, the back end should return index.html by default

(2) Change the publicPath of Webpack to the absolute path. For example, publicPath: ‘/static/’. (otherwise you might have problems loading part of chunk 404);

3. The active application requests microapplication static resources that do not carry cookies and are customizedfetchTo solve

const app = loadMicroApp(this.microList[appnname], {
  fetch(url, data) {
    constfetchConfig = { ... data,credentials: 'include'
    };
    return window.fetch(url, fetchConfig); }});Copy the code

【 References 】

qiankun

When will the options request be sent

Summary of Qiankun Micro-front-end Practice (II)