10

1. Business Background

The current customer service one-stop workbench includes four functions: online service, telephone, work order and tools . The basic structure of the page is as follows:

Each business module is relatively independent and has its own business system. A single module is relatively large. The overall project adopts the SPA + iframe architecture model, and the work order system is nested through iframes . In the process of continuous iteration of customer service business, the architectural model of SPA + iframe has exposed many problems. The main problems are as follows:

  • Problem 1: In the SPA architecture mode, because each module is concentrated in one architecture, the first screen loading resources are too many, and the first screen loading speed is slow; SPA only has entry files, so it is necessary to make business models compatible with each module, resulting in entry files. There are many conditional statements in the code, and the code is disordered. When there are online problems, it is difficult to troubleshoot. If new students participate in the development, it is also difficult to sort out the business, and sometimes it is difficult to understand.
  • Question 2: A large number of iframes are nested in the project. The iframe will also slow down the loading speed of the page. When the iframe uses postMessage communication, it will also bring about various problems such as data delay and data loss. When the page in the previous page cannot be completely released, the memory occupied by the browser keeps soaring, and eventually the browser crashes.

Based on the above two problems, we use the micro-frontend technology to split the one-stop workbench business. This article mainly describes the problems and challenges encountered in the splitting process.

2. Research on technical solutions

Through the investigation of micro-front-end technical solutions, we can know that micro-front-end is an architecture similar to micro-services , which applies the concept of micro-service to the browser side, that is, the single-page front-end application is transformed from a single monolithic application to a multiple An application that aggregates a small front-end application into one, has the following core values:

  • The technology stack is irrelevant: the main framework does not restrict the technology stack of the access application, and the micro-application has complete autonomy
  • Independent development and independent deployment: the micro-application warehouse is independent, the front and back ends can be independently developed, and the main framework automatically completes the synchronization update after the deployment is completed
  • Incremental upgrade: In the face of various complex scenarios, it is usually difficult for us to do a full technology stack upgrade or refactoring of an existing system, and micro-frontend is a very good means of implementing incremental refactoring and Strategy
  • Independent runtime: state isolation between each micro-application, runtime state is not shared

Through the research on related micro-front-end technologies in the open source community, today's mainstream micro-front-end solutions mainly include the following:

  • Technical framework: iframe, single-spa, qiankun, icestark, Garfish, microApp, ESM, EMP
  • Technical highlights: js Entry, html Entry, sandbox isolation, style isolation, web Component, ESM, ModuleFederation
##### Solution ##### Source ##### Features ##### shortcoming
iframe - Naturally isolate styles and scripts, multiple pages The size of the window is not easy to control, and the isolation cannot be broken, resulting in the inability to share the context between applications, which brings about problems such as development experience and product experience. It is impossible to achieve a single page and many functions cannot be displayed in the main application normally.
single-spa foreign Js Entry, the main application rewrites window.addEventListener to intercept the time of the listening route, execute the internal reroute logic, and load the sub-application Based on reroute, it is not suitable for scenarios that require caching and load multiple applications
qiankun Ant Financial Based on single-spa, core functions such as html-entry, sandbox, globalSate, resource preloading, etc. have been added It needs to be compiled into umd mode. For AMD, systemJs support is not friendly, and the official does not publicly support vite build
icestark Ali Write most of the configuration into the window['icestark'] global variable through the cache Only supports React, cross-framework support is not friendly
Garfish byte Enhancement to existing MFE framework, VM Sandbox -
microApp JD.com Implementation based on web Component There are compatibility problems, and the exploration of micro front-end is not mature enough
ESM - Micro modules, compiled into js by build tools, remotely loaded modules, no technology stack restrictions, irrelevant to page routing, can be mounted anywhere Not compatible with all browsers (but it can be solved by compiling tools), need to isolate styles manually (can be solved by css module), application communication is not friendly
EMP Gathering Times Based on Module Federation, decentralization, cross-application state sharing, cross-framework component invocation, remote pulling of ts declaration files, dynamic update of micro-applications, sharing of third-party dependencies, etc. Not all frameworks are currently covered

After research and combined with our current business situation, we adopted qiankun + Module Federation as the technical framework of our micro-frontend, and split the application into 4 independent systems according to functions, which can be independently developed and deployed independently, and can be configured according to permissions. Access to the base ; where the project involves dependencies on other modules, use remote components to load dependent components , such as: IM, the phone will rely on the work order creation, compensation, work order details, order details and other components in the work order, Toolbox currently relies on the session recording component in IM, so IM, work order can be used as remote side, IM, phone, toolbox can be used as host side, providing a more friendly component reuse method, canceling the previous iframe loading method, There is no need to use qiankun to load multiple micro-applications to achieve this, avoiding repeated loading of a large number of resources and improving the response speed of the page.

1. One-stop workbench micro front-end architecture diagram

2. MF remote component planning diagram

3. The specific implementation of the plan

Earlier, we have used qiankun as a micro-front-end framework for business application splitting, and module federation as a framework for sharing remote components between different applications , forming a preliminary framework system. Under this framework system, we are faced with There are many technical challenges, as follows:

  • Micro-applications need to have the ability to cache (keep-alive), and the application switching state cannot be lost
  • Need to be able to load multiple microapps at the same time
  • Sandbox isolation and introduction of third-party resources
  • pedestal-micro-apps, micro-apps-how to communicate between micro-apps
  • How to access remote components
  • style isolation

Schematic diagram of the base-microapp connection

1. Implementation of micro-application caching capability

qiankun provides us with two registration methods: registerMicroApps , loadMicroApp

  • registerMicroApps(apps, lifeCycles?) : Applicable to route-based scenarios, routing changes will help us automatically register micro-applications and destroy the previous micro-application. For applications that do not need to be cached, this method is recommended. It is simple and easy to use. You only need to set an independent route matching rule for the micro application.

The following is a demo example from the official website of qiankun:

 import { registerMicroApps } from 'qiankun';

registerMicroApps(
  [
    {
      name: 'app1',
      entry: '//localhost:8080',
      container: '#container',
      activeRule: '/react',
      props: {
        name: 'kuitos',
      },
    },
    {
      name: 'app2',
      entry: '//localhost:8081',
      container: '#container',
      activeRule: '/vue',
      props: {
        name: 'Tom',
      },
    },
  ],
  {
    beforeLoad: (app) => console.log('before load', app.name),
    beforeMount: [(app) => console.log('before mount', app.name)],
  },
);
  • loadMicroApp(app, configuration?) : Suitable for scenarios where a micro-app needs to be loaded/unloaded manually. For us, which needs to implement caching and load multiple micro-apps at the same time, this method is more suitable.

in conclusion:

After qiankun 2.0, the official loadMicroApp API is provided for us , which gives us the ability to manually control application loading/unloading, and it is not based on routeBase to load resources, so we don't have to worry about the previous micro-application being actively uninstalled when switching menus.

Based on the feature of loadMicroApp to manually control the loading of micro-applications, if you want to achieve the keep-alive capability, you can set the appropriate keep-alive caching strategy on the base and micro-applications, and then use the "display: none" method to control the display and hide of the switch ( DOM re-rendering will result in loss of historical state), set a mount point for each micro-app in the base, and the DOM of the previous micro-app will not be unloaded when the application is switched.

Logic in pedestal :

When we detect a route change, manually call loadMicroAppFn to load the corresponding micro-application. For scenarios that need to be loaded at the same time, we can call the load in a loop (loading multiple micro-applications under vite construction may fail, it is recommended to use webpack build).

For specific reasons, please refer to the issue:

 // 手动加载微应用方法封装
const loadMicroAppFn = (microApp) => {
  const app = loadMicroApp(
    {
      ...microApp,
      props: {
        ...microApp.props,
        // 下发给微应用的数据
        microFn: (status) => setMicroStatus(status)
      },
    },
    {
      sandbox: true,
      singular: false
    }
  );
  
  return app;
}
 // 为每个微应用提供一个挂载的容器节点:
<template>
  <div class="tabs-view">
    <div class="tabs-view-content tabs-view-container">
      <template v-if="microApps && microApps.length">
        <div
          v-for="micro in microApps" :key="micro.name"
          :id="micro.id"
          v-show="currentPath && currentPath.startsWith(`${micro.key}`)"
        ></div>
      </template>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed, toRefs, watch, ref } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from 'vuex'

export default defineComponent({
  name: 'Micro-content',
  components: {
  },
  props: {
    currentMenu: {
      type: String,
      default: ''
    }
  },
  setup(props) {
    const route = useRoute()
    const store = useStore()
    // 微应用注册表
    const microApps = computed(() => store.getters.microAppsList).value
    const currentPath = ref(route.path)

    watch(
      () => route.path,
      (to, _) => {
        currentPath.value = route.path
      },
      { immediate: true }
    )

    return {
      route,
      spin,
      microApps,
      currentPath
    }
  }
})
</script>
 <template>
  <div class="app-content">
    <a-config-provider :locale="zhCN" prefixCls="basic">
      <router-view v-if="isShowViews" v-slot="{ Component }">
        <keep-alive v-if="isKeppAlive">
          <component :is="Component" />
        </keep-alive>
        <component :is="Component" v-else />
      </router-view>
    </a-config-provider>
  </div>
</template>

Logic in the sub-application: The qiankun life cycle needs to be called, and the entry file sets the appropriate keep-alive caching strategy

 import './public-path'
import { createApp } from 'vue'
import App from './App.vue'
import router, { setupRouter, destroyRoute } from '@/router'
import { setupStore } from '@/store'
import { isChildApp } from '@/utils/env'

let app: any = null
function render(props) {
  app = createApp(App)
  // 挂载vuex状态管理
  setupStore(app, props)
  // 挂载路由
  setupRouter(app)
  // 路由准备就绪后挂载APP实例
  router.isReady().then(() => {
    app.mount(document.getElementById('miro-app'))
  })
}

// 独立运行时
if (!isChildApp()) {
  render({})
}

// 暴露主应用生命周期钩子
export async function mount(props: any) {
  render(props)
}

export async function bootstrap() {
  console.log('vue app bootstraped')
}

// 销毁生命周期
export async function unmount(props: any) {
  app.unmount()
  app._container.innerHTML = ''
  destroyRoute()
  app = null
}
 <template>
  <a-config-provider :locale="zhCN">
    <router-view v-slot="{ Component }">
      <keep-alive v-if="isKeepAlive">
        <component :is="Component" />
      </keep-alive>
      <component :is="Component" v-else />
    </router-view>
  </a-config-provider>
</template>

Performance comparison chart before and after micro application loading:

  • The performance consumption of activating each micro-app for the first time:

  • Performance consumption of switching micro-apps after successful loading:

\

Through the performance comparison before and after the activation of the micro-app, we can see that:

  • When the micro-application is initialized and loaded, it needs to go through a resource request and page rendering, and there will be a large performance overhead;
  • After the micro-app is loaded successfully, switch it back here, and use "display: none" + keep-alive processing + routing filtering. Although it needs to go through a reflow and redraw, it will not bring too much performance overhead ;

2. Sandbox isolation and introduction of third-party resources

The sandbox inside qiankun is mainly divided into LegacySandbox and SnapshotSandbox according to whether it supports window.Proxy or not. For third-party resources loaded through the script tag, it should be noted that a global variable to be displayed is declared and mounted on the window, so that it can be obtained when it is used.

Extended reading: There is also a ProxySandbox sandbox for multiple instances, which seems to be the best solution at present. Since its behavior is slightly different from the old version, it is only used in multi-instance mode for the time being. The ProxySandbox sandbox may be used as a single instance sandbox after it is stabilized. Original link: https://segmentfault.com/a/1190000022275991

 // 例如下面这个例子
// global.js中定义一个全局变量
var globalMicroApp = 'micro-name'
// index.html引入这个global.js
<script src="global.js"></script>

// global.js中定义一个全局变量
var globalMicroApp = 'micro-name'
window. globalMicroApp = globalMicroApp
// index.html引入这个global.js
<script src="global.js"></script>

Case 1 Due to sandbox isolation, the global variable cannot be obtained when using it. Case 2 is the correct way. If jQuery is used, it is best to load it in the base. For example, when using ajax jsonp to load resources across domains When it is placed in the micro-application for sandbox isolation, the callbackName cannot be obtained (the one that is not displayed is mounted on the window). Special processing is also required for jsonp cross-domain requests, otherwise qiankun will hijack the jsonp request. Converting it to a fetch request causes cross-domain failure.

 const loadMicroAppFn = (microApp) => {
  const app = loadMicroApp(
    {
      ...microApp,
      props: {
        ...microApp.props
      },
    },
    {
      sandbox: true,
      singular: false,
      // 指定部分特殊的动态加载的微应用资源(css/js) 不被 qiankun 劫持处理
      excludeAssetFilter: (url) => {
        return !!(url.indexOf("https://xxx.com/xxx") !== -1);
      },
    }
  );

  return app;
};

3. Communication between applications

Communication methods can be adopted: URL carrying parameters, window, postMessage, props provided by qiankun, initGlobalState, etc.; only two methods of props and initGlobalState are introduced here.

  • Parameters are passed as props:

The base sends a state parameter through the qiankun loadMicroApp method. This state can be a common type, a callback, or a vuex action method. After the micro application is activated, the state passed by the props can be obtained through the qiankun life cycle function mount. If If the micro-app needs to update the data to the base, it can send an action or callback. After accepting the method, the micro-app saves it to its own vuex store. After the data needs to be updated, it directly calls the cached action or callback.

Schematic diagram of props communication

  • Parameters are passed in initGlobalState method:

Schematic diagram of action subscription-publishing mode

Pedestal:

 import { initGlobalState, MicroAppStateActions } from 'qiankun';

// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);

actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();

Microapps:

 // 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });

  props.setGlobalState(state);
}

4. How to access remote components

Remote components are implemented using webpack5 module federation. Matters needing attention in micro front-end practice:

 // mian.ts中只能导出qiankun生命周期
const { bootstrap, mount, unmount } = await import('./bootstrap')
export { bootstrap, mount, unmount }

It is necessary to transfer the entry file (mian.ts) to a new file (bootstrap.ts), and export the qiankun life cycle in the entry file, so as to avoid packing out two entry files, causing the qiankun to fail to load the life cycle function.

For detailed access methods, please refer to this article: Best Practices of Module Federation in Dewu Customer Service Work Order Business

5. Style isolation

The official qiankun API provides us with a very complete API, as shown below:

sandbox - boolean | { strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }

  • The default scenario sandbox: true, can only guarantee the style isolation under a single instance, cannot guarantee the coexistence of multiple micro-applications, and the style isolation between the base-micro-application;
  • Set to strictStyleIsolation: true ; indicates that strict style isolation mode is enabled. In this mode, qiankun will wrap a shadow dom node for the container of each micro application to ensure that the style of the micro application will not affect the global;
  • qiankun also provides an experimental style isolation feature. When experimentalStyleIsolation is set to true, qiankun will rewrite the style added by the sub-application and add a special selector rule to all style rules to limit its scope of influence, so the rewritten The code will express a structure similar to the following:
 .app-main {
  font-size: 14px;
}

div[data-qiankun-react16] .app-main {
  font-size: 14px;
}

This experimental feature (experimentalStyleIsolation) can also be implemented through the postcss plugin. The community provides a plugin postcss-plugin-namespace , which is relatively simple to use. The configuration is as follows:

 postcss:{
    plugins:[require('postcss-plugin-namespace')('.basic-project',{ ignore: [ '*'] })]
}
 .app-main {
  font-size: 14px;
}

.basic-project .app-main {
  font-size: 14px;
}

Although the official provides a very complete API, it cannot perfectly solve the problem of style conflicts for many scenarios. For example, the global style of the base will pollute the global style of the micro application. If you use antd/ant-design -vue, you can change the UI library prefix in the following way, which is also a good solution: In the entry file app.vue: ant-design-vue provides a prefixCls to help us modify the class prefix:

In vue.config.js, you can override the class name global variable of ant-design-vue in less/sass loader :

The effect after modification:

 // 修改前
.ant-menu-item {
  text-align: center;
  padding: 10px;
}

// 修改后
.basic-menu-item {
  text-align: center;
  padding: 10px;
}

Fourth, the results

Through the transformation of the one-stop workbench through the micro-front-end technology, we have made a comparison before and after the transformation:

project name CR efficiency Development efficiency shuttle bus release system remote component
Transformation of the former slower The project is heavy, the code coupling is high, and the development is difficult The application is heavier, and there are many issues to be considered in the release of the shuttle bus not support
After transformation Each sub-application is split and completely decoupled, saving 1/3 of the time Independent application development, business logic decoupling, higher development efficiency Independent development, independent release, lighter weight, shuttle release, less content to be tested and returned, and faster delivery of business requirements support

Five, thinking and summary

Going through the whole process from project initiation to completion, we chose qiankun as our micro front-end framework. The whole development process was difficult and tortuous. The first difficulty was the realization of the micro-application caching capability. There was only a short demo in the community, and the distance to the real landing The project is far from bad; secondly, our project also needs to consider refreshing the page and reloading other micro-applications in the current micro-application scenario; some micro-applications need to rely on third-party plug-ins, this plug-in may be a jQuery plug-in, it may be We will also encounter jsonp cross-domain scenarios; we also need to consider the reuse of common components between micro-applications; the original project was built with vite, and in the face of qiankun’s unfriendly support for vite, we finally had to choose webpack5.

After encountering this series of problems, and then to solve these problems, for us, the benefits are still great, and we have accumulated a lot of shortcomings in the community plan. After this project, my thinking is: any technical framework has its applicable scenarios. For a specific business scenario, the original technical framework may be bloated, but it may be the most suitable. Micro front-end is not a myth, and the correct scenario is used. The right technology is the best choice.

6. Reference documents

Text/CHENLONG

Pay attention to Dewu Technology and be the most fashionable technical person!


得物技术
841 声望1.5k 粉丝