Author: Zhang Yanqing

Business background

Dsp (demander platform) platform of cloud music advertisement is divided into contract platform (Vue framework) and bidding platform (React framework). Due to historical reasons, the framework selection has not been unified. Recently, new requirements have come, and the same module needs to be added to both platforms at the same time. Therefore, considering component reuse and maintenance cost reduction, we are considering how to unify the technology stack and plug the React system into the Vue project for presentation.

The system has a traditional left-right layout, with the left sidebar showing the menu bar, the head navigation showing the basic information, and the app content all filled in the blue content area.

To be honest, my first thought was to nest iframe, but anyone who has used iframe technology knows its pain:

  • Browser history stack issue forward/back
    No matter how deep you lurk in the iframe, you take 10,000 steps back, and the experience is really uncomfortable

  • Application of communication
    Sometimes the main application just wants to know the URL parameters of the subsystem, but iframe applications have different sources, so you have to find other ways to get parameters, and the most common one is this
    postMessage

  • The cache
    iframeAfter the application update goes online, the system will find that the system matches the old content displayed in the cache, which needs to be resolved by using a timestamp solution or forcibly refreshed

In addition, MPA + route distribution is used. When a user visits a page, Nginx and others are responsible for distributing the page to different service applications according to the route, and each service application completes the assembly of resources and returns them to the browser. In this way, the interface and navigation should be made similar.

  • advantages:
    • Multi-framework development;
    • Independent deployment operation;
    • Complete isolation between applications.
  • disadvantages:
    • Poor experience and long loading time for each independent application;
    • Because of complete isolation, changes in common areas such as navigation and the top are large and reusability is poor.

There are also several mainstream micro front-end schemes:



  • Base mode: Based on route distribution, a base application listens for routes and loads different applications according to routing rules to decouple applications
  • EMP: Webpack5 Module Federation, a decentralized micro-front-end solution, can easily realize resource sharing and communication between applications on the basis of application isolation;

In general, IFrame is mainly used for simple and low performance requirements of third-party systems; MPA can not meet current business requirements in terms of implementation cost and experience. Base mode and EMP are both good choices. As Qiankun is widely used in the industry and relatively mature, it was finally selected

Qiankun

Qiankun is a front-end microservice framework launched by Ant Financial based on Single-SPA implementation. In essence, it is still a routing distribution service framework, which is different from the original single-SPA scheme that uses JS Entry to load sub-applications. Alternative optimization was implemented using HTML Entry.

Restrictions on the use of JS Entry:

  • Limit one JS entry file
  • Static resources such as images and CSS need to be packaged into JS
  • Code Splitting cannot be applied

Compared with JS Entry, HTML Entry is much more convenient to use. After the project is configured with a given Entry file, Qiankun will Fetch resources by itself, parse OUT JS and CSS file resources, and insert them into the given container. Perfect

JS Entry is usually done by subapplications typing resources into an Entry Script, similar to the single-SPA example;

HTML Entry is the organization of sub-application resources using HTML format. The main application obtains static resources of sub-application through Fetch HTML, and at the same time, the HTML Document is inserted into the container of the main application as a child node. Better readability and maintainability, closer to the effect of the last page mounted, and no need for bi-directional escape.

Project practice

Since the Vue project has been developed, we need to transform it in the original project. It is obvious that the Vue project is selected as the base application. The New requirements development uses the Create React App to build the React sub-application

Base application modification

The base (main) is constructed by vuE-CLI. We keep its original code structure and logic unchanged, and on this basis provide a mounted container DIV for the child application, which is also filled in the same content display area.

Qiankun only needs to be introduced into the base application. To facilitate management, we added a new directory named Micro, identified the micro front-end transformation code in the directory, and initialized the global configuration as follows:

Route configuration file app.js

// Route configuration
const apps = [
  {
    name: 'ReactMicroApp'.entry: '//localhost:10100'.container: '#frame'.activeRule: '/react'}];Copy the code

Apply the configuration registration function

import { registerMicroApps, start } from "qiankun";
import apps from "./apps";

// Register sub-application functions and wrap them as higher-order functions, so that the app configuration can be modified if there are parameters injected later
export const registerApp = () = > registerMicroApps(apps);

// Export the startup function of Qiankun
export default start;
Copy the code

Layout components

<section class="app-main">
  <transition v-show="$route.name" name="fade-transform" mode="out-in">
    <! -- Main application rendering area, used to mount components triggered by main application routing -->
    <router-view />
  </transition>

  <! -- Child application rendering area, used to mount child application nodes -->
  <div id="frame" />
</section>
Copy the code
import startQiankun, { registerApp } from ".. /.. /.. /micro";
export default {
  name: "AppMain".mounted() {
    // Initialize the configurationregisterApp(); startQiankun(); }};Copy the code

Two important apis of Qiankun will be used here:

  • registerMicroApps
  • start

Note: In the Mounted life cycle, initial configuration is performed to ensure the existence of the mounted container

Let’s understand the process of qiankun registration sub-application in detail through the illustration:

  • After dependency injection, the identification variable parameters are initializedxx_QIANKUN__, for the sub-application to judge the environment
  • When the Qiankun will passactiveRuleTo determine whether to activate the child application
    • activeRuleIf is a string, automatic interception is implemented in route interception mode
    • activeRuleIs a function, check whether the function is activated based on the return value of the function
  • When a child application is activated, the static resource address of the child application is resolved through HTML-Entry and mounted to the corresponding container
  • Create a sandbox environment, find the child application lifecycle functions, and initialize the child application

Build qiankun sub-application

We created a React project based on the Create React App. As described in the process above, we know that the sub-application will expose a series of life cycle functions for qiankun to call, which will be modified in the index.js file:

Add the public-path.js file

Js file. When the child application is mounted under the master application, if some static resources use publicPath=/ configuration, we will get the domain name of the master application, which will cause resource loading error. Fortunately, Webpack provides modification methods. As follows:

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Copy the code

Route Base Settings

This is because, in general, the main application intercepts browser route changes to activate and load child applications. ActiveRule: /react: /react: / activeRule: /react: /react This means that when the browser pathname matches/React, the child application will be activated, but if our child application route configuration looks like this:

<Router>     
  <Route exact path="/" component={Home} />
  <Route path="/list" component={List} />  
</Router>      
Copy the code

How do we implement the domain name /react to load components correctly? You must have experienced the requirement of using domain name level 2 directory to access, here is the same, we can decide whether to adjust base in the Qiankun environment, as follows:

const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/react" : ""; . <Router base={BASE_NAME}> ... </Router>Copy the code

Add life cycle functions

Life cycle function initialization is added to the entry file of the subapplication to facilitate the active application to invoke the life cycle of the subapplication based on the application name after the resource is invoked

/** * Bootstrap will only be called once during the micro-application initialization. The next time the micro-application re-enters, the mount hook will be called directly. Bootstrap will not be triggered again. * This is usually where we can initialize global variables, such as application-level caches that will not be destroyed during the unmount phase. * /
export async function bootstrap() {
  console.log("bootstraped");
}

/** * 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("mount", 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() {
  console.log("unmount");
  ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}
Copy the code

Note: All generative periodic functions must be promises

Modifying the packaging Configuration

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 = `ReactMicroApp`;
    // Expose your library in a way that all module definitions work
    config.output.libraryTarget = "umd";
    // Load as needed, set to webpackJsonp_ReactMicroApp
    config.output.jsonpFunction = `webpackJsonp_ReactMicroApp`; 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

Note: The configuration changes serve two purposes, one is to expose the lifecycle functions to the main application calls, and the other is to allow cross-domain access. Note the changes in the code comments.

  • Exposed Life Cycle: UMD allows Qiankun to match life cycle functions by application name
  • Cross-domain configuration: The primary application fetches resources through Fetch, so to solve the cross-domain problem, it must be set to allow cross-domain access

Summary: The jump process was sorted out and the sub-application jump path was defined in the main application router, as shown in the figure below. In the call to component Mounted life cycle, the loadMicroApp method exposed by Qiankun was used to load the sub-application and jump to the route defined by the sub-application. At the same time using addGlobalUncaughtErrorHandler and removeGlobalUncaughtErrorHandler monitor and deal with abnormal situations (for example, the applied load failure), when listening to hop routing, Load the component defined by the child application (in the

component above) to jump from the main application to the child application.

{
    path: '/xxx'.component: Layout,
    children: [{path: '/xxx'.component: () = > import('@/micro/app/react'),
        meta: { title: 'xxx'.icon: 'user'}}},Copy the code

Problems encountered in the project

1. The sub-application fails to be loaded

If the sub-application system is not loaded after the project is started, we should open the console to analyze the cause:

  • Console no error: The subapplication is not activated. Check whether the activation rules are correctly configured
  • The mounted container was not found: Check that the container DIV must have existed at the time of qiankun start. If not, try to do this after the DOM is mounted.

2. Base applies routing mode

The base application project is a hash route. In this case, the routing mode of the sub-application must be the same as that of the main application; otherwise, an exception will be loaded. The reason is simple. Assuming that the subapplication is in history mode, the pathname will be changed every time the route is switched. In this case, it is difficult to match the subapplication by activating rules, resulting in the unmount of the subapplication

3, CSS style disorder

Since the CSS sandbox is not enabled for style isolation by default, {strictStyleIsolation can be enabled when the main and child applications have style errors: The true} configuration enables strict isolation. This time, the child application will be wrapped with Shadow Dom nodes. I believe you are familiar with this, which is consistent with the style isolation scheme of pages and custom components in wechat applet.

4. In addition, several points needing attention are summarized in the access process

  • Although Qiankun supports jQuery, it is not very friendly to access old projects with multi-page applications. Each page needs to be modified and the cost is also high.
  • Because the method of Qiankun extracted JS files and DOM structure through HTML-entry, it actually shared the same Document with the main application. If the sub-application and the main application defined the same event at the same time, they would affect each otheronClickaddEventListener<body>The addition of a click event does not eliminate the JS sandbox’s impact, depending on the usual code specification
  • The deployment is a bit cumbersome and requires manual resolution of cross-domain issues

5. There may be issues to consider in the future

  • Reuse of common component dependencies: I probably wouldn’t want to write the same request wrapper code in a child application if it was unavoidable in the project, such as request library encapsulation
  • Automatic injection: The process of transformation of each sub-application is actually quite troublesome, but in fact most of the work is standardized process, in consideration of automatic registration of sub-applications through scripts, to achieve automation

conclusion

Actually write down the whole project, the biggest feeling qiankun out-of-box availability is very strong, need to change the project configuration is very few, of course, encountered some pit points must be stepped on to be more clear.

If there are any problems or mistakes in the article, welcome to correct the exchange, thank you!

This article is published from netease Cloud Music big front end team, the article is prohibited to be reproduced in any form without authorization. Grp.music – Fe (at) Corp.Netease.com We recruit front-end, iOS and Android all year long. If you are ready to change your job and you like cloud music, join us!