This is the first day of my participation in the August Text Challenge.More challenges in August

Because this development project needs to embed the previous old project, due to take into account the iframe speed is slow, CSS/JS need additional requests, blocking page loading, browser forward/back and other shortcomings, so I intend to step on the qiankun, in order to get earlier.

Introduction to the

Qiankun is a single-SPa-based microfront-end implementation library that aims to make it easier and painless to build a production-ready microfront-end architecture system.

Official resources:

  • A sample code is provided
  • An online demo of sample code
  • Access the online VUE child application separately
  • Access the React child app alone

According to theQiankun official fileIntroduction, mainly has the following seven features:

  • 📦 provides a more out-of-the-box API based on a single-SPA package.
  • 📱 technology stack independent, any technology stack applications can use/access, be it React/Vue/Angular/JQuery or other frameworks.
  • 💪 HTML Entry access, let you access micro applications as easy as iframe.
  • 🛡 style isolation to ensure that styles do not interfere with each other in microapplications.
  • 🧳 JS sandbox to ensure that global variables/events do not conflict between microapplications.
  • ⚡️ Resource preloading: Preloads unopened micro-application resources in idle time of the browser to speed up the opening of micro-applications.
  • 🔌 UMI plug-in, providing @umiJS/plugin-Qiankun for UMI applications to switch to a micro front-end architecture system with one click.

Views and practices of other front-end teams in the industry on microfronds:

  • Daily Youxian supply chain front end team micro front end transformation
  • Micro front end in meituan takeout practice
  • Polish and application of front-end microserver in Bytedance
  • Practice of micro front end in Mi CRM system
  • Implementation of standard microfront-end architecture in Ant

The API is introduced

This section only describes the basic functions of the API. For more information, please refer to the official documentation

registerMicroApps(apps, lifeCycles?)

Basic configuration information for registering microapplications. When the browser URL changes, it will automatically check the activeRule rule registered for each micro-application, and the application that meets the rule will be automatically activated.

import { registerMicroApps } from 'qiankun';

registerMicroApps(
  [
    {
      // name-string - Specifies the name of the micro-application. Each micro-application must be unique
      name: 'apass-micro'.// entry-string - Mandatory, the entry of the micro-application
      entry: 'localhost:8080'./ / container - string | HTMLElement - will be chosen, the application of micro selector of the container node or Element instance
      container: '#apassMicroTemplateConfig'.// activeRule -string - Mandatory, micro application activation rule
      activeRule: '/index/config/template/edit'.// props - Object - Optional, the data that the main application needs to pass to the micro-application
      props: {
        name: 'kuitos'.routerPushFunc: (that) = > {
          that.$router.push('/713/5f4f65fabcb7c173/fields')},data: {
          // Reactive data communication
          store: microAppStore.getGlobalState
        },
      }
    }
  ],
  {
    beforeLoad: app= > console.log('before load', app.name),
    beforeMount: [
      app= > console.log('before mount', app.name),
    ],
    afterMount: [
      app= > console.log('after mount', app.name),
    ],
    beforeUnmoun: [
      app= > console.log('before unmount', app.name),
    ],
    afterUnmount: [
      app= > console.log('after unmount', app.name),
    ]
  },
);
Copy the code

start(opts?)

Start the qiankun

import { start } from 'qiankun';

start();
Copy the code

setDefaultMountApp(appLink)

Set the microapps that enter by default after the main app starts.

import { setDefaultMountApp } from 'qiankun';

setDefaultMountApp('/homeApp');
Copy the code

runAfterFirstMounted(effect)

The first micro applies methods that need to be called after the mount, such as enabling some monitoring or burying scripts.

import { runAfterFirstMounted } from 'qiankun';

runAfterFirstMounted(() = > {
  console.log('This method is called after the first child application loads.')
  this.otherFunction()
})
Copy the code

loadMicroApp(app, configuration?)

This applies to scenarios where you need to manually load or uninstall a microapplication.

Typically, a microapplication in this scenario is a business component that can run independently without routing. You are advised to split microapplications based on service domains. Functional units with close business connection should be made into one micro-application, whereas those with weak business connection can be divided into multiple micro-applications. One criterion for determining whether the business is closely related is whether this micro-application has frequent communication requirements with other micro-applications. If it is possible to demonstrate that the two microapplications themselves serve the same business scenario, merging them into a single microapplication may be more appropriate.

import { loadMicroApp } from 'qiankun';

// Since loadMicroApp() returns an instance of the child application, take a global variable and then perform other operations such as manually unloading the child application
this.microApp = loadMicroApp(
  {
    name: 'sub-vue'.entry: 'http://localhost:7777/subapp/sub-vue'.container: '#apassMicroTemplateConfig'.props: {
      routerBase: '/index/config/template/edit'.getGlobalState: microAppStore.getGlobalState,
      sheetId: '2133123123'}}, {// sandbox - boolean | { strictStyleIsolation? : boolean, experimentalStyleIsolation? : Boolean} - Optional, whether to enable sandbox. Default is true
    sandbox: { strictStyleIsolation: true },
    // singular - boolean | ((app: RegistrableApp
      
       ) => Promise
       
        ); - Optional, whether it is a single instance scenario, which means that only one microapplication is rendered at a time. The default is false
       
      
    singular: true})// Encapsulates the function that uninstalls the child application
private unmountMicroApp () {
  if (this.microApp) {
    this.microApp.mountPromise.then(() = > {
      this.microApp.unmount()
    })
  }
}
Copy the code

prefetchApps(apps, importEntryOpts?)

Manually preloads the specified microapplication static resources. Manually loading the prefetch attribute is required only in micro-application scenarios. Configure the Prefetch attribute based on automatic route activation scenarios.

import { prefetchApps } from 'qiankun';

prefetchApps([ { name: 'app1'.entry: '//locahost:7001' }, { name: 'app2'.entry: '//locahost:7002'}])Copy the code

Active Application Configuration

Install qiankun

$ npm i qiankun -S # yarn Add Qiankun
Copy the code

Adjust the main. Js

If you need to load these subapps at project initialization, you’ll need to change some of the main.js configuration. You can skip this step if you manually load the file on the page.

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

import { registerMicroApps, setDefaultMountApp, start } from "qiankun"
Vue.config.productionTip = false
let app = null;
/** * Render function * appContent child application HTML content * loading child application loading effect, optional */
function render({ appContent, loading } = {}) {
    if(! app) { app =new Vue({
            el: "#container",
            router,
            data() {
                return {
                    content: appContent,
                    loading
                };
            },
            render(h) {
                return h(App, {
                    props: {
                        content: this.content,
                        loading: this.loading } }); }}); }else{ app.content = appContent; app.loading = loading; }}/** * Route listener *@param {*} RouterPrefix prefix * /
function genActiveRule(routerPrefix) {
    return location= > location.pathname.startsWith(routerPrefix);
}

function initApp() {
    render({ appContent: ' '.loading: true });
}

initApp();

// Pass data to the child application
let msg = {
    data: {
        auth: false
    },
    fns: [{name: "_LOGIN"._LOGIN(data) {
                console.log('Parent application returns information${data}`); }}};// Register child applications
registerMicroApps(
    [
        {
            name: "sub-app-1".entry: "//localhost:8091",
            render,
            activeRule: genActiveRule("/app1"),
            props: msg
        },
        {
            name: "sub-app-2".entry: "//localhost:8092",
            render,
            activeRule: genActiveRule("/app2"}, {),beforeLoad: [
            app= > {
                console.log("before load", app); }].// Pre-mount callback
        beforeMount: [
            app= > {
                console.log("before mount", app); }].// Mount callback
        afterUnmount: [
            app= > {
                console.log("after unload", app); }]// Callback after uninstallation});// Set the default child application to the same parameters as in genActiveRule
setDefaultMountApp("/app1");

/ / start
start();
Copy the code

Change the ID in app. vue or add render child App box

Since a main App can have multiple sub-apps nested, app.vue will inevitably have the same name, so it is best to add a prefix of your project name to distinguish it.

<template>
  <div id="main-root">
    <! -- loading -->
    <div v-if="loading">loading</div>
    <! -- App box -->
    <div id="root-view" class="app-view-box" v-html="content"></div>
  </div>
</template>

<script>
export default {
  name: "App".props: {
    loading: Boolean.content: String}};</script>
Copy the code

Configure vUE subapplications

As the sub-application itself was a separate application, it was not necessary to install Qiankun and only needed to expose the three life cycles required by qiankun when it was embedded as a sub-application.

Configuration maim. Js

In addition to supporting embedding as a child application, the project needs to be supported to run independently, compatible with the previous configuration

import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import './public-path';

Vue.config.productionTip = false;

let router = null;
let instance = null;

function render() {
    router = new VueRouter({
        base: window.__POWERED_BY_QIANKUN__ ? '/app1' : '/'.mode: 'history',
        routes,
    });

    instance = new Vue({
        router,
        render: h= > h(App),
        beforeMount () {
            if (window.__POWERED_BY_QIANKUN__) {
                routerPushFunc(this)
                AppModule.SET_CURRENT_ENV()
            }
        }
    }).$mount(container ? container.querySelector('#templateConfig') : '#templateConfig');
}

if (!window.__POWERED_BY_QIANKUN__) {
    render();
}

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

export async function mount(props) {
    console.log('props from main app', props);
    render();
}

export async function unmount() {
    (instance as Vue).$destroy();
    (instance as Vue).$el.innerHTML = ' '; // Prevent memory leaks and clear the DOM when subprojects are destroyed
    instance = null;
    router = null;
}
Copy the code

public-path.js

Static publicPath configuration with Webpack: You can set it either by importing the public-path.js file directly into mian.js, or by modifying vue.config.js directly in your development environment

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
Copy the code

Configure the vue. Config. Js

Sub-application must support cross-domain: Since Qiankun acquired static resources introduced by sub-application through FETCH, it must require these static resources to support cross-domain

const path = require('path');
const { name } = require('./package');

function resolve(dir) {
    return path.join(__dirname, dir);
}

const pagesMicro = {
  templateConfig: {
    entry: 'src/microPage/templateConfig/main.ts'.template: 'src/microPage/templateConfig/index.html'.chunks: ['runtime~templateConfig'.'chunk-vendors'.'chunk-common'.'templateConfig']}}const pagesMain = {
  index: {
    entry: 'src/main.ts'.template: '/index.html'}}const pages = process.env.VUE_APP_ENTRY === 'main' ? pagesMain : pagesMicro

let config = {
  /** * You will need to set publicPath if you plan to deploy your site under a sub path, * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, * then publicPath should be set to "/bar/". * In most cases please use '/' !!! * Detail: https://cli.vuejs.org/config/#publicpath */
    outputDir: 'dist'.assetsDir: 'static'.filenameHashing: true.// tweak internal webpack configuration.
    // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
    devServer: {
        / / host: '0.0.0.0',
        hot: true.disableHostCheck: true,
        port,
        overlay: {
            warnings: false.errors: true,},headers: {
            'Access-Control-Allow-Origin': The '*',}},// Customize the WebPack configuration
    configureWebpack: {
        resolve: {
            alias: {
                The '@': resolve('src'),}},output: {
            // Package the child application into the UMD library format
            library: `${name}-[name]`.libraryTarget: 'umd'.jsonpFunction: `webpackJsonp_${name}`,}}};if (process.env.VUE_APP_ENTRY === 'micro') {
  config.pages = pagesMicro
}

module.exports = config
Copy the code

FAQ and Solutions in Qiankun

Avoid CSS contamination

Qiankun can only solve the style pollution among sub-projects, but not the style pollution of the main project. There are about 5 solutions in terms of technology and specifications:

  • Vue comes with scope
    • It can only solve some of the style contamination within the page, but it is generally not a problem
  • BEM naming mode
  • css-in-js
    • High learning curve; Poor readability; Use the front-end stack to consume performance;
  • css-loader
    • Turn on CSS-modules, similar to lazy loading of images, instead of attr
    • Cons: You need to write class in CSS-modules; After many styles, the form of hash is not readable;
  • postcss-loader
    • Convert classes in all CSS files to JSON objects using the getJson() function of postCSs-Modules; Render json objects back to the HTML page class using postCSS-HTML
    • Disadvantages: It is not meaningful to use the new GULP. Compile every change, very slowly;

Take CSS-loader as an example to enable CSS-modules, refer to the following articles:

  • Ruan Yifeng CSS Modules usage tutorial
  • CSS Modules Basic usage
  • Talk about CSS Modules and the use of CSS Modules in vue.js
  • CSS names: BEM, Scoped CSS, CSS Modules and CSS-in-js
  • Vue CLI CSS configuration
  • CSS – lot of loader
  • CSS – lot of modules
  • CSS Modules are used in TypeScript
module.exports = {
  / /... Omit other configurations
  css: {
    // Whether to use CSS ExtractTextPlugin
    extract: false.// Enable CSS source maps?
    sourceMap: false.// CSS default configuration item
    loaderOptions: {
      css: {
        // These properties are valid:
        // object { url? , import? , modules? , sourceMap? , importLoaders? , localsConvention? , onlyLocals? , esModule? }
        modules: {
          // These properties are valid:
          // object { auto? , mode? , exportGlobals? , localIdentName? , localIdentRegExp? , context? , hashPrefix? , getLocalIdent? }
          exportGlobals: true.localIdentName: '[path][name]__[local]--[hash:base64:5]'
        },
        localsConvention: 'asIs' // asIs camelCase camelCaseOnly dashes dashesOnly}},// Enable CSS modules for all CSS/pre-processor files.
    requireModuleExtension: true}},Copy the code

Use position: fixed with caution

In the sub-project this location will appear problems, basically appear in the modal box and drawer positioning, should try to avoid using, there is a relative to the browser window positioning requirements, you can use position: sticky, but there will be compatibility problems (IE does not support). If the location uses bottom and right, this is not a problem. Alternatively, positions can be written as dynamically bound styles:

<div :style="{ top: isQiankun ? '10px' : '0'}">
Copy the code

Events bound to body, document, etc., should be cleared during the unmount cycle

Js sandbox hijacked only window. The addEventListener, the use of the document. The body. The addEventListener or document. The body. Add the onClick event will not be remoed sandbox affect other pages, Clear it in an unmount cycle

Uncaught Error Application ‘XXX’ died in status LOADING_SOURCE_CODE: [qiankun] You need to export lifecycle functions in xxx entry

It is usually the wrong packaging posture, possible reasons: not packaged into UMD format; The required JS file is packaged as a whole but not loaded and needs to be packaged separately using runtimeChunk

Now refresh the page error, container can not be found

Solution 1: Register and start qiankun during component Mounted cycles

Solution 2: After new Vue(), wait for the DOM to load and then register and start Qiankun

const vueApp = new Vue({
  router,
  store,
  render: h= > h(App)
}).$mount("#app");
vueApp.$nextTick(() = > {
  // Register and launch Qiankun here
})
Copy the code

The history mode can be used for both the primary and sub-application routes

Because the history mode of vue-Router is fully matched, if the current sub-application is embedded by qiankun, all the routes of the master application except for http://ip+port/ should be added before the sub-application’s first-level routes, that is, the initial sub-application in the master application is the activeRule defined.

router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/templateConfig' : '/'.mode: 'history'.routes: [{...}]})Copy the code

In history mode, the route configuration of the primary and sub-applications failed

If the vue-router of both the primary and sub-applications is in history mode, that is, all routes match

  • The path attribute of the route information in the primary application needs to be changed to ‘index/edit*’, i.e. fuzzy full match, and the sub-application’s route needs to be changed to ‘index/ Edit /’ (as mentioned above). Otherwise, if the primary application fails to match the current page after the sub-application changes the route, it will jump back to the login page and adjust to 404.
  • Child application route information should not have ” or ‘*’ nulls. Otherwise, after the primary application (from the embedded page of the sub-application) redirects to another page, the route matching rule of the sub-application is triggered and the sub-application is redirected to the login page of the sub-application. As a result, the route redirecting of the primary application fails.

Jump from one subproject to another

Inside the subproject how to jump to another component/main project page, or write directly in the router. Push/router. Replace are not enough, the reason is the router is a subproject of routing, all the jump will be based on the base of sub-projects. Writing links can jump to the past, but will refresh the page, the user experience is not good.

The solution is also relatively simple. When the child project is registered, the main project route instance object is passed, and the child project is mounted to the global, using the parent project router jump can be done.

However, there is a drop is not perfect, so that only through JS to jump, jump link can not use the browser’s own right menu

Image resource error 404

It’s best to change to an absolute path

<img src="./img/logo.jpg">
<!-- 改为 -->
<img src="/img/logo.jpg">
Copy the code

Or configure the proxy for nginx static files in the main application (there is no background nginx configuration here, so take the proxyTable proxy that comes with WebPack as an example)

if (item === '/index/config/template/edit/static') { // Login page img
    proxyObj[item] = {
      target: 'http://localhost:8081'.ws: false.changeOrigin: true.pathRewrite: { '^/index/config/template/edit/static': '/static'}}}else if (item === '/static/home') { / img/front page
    proxyObj[item] = {
      target: 'http://localhost:8081'.ws: false.changeOrigin: true.pathRewrite: { '^/static/home': '/static/home'}}}Copy the code

If the js file of the child application is too large, the child application will block when loading the child application manually

If you are manually loading the child application, loadMicroApp(), it is recommended to preload the resource, prefetchApps(), when the page is initialized. Avoid request pending times that are too long and block loading

Ts project and JS project file loading problems

Since the main project is TS, ts files are loaded by default. But the subproject is JS. So when introducing js files into subprojects, make sure they have a clear suffix, for example

// Error Unknown custom element: <widget> - did you register the component correctly? For recursive components, make sure to provide the "name" option. import {widgetInRecord as widget} from '@ / views/sheetConfig/fieldConfig/widget/widgets' add suffix is not an error / / import {widgetInRecord as widget} the from '@/views/sheetConfig/fieldConfig/widget/widget.js'Copy the code

Load the same sub-app with different initialization data within a page (e.g. list on the left and detail on the right)

Reloading problem, data communication problem, request response problem

Copy the code

Data communication between main project and subproject

There should not be too much data dependence between projects, after all, projects should be run independently. Communication operations need to determine whether the Qiankun mode is compatible.

Pass Vuex from the parent project through props, which works well if the child project is a VUE technology stack. If the child project is jQuery/react/angular, it will not be able to monitor data changes very well.

Qiakun provides a global GlobalState to share data. After the master project is initialized, subprojects can listen for changes to this data and can also commit this data.

// The main project is initialized
import { initGlobalState } from 'qiankun';

const actions = initGlobalState(state);

// Main project project monitor and modify
actions.onGlobalStateChange((state, prev) = > {
  // state: state after the change; Prev Status before change
  console.log(state, prev);
});

actions.setGlobalState(state);

// Subprojects listen and modify
export function mount(props) {
  props.onGlobalStateChange((state, prev) = > {
    // state: state after the change; Prev Status before change
    console.log(state, prev);
  });
  props.setGlobalState(state);
}
Copy the code

Vue subproject memory leak problem

This problem is very difficult to find in the Issue area of Qiankun: github.com/umijs/qiank… I’ll skip the screening process. The solution is simple.

Empty the DOM when the subproject is destroyed:

export async function unmount() {
  instance.$destroy();
+ instance.$el.innerHTML = ""; // Add this line of code
  instance = null;
  router = null;
}
Copy the code

However, switching back and forth between subprojects does not increase memory. That is, even if the memory occupied by the subproject is not freed when the subproject is unloaded, the memory will be reused the next time it is loaded. Will the subproject load faster? (Not verified yet)

Safety and performance issues

Qiankun records the JS/CSS file contents of each subproject in a global variable. If there are too many subprojects or the file size is too large, it may take up too much memory and cause the page to lag.

In addition, qiankun runs the JS of the sub-project, not through script tags, but through eval functions, whose security and performance are controversial: Introduction to EVAL in MDN

No Bug ~ wish you