One, foreword

“There are a thousand Hamlets in the eyes of a thousand people.” This article is only the author’s thinking and comprehension of the micro front end during this period of time. There are many ways to realize the micro front end, but the micro front end is not perfect. The best practice is what works for you and the team. Hopefully, this article has helped you save a little time on your micro front end learning journey. We also welcome your comments and feedback. I hope we can make progress together. Come on

Because this article is longer (PS: I am lazy score), catalog 1 to catalog 7 partial theoretical knowledge, catalog 7 after the practical operation and thinking, we can see the appropriate jump

What is the micro front end?

  • A microfront is not a specific technology, but an idea. ThoughtWorks Technology Radar was proposed in 2016 to convert a single Web application into multiple small front-end applications by drawing on the architecture model of back-end microservices.
  • So the micro front end is not a specific library, not a specific framework, not a specific tool, but an ideal and architectural pattern.
  • The three core principles of the micro front end are: independent operation, independent deployment, independent development so the best person to meet these is “iframe”!!

What problems can the micro front end solve for us?

For example, an application that lasts for many years and goes through several iterations of business update will encounter the following problems when the project reaches a certain level

  1. How to manage the continuous stacking and cross-referencing of business modules and business coupling?
  2. Old technology, old code dare not move, new technology, new architecture want to use?
  3. Ten thousand years of technical debt? How can the old framework libraries be upgraded smoothly while keeping up with business agility iterations and keeping the code base growing for the better?
  4. How to solve the conflict of parallel development?
  5. The code base continues to swell, and the project code is difficult to maintain. Or start all over again?

Is there an architectural pattern that can break down complexity, improve collaboration, and support flexible scaling? The micro front end is here to stay — the “Friendlier Iframe” takes a giant app and breaks it down into separate micro apps that the user doesn’t know about!

Core principles of the micro front end:

  • Technology stack independent: The master application does not limit the access of sub-applications to the technology stacks, and the selection of technology stacks for each application can be based on business scenarios.
  • Independent development, independent deployment: it can be run in combination or separately.
  • Environment isolation: Separate JavaScript and CSS applications from each other to avoid interaction
  • Message communication: unified communication method, reduce the cost of using communication
  • Dependency reuse: Solves the problem of dependency and common logic requiring repeated maintenance

This means we can take a step-by-step dismantling of Stonehenge applications, to technology upgrades, to architecture attempts, to business takeaways, and so on. Low cost, low risk, bring more possibilities to the project

Is our project suitable for transformation into a micro front-end project model?

To see whether our project meets the requirements of micro-front-end, we can first see whether it can meet the following points.

  • Whether there are clear business boundaries and whether the business is highly centralized.
  • Is the business highly coupled and the project large enough to be broken up?
  • Multiple technology stacks exist in the team and cannot be unified, requiring access to the same master system.
  • The technology is old, expansion difficult and maintenance thankless.
  • Development coordination, deployment and maintenance work, low efficiency, one careless move, the whole game is lost.

Note: there is no pressing need to plug into the microfront end, just an added burden to know what we are using the microfront for.

Second, the selection of micro front-end technology

Comparison of micro front-end implementation schemes

Technical solution describe Technology stack advantages disadvantages Build/deploy separately Build speed SPA experience Project invasiveness Learning costs Communication is difficult
iframe Each microapplication is independently developed and deployed. These applications are embedded into the parent application system by means of IFrame unlimited 1. Technology stack is independent, and sub-applications are built and deployed independently

2. Simple implementation, sandbox between sub-applications, natural isolation, mutual influence
Poor experience, routes cannot be remembered, page adaptation is difficult, monitoring is impossible, dependencies cannot be reused, compatibility is limited, resource overhead is huge, and communication is difficult support normal Does not support high low high
Nginx route forwarding Different paths are mapped to different applications through Nginx configuration unlimited Simple, fast, and easy to configure Page refresh is triggered when switching applications, making communication difficult support normal Does not support normal low high
Npm integration Microapplications are separated into packages, distributed in Npm, used by the parent application in a dependent manner, and integrated into the project at build time unlimited 1. Applications in the compilation stage do not need to be loaded during the project running stage, and the experience is smooth

2. Low cost of development and access, easy to understand
1. Impact on compilation speed and packaged volume of the main application

2. Dynamic delivery is not supported. After the NPM package is updated, the package needs to be updated again and the main application needs to be released and deployed again
Does not support slow support high high normal
Universal center routing pedestal type Microapplications can use different technology stacks; Microapplications are completely independent of each other. The unified management is carried out by the base project, which is completed according to the registration, mounting and unmounting of DOM nodes. unlimited Sub-applications are built independently, with good user experience, strong controllability and adaptability to rapid iteration The cost of learning and implementation is high and requires additional processing of dependency reuse support normal support high high normal
Base type for specific center routing Use the same technology stack between microapplication lines of business; Pedestal engineering and microapplications can be developed and deployed separately; Microapplications have the ability to reuse the common infrastructure of pedestal engineering. Unified technology stack Sub-applications are built independently, with good user experience, strong controllability and adaptability to rapid iteration The cost of learning and implementation is high and requires additional processing of dependency reuse support normal support high high normal
Webpack5 module federation The webpack5 module federally decenters and detach from the base mode. Each application is deployed separately on its own server, and each application can reference and be referenced by other applications Unified technology stack Based on Webpack5, there is no need to introduce a new framework, the cost of learning is low, and it is as convenient as introducing a third-party library. The resources of each application can be shared with each other, and the relationship between applications is loosely coupled and parallel The need to upgrade the Webpack5 technology stack must be consistent and upgrading old projects is difficult support normal support low low normal

For students with difficulty in choosing, the following latitude can be referred to for the selection of scheme technology

Refer to the latitude Whether future iterations are supported
The stability of Whether the program has experienced the test of the community, there are more mature cases, while maintaining a high level of activity
Can expand sex Support custom development, provide high scalability, while the cost is within an acceptable range
controllability After a problem occurs, troubleshoot the problem in the first time and deal with the problem with the fastest response speed. Whether the solution to repair depends on the external environment

Market framework comparison:

  • Magic-microservices is a lightweight micro-front-end factory function based on Web Components.
  • Icestark is a micro front-end solution for large systems
  • Single-spa is a JavaScript micro-front-end framework that aggregates multiple single-page applications into a single application
  • Manufactured by Ant Financial, based on single-SPA packaging on the basis of single-SPA
  • Module Federation, produced by EMP YY and based on Webpack5, not only has the ability of micro front end, but also realizes the ability of cross-application state sharing and cross-framework component invocation
  • MicroApp is a lightweight, efficient and powerful micro-front-end framework based on the idea of WebComponent

Based on the comparison of the above schemes, we determined to adopt the development scheme of qiankun specific center routing pedestal for the following reasons:

  • Ensure that the technology stack unified Vue, micro applications are completely independent, do not affect each other.
  • Friendly “micro front end solution”, stack-agnoist simple access, as simple as iframe
  • The transformation cost is low, and the invasion of existing engineering and the cost of business line migration are also low.
  • And the original development model is basically no different, the developer learning cost is lower.
  • The micro front end of Qiankun has accumulated 3 years of usage scenarios and Issue problem solving, and the community is also more active, so it is easier to save themselves on the road of stepping on pits

Three, you need to be clear

Microfront-end is not a panacea. Without proper treatment, all codebase will end up in a mountain of shit.

  • Qiankun is not a complete micro front end solution!

  • Qiankun is not a complete micro front end solution!!

  • Qiankun is not a complete micro front end solution!!

1. Runtime container for the micro front end

  • The part that Qiankun is helping you solve is actually the run-time container for the microfront end, which is part of the whole microfront engineering process
  • From this perspective qiankun is not a complete front-end solutions, but the micro front-end runtime containers of a complete solution, when you use the qiankun, you can solve almost all the micro front problem of container, but some more involved and platform construction, requires us to think and processing.
  • As a library, Qiankun can not solve the problems of version control, configuration delivery, monitoring and release, security testing, etc. We have to choose our own solutions according to the specific situation

2. Migration costs

  • For the old project access, it is difficult to achieve zero-cost migration, in the development of time to reserve enough step on the pit, magic change code time. If it’s a mountain of shit that’s been stacked for years and needs to be fixed with all sorts of weird compatibility issues due to non-standard coding, you might even wonder, “Is a micro front end really necessary?”

3. Selection of technology stack

  • The core of the micro front end is not the coexistence of multiple technologies, but the decomposition of complexity, the improvement of collaboration efficiency, and the support of flexible expansion. It can turn “a bunch of complicated things” into “a simple thing”, but it is not mindless use. As the Cantonese saying goes, “multiple censers and multiple ghosts”, each additional technology stack will increase: Maintenance costs, compatibility costs, resource overhead costs, these are all invisible drag on productivity.
  • Between base and micro application, strongly recommend using the same technology stack, the same technology stack can realize the public library, UI library, such as pulling away, reduce resource costs, improve loading speed, the most important thing is: “the best way to reduce conflict is” unity, by constraining technology stack can reduce the conflicts between the project as possible, reduce the workload and maintenance costs.

4. Initial attempt of micro front-end

  • The best time to access the micro front end is for the projects that have just started or are not particularly important. On the one hand, the projects have the engineering ability to be compatible with the micro front end; on the other hand, the cost of using the micro front end solution is the lowest and there is no need to change too much code
  • For the old project access advice is still from the edge of the simple template, gradually decomposed.

7. It is standardization that increases productivity

  • Messy projects drag productivity down, and messy microfronts exacerbate internal friction, so only standardization can improve productivity.
  • Solving the access problem of the micro front end is the easiest, but after the micro front end is connected: engineering, application monitoring, application specification, application management is the difficult part of the micro front end. If you want to simply embed an application, I recommend you to use “iframe”.

9. Qiankun does not support Vite!!

  • 🚀 Link Github will consider supporting Vite in the future
  • It is not recommended to try to change the current Qiankun. The transformation cost of Vite is really too high. Although Webpack is slower than Vite, the application content after splitting is already small, so it will not have too much drag on the project.

10. It wasn’t hard

  • In fact, we do not need to worry about the study of Qiankun, as if it is very difficult to listen to the micro front end. Because Qiankun is really simple, there are no 10 apis. Let’s step into the world of Qiankun
  • 🚀 Link Qiankun website

Four, micro application split rules

The disassembly and combination of microapplications: the disassembly is the system complexity, and the combination is the system reuse degree core principle: high cohesion, low coupling

There are no specific rules for dismantling microapplications, but the following rules should give you some basis for dismantling systems.

  1. “Try to reduce communication and dependence on each other”. The cost of communication interaction, link jump and other operations of the micro front end is actually very high, so try to be “completely independent and independent” when splitting.

  2. Micro application of the separation of the time to avoid “blind meticulous separation”, excessive separation will lead to “do very cool, but no use of the dilemma”, micro application of the separation is not a step in place, we should step by step according to the actual situation. If you don’t know how thin you should be at first, start with coarse-grained divisions and gradually break them down as your requirements evolve.

    • For example: now there is a after-sales management system, we split it into: customer service management, inventory management, logistics management, future customer service management demand function continues to be huge and then disassembled into: intelligent customer service, telephone customer service, online customer service. And these customer service, and can be embedded in the supplier management center, commodity management center and other projects.
  3. When splitting, we should try to consider the future scenarios: gradual technology stack migration, front-end application aggregation, multi-system business reuse, how to do business decoupling and code reuse.

  4. The applications should be decoupled as much as possible, and the child applications should do the work of the child applications.

    • For example, some identifiers of the child application, such as route prefix, application name, root node container name, dependency library usage
    • You need to determine which child applications should be maintained and which parent applications should be maintained. If all resources are used by the parent application, serious coupling between applications may occur.

You are advised to split nodes by service domain

  1. Keep core business independent and decouple irrelevant sub-business. The development of services does not affect each other. Microapplications can be disassembled, packaged and deployed separately.
  2. 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. The criterion for judging whether the business connection is close is to see whether the micro-application has frequent communication requirements with other micro-applications.
  3. 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.
  4. Analyzing platform differences, platform differences can be divided according to platform characteristics
  5. Analyze the page structure, if the structure is clear, can be divided according to the structure
  6. Analyze the product business and merge the product logic coupled functions together

5. Introducing Qiankun – Registering microapplications in the main application

Select base mode?

  1. Common hub routing pedestal: main application with only common functions (menu bar, login, exit…) Does not contain any business logic
  2. Specific hub routing pedestal: a project with business code as the pedestal, with all new functionality introduced as sub-applications

In the following case, Vue technology stack is used as the application technology stack. It is suggested to unify the technology stack among applications to reduce maintenance, getting started and learning costs. The more different the technology and the version of the library, the more processing needs to be done.

Qiankun registered the way of microapplication:

💫 Automatic mode: Use registerMicroApps + start to load microapps with route changes

  • Once the url of the browser changed after the micro-application information was registered, it would automatically trigger the matching of Qiankun
  1. First load application, create child application instance, render.
  2. Cutting to other child applications and then cutting back creates a new child instance and renders it.
  3. The previous sub-application instance Qiankun was removed, even if you didn’t manually destroy the instance.
  4. In this mode, you must manually destroy the instance in the unmount hook exposed by the child application, otherwise it will cause a memory leak.
  • ActiveRule – string | (location: the location) = > Boolean | Array < string | (location: the location) = > Boolean > will be chosen, the application of micro active rules.

  • You can directly configure strings or arrays of strings, such as activeRule: ‘/app1’ or activeRule: [‘/ App1 ‘, ‘/app2’]. If the string is set to a string, it matches the prefix of the path in the URL. If the match is successful, the current application will be activated.

  • You can configure an active function function or a group of active functions. The function takes the current location as an argument, and returns true to indicate that the current microapplication is activated. Such as location = > location. The pathname. StartsWith (‘/app1)

  1. Automatic mount: registerMicroApps + start
yarn add qiankun // ps: only the main application is installed
Copy the code
// Main application/SCR /main.js
import { registerMicroApps, start } from 'qiankun';

// 1. Obtain the micro-application configuration
const MICRO_CONFIG = [
  {
    name: 'vue app'.// The application name must be unique
    entry: '//localhost:7100'.// The default loading of the HTML parse JS fetch is dynamic execution (subapplications must support cross-domain)
    container: '#yourContainer'.// Mount a specific container ID
     // 3. Activate subapplications based on route matching
    activeRule: '/yourActiveRule'.props: {
        xxxx: '/' // send to the child application}}]// 2. Register microapplications
registerMicroApps(MICRO_CONFIG)

start() // Start microservices
Copy the code

ActiveRule rule example: here is an example of the official website ~ ctiveRule: ‘/app1’

ActiveRule: ‘/ users / : userId/profile’

ActiveRule: ‘the pathname / # / hash’

ActiveRule: [‘/pathname / # / hash ‘, ‘/ app1]

💪 Manual mode: Manually register microapplications using loadMicroApp

  1. Each child application has a unique instance ID, and the previous instance is reused on reload
  2. Unmount xxxmicroapp.unmount ()

Due to the feature of registerMicroApps, the keep alive of routes will fail. Therefore, loadMicroAp + router.beforeEach is used in this paper to achieve automatic registration.

If the microapplication is not directly associated with the route, you can choose to manually load the microapplication for more flexibility.

Manual mount: loadMicroApps

// Any page can be registered

import { loadMicroApp } from 'qiankun';

// Obtain the application configuration and manually mount it. After mounting, return to the mounted object
this.microApp = loadMicroApp({
    name: 'vue app'.// The application name must be unique
    entry: '//localhost:7100'.// The default loading of the HTML parse JS fetch is dynamic execution (subapplications must support cross-domain)
    container: '#yourContainer'.// Mount a specific container ID
    activeRule: '/yourActiveRule'.// Activate subapplications based on the route
    props: {
        xxxx: '/' // send to the child application}})this.microApp.unmount() // Manually destroy ~
Copy the code

Microapplications mount nodes

Microapplications can be mounted anywhere on a page. Microapplications, microprojects, micropages, microcomponents, anything is possible.

  • There are two common mount scenarios for microapplications

The first: routing page mount, the child application embedded into the use

// SRC /views/ about.vue<template>
  <div class="about">
    <div id="sub-app-container"></div>
  </div>
</template>
Copy the code

Second: mount the root DOM at the same level as the main application. Hide the application and display the current application when switching

// Main App/SCR/app.vue<template>
    <div id="app">
        <! -- Different microapplications -->
        <div v-show="location.hash.startsWith('#/operation')" id="sub-operation-container"></div>
        <div v-show="location.hash.startsWith('#/inventory')" id="sub-inventory-container"></div>
    </div>
</template>
Copy the code

7. Application load analysis flow chart

Simple illustration of how To load microapplications with import-HTmL-entry

Simple process:

  1. Qiankun will use the native fetch method to request entry of micro-application to obtain micro-application resources, and then convert the obtained content into strings through Response. text.
  2. Pass in the HTML stringprocessTplFunction, HTML template parsing, through the regular match in the HTML corresponding javaScript (inline, inline), CSS (inline, inline), code comments, entry, ignore collection and replace, removehtml/head/bodyOther resources remain as they are
  3. Will collect thestylesThe external link URL object fetch the CSS, and the CSS content to<style>Is replaced by the original link tag
  4. Collection of script external chain objects, for asynchronous execution of JavaScript resources will be typedasyncIdentity, will be usedrequestIdleCallbackMethod to delay execution.
  5. Next, an anonymous self-executing function is created to wrap the js string. Finally, eval is used to create an execution context to execute the JS code, and a proxy is passed to change the window pointer, completing JavaScript sandbox isolation. Source location.
  6. Since Qiankun is a JavaScript that executes microapplications by self-executing functions, there is no reference of JavaScript resources in the loaded microapplications, only a symbol of resource replacement being executed.
  7. When everything is ready, execute the JavaScript code of the microapplication and render the microapplication

Eight, micro-application access three steps

Step 1: Modify the entry file of the microapplicationwebpack_public_path

  • insrcDirectory of newpublic-path.js
  • webpackThe defaultpublicPath 为 ""An empty string that loads resources based on the current path. However, when we load microapplication resources in the main application, the resources will be lost, so we need to reset__webpack_public_path__The value of the
// micro-application/SRC /const/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
} 
Copy the code

Step 2: Microapply webPack new configuration

  • Webpack configuration changes PS: What is a UMD module?
const { name } = require('./package.json')

module.exports = {
  devServer: {
      port: 8081.// The parent application configures the microapplication port, which must be consistent with the microapplication port
      disableHostCheck: true.// Turn off host checking so that microapplications can be fetched
      headers: {
          'Access-Control-Allow-Origin': The '*' Since all internal requests in Qiankun are fetch requests for resources, sub-applications must be allowed to cross domains}},configureWebpack: {
      output: {
          library: `${name}-[name]`.// The package name of the micro-application, which is the same as the registered micro-application name in the main application
          libraryTarget: 'umd'.// This is set to umD, meaning it is accessible after AMD or CommonJS require.
          jsonpFunction: `webpackJsonp_${name}` // Webpack is used to asynchronously load chunk's JSONP function.}}}Copy the code

Step 3: Add a microapplication lifecycle

Microapplications need to add bootstrap, mount, and unmount lifecycle hooks to their entry files, which can be called by the host application when appropriate.

  • Main.js registers microapps to add judgment so that the child app can run independently even without the parent app
  • The qiankun lifecycle functions must all be Promises. Using Async will return a Promise object
// micro applications/SCR /main.js

import './public-path.js'
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

let instance = null

// 1. Wrap the registered methods in functions for subsequent master applications and independent run calls
function render(props = {}) {
  const { container } = props
  instance = new Vue({
    router,
    store,
    render: h= > h(App),
  }).$mount(container ? container.querySelector('#app-micro') : '#app-micro')}// Determine whether to run independently in a universe environment or not
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

// 2. Export lifecycle
/** * 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('[vue] vue app 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('[vue] props from main framework', 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.$el.innerHTML = ' '
  instance = null
}

/** * Optional lifecycle hooks that only work when microapplications are loaded using loadMicroApp */
export async function update(props) {
  console.log('update props', props)
}
Copy the code

After these steps, qiankun parent application and micro application were connected. When the parent application finishes loading the micro-application, the micro-application will be inserted into the HMTL of the parent application in accordance with the corresponding parsing rules.

9. Preload microapplications

Static resources such as HTML, JS, and CSS of sub-applications are requested in advance. When switching sub-applications, these static resources can be directly read from the cache to speed up the rendering of sub-applications.

  1. RegisterMicroApps mode instartMethod To configure a preloaded application

    import { registerMicroApps, start } from 'qiankun';
    
    registerMicroApps([...AppsConfig])
    
    start({ prefetch: "all" }) // Configure preloading
    Copy the code
    • prefetch – boolean | ‘all’ | string[] | (( apps: RegistrableApp[] ) => { criticalAppNames: string[]; MinorAppsName: string[]}) – This parameter is optional. The default value is true.

      If set to true, static resources for other microapplications will be preloaded after the first microapplication is mounted

      If the parameter is set to all, the active application starts preloading all static microapplication resources after start

      If string[] is set to String, the array starts to load the resources in the array after the first micro-application mounted

      If this parameter is set to function, you can fully customize the resource loading timing of applications (first-screen applications and second-screen applications).

  2. LoadMicroApps mode

    import { prefetchApps } from 'qiankun';
    
    export const MICRO_PREFETCH_APPS = [
        { name: 'vue-child'.entry: '//localhost:7101/' },
        { name: 'vue-app'.entry: '//localhost:8081/' }
    ]
    
    prefetchApps(MICRO_PREFETCH_APPS)
    Copy the code
    • The mode used by the author is loadMicroApps mode. In order to maintain portability in the future, modify it: Add isPreload field to maintain whether to enable preloading, so that the information about microapps is maintained in this JS file, avoiding scattershot modification.
    / / base/SRC/const/micro/application - list. Js
    
    export const MICRO_CONFIG =  [
        {
            name: 'you app name'.// The application name
            entry: '//localhost:7286/'.// The default loading of the HTML parse JS fetch is dynamic execution (subapplications must support cross-domain)
            container: '#yuo-container-container'./ / container id
            activeRule: '/your-prefix'.// Activate the path according to the route
            isPreload: true./ /!!!!! Enable preloading!!}]Copy the code
    import { prefetchApps } from 'qiankun';
    import { MICRO_CONFIG } from '@/const/micro/application-list.js';
    
    // Get the configured isPreload field and generate the corresponding format for loading
    const MICRO_PREFETCH_APPS = MICRO_CONFIG.reduce(
        (total, { isPreload, name, entry }) = > (isPreload ? [...total, { name, entry }] : total),
        []
    )
    // Preload the application
    prefetchApps(MICRO_PREFETCH_APPS)
    Copy the code

X. Routing mode selection and transformation

👉 How should we choose the route?

The best routing mode is that the primary application and sub-applications work in the same mode, which reduces the compatibility between different modes

This document selects the parent-child route hash mode

Main mode Subpatterns. recommended Access to the impact The solution note
hash hash It is highly recommended There is no
hash history Is not recommended There are history.pushState Large renovation cost
history history It is highly recommended There is no
history hash recommended There is no

PS: The combination of each mode cannot be completed by adding route prefixes, route configuration base Settings, and different mode activeRule rules are different

⚒ Route reconstruction

The micro-application route prefix is added

The new route prefix is not required for microapplications, but is added to isolate the microapplication from other applications on the URL, and to enable the activeRule method to identify and activate the application when accessing the old application.

Parent application routing table

[
    Router.js: If we want to match any path, we can use the wildcard (*) :
    {
        path: '/your-prefix'.name: 'Home'.component: Home
    },
     // The specific page pocket will match any path starting with '/your-prefix'
     // e.g. /your-prefix/404, /your-prefix/no-permission....
    {
        path: '/your-prefix/*'.name: 'Home'.component: Home
    }
]
Copy the code

PS: Sub – application route switching. Since both applications and routes are registered and destroyed through URLS, the page will be destroyed if the sub – application route redirect address fails to match the parent application route address. Therefore, you need to pay attention to route matching or add route pockets.

Sub-application hash mode

// The hash mode cannot use base, only the prefix can be changed
new VueRouter({
    mode: 'hash'.routes: [{// Add route prefix judgment
            path: `The ${window.__POWERED_BY_QIANKUN__ ? 'your-prefix' : ' '}/login`.component: _import('login/index.vue')}]})Copy the code

Subapplies the history mode

new VueRouter({
    mode: 'history'./ ** If the child application is in history mode, just set the router base instead of hash
    base: window.__POWERED_BY_QIANKUN__ ? 'your-prefix' : null.routes: [{path: '/login'.component: _import('login/index.vue')}]})Copy the code

11. Route access transformation of 📝 old project

However, since the author is accessing the old project and has hash routing mode to smoothly access, adding three yuan one by one will change the routing table too much. In order to reduce the impact on the access of the old project, only the following three modifications are made (PS: because of laziness).

1. Hash routing mode: Format routing table objects, micro-routing table paths, aliases, and redirection add prefixes to distinguish applications

  • Here we use recursive functions to dynamically add prefix, path, redirect and alias to the route. These three states need to be handled dynamically
  1. Routing table data
const routes = [
    {
        path: '/home'.name: 'home'.component: Home
    },
    {
        path: '/about'.name: 'about'.redirect: 'home'.component: () = > import('.. /views/About.vue')}, {path: '/about'.name: 'about'.alias: '/user/about'.component: () = > import('.. /views/About.vue')}]Copy the code
    1. Formatted routing method
let SUN_ROUTER_PATH_LIST = [] // All formatted routes are recorded

// 1. Format router parameters recursively
export function formatRouterParams(parameter) {
    // Return as-is if the content is not qiankun environment
    if (!window.__POWERED_BY_QIANKUN__) {
        return parameter.data
    }
    
    // Recursive functions: data: data source, params: parameter to be replaced (array, string), value Value to be replaced (function), deepKey: parameter name to determine whether recursion is required
    const recursionData = ({ data, params, value, deepKey }) = > {
        return data.reduce((total, item) = > {
            item = formatData({ item, params, value })
            // Determine if the recursion needs to continue
            if (deepKey && item[deepKey] && Array.isArray(item[deepKey])) {
                item[deepKey] = recursionData({
                    data: item[deepKey],
                    params,
                    value,
                    deepKey
                })
            }
            return [...total, item]
        }, [])
    }
    return recursionData(parameter)
}

// 2. Format the route data and add the prefix
export function formatData({ item, params, value }) {
    if(! item)return
    
    // If params is array: [path,...more] traversal adds prefix
    if (Array.isArray(params)) {
        params.forEach(key= > {
            if (Object.prototype.hasOwnProperty.call(item, key)) {
                item[key] = geRouterValue(value, item[key])
            }
        })
    } else if (params) {
        item[params] = geRouterValue(value, item[params])
    }

    // Record the formatted routing path
    SUN_ROUTER_PATH_LIST.push(item)
    return item
}

// 3. Check whether value is a function or a value. If it is a function, call the function and return the corresponding value
export function geRouterValue(value, key) {
    return typeof value === 'function' ? value(key) : value
}
Copy the code
  1. A recursive method is called to uniformly replace the micro-application routing table
const BASE_ROUTER_PATH = 'your-prefix'

const router = new VueRouter({
    mode: 'hash'.// Call method to format routing table parameters!!
    routes: formatRouterParams({
        data: route,
        deepKey: 'children'.params: ['path'.'redirect'.'alias'].value: value= > { // value Format method
            if (window.__POWERED_BY_QIANKUN__ && typeof value === 'string') {
                 const path = value[0= = ='/' ? value : ` /${value}`
                 return BASE_ROUTER_PATH + path
            }
            return value
        }
    })
})
Copy the code

– route table is returned after traversal, adding “your-prefix” prefix

[{"path": "your-prefix/home"."name": "home"."component": "Home"
    },
    {
        "path": "your-prefix/about"."name": "about"."redirect": "your-prefix/home"."component": ""
    },
    {
        "path": "your-prefix/about"."name": "about"."alias": "your-prefix/user/about"."component": ""}]Copy the code

2. router.beforeEach

The check jump function is called to determine whether the prefix needs to be added

// Route global guard
// ps: Internally check whether the redirected route is irrelevant to the current sub-application
router.beforeEach((to, from, next) => {
    checkLink(to, next, () => {
        next()
    })
})
Copy the code
  • To put it simply: Spliced next only if it is in the Qiankun environment and is not a path to jump to other micro applications, and the jump is not a path with formatted prefix, and the current spliced address is consistent with the formatted routing address
// the LINK_MICRO_APP_LIST, SUN_ROUTER_PATH_LIST variables are recorded above

const { name } = require('.. /.. /package.json')
export const BASE_ROUTER_PATH = ` /${name}` // Here the author uses package.json name as "your-prefix" for later maintenance

// Check whether the prefix needs to be added
export function checkLink(to, next, callback) {
    // Check whether there is a Qinakun environment
    const IS_HAVE_QIANKUN = window.__POWERED_BY_QIANKUN__
    // Whether to jump to other microapplications
    const IS_JUMP_TO_MICRO_APP = Object.values(LINK_MICRO_APP_LIST).includes(to.path)
    // Whether to jump to the root path
    const IS_BASE_PATH_SYMBOL = to.path === '/'
    // Check whether the root paths are consistent
    const IS_HAVE_BASE_ROUTER_PATH = getBasePath(to.path, '/') === getBasePath(BASE_ROUTER_PATH, '/')
    // Determine whether to add a dynamic prefix to the route
    constIS_ADD_PREFIX = IS_HAVE_QIANKUN && ! IS_JUMP_TO_MICRO_APP && ! IS_HAVE_BASE_ROUTER_PATHif (IS_ADD_PREFIX || IS_BASE_PATH_SYMBOL) {
        const path = `${BASE_ROUTER_PATH}${to.path}`
        // Check whether the current IP address is consistent with the current formatted route
        if (SUN_ROUTER_PATH_LIST.some(e= > [e.path, e.redirect, e.alias].includes(path))) {
            next({ path })
        }
    }
    // Execute the callback function
    callback && callback()
}

/ / get the basis of the current path Such as: getBasePath ('/user/age/XXX ', '/') = > '/ user'
export function getBasePath(path, prefix = ' ') {
    if(! path)return
    const pathArray = String(path).split('/').filter(item= > item)
    const basePath = prefix + pathArray[0]
    return basePath
}
Copy the code

3. Rewrite router. Push OR router

// The next() address is changed, and the next() address is changed
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
    return originalPush.call(this, location).catch(err= > err)
}
Copy the code

Router. BefroeEach (router. BefroeEach

const originalPush = VueRouter.prototype.push

VueRouter.prototype.push = function push(location) {
    const IS_JUMP_TO_MICRO_APP = Object.values(LINK_MICRO_APP_LIST).includes(location.path)
    const IS_HAVE_QIANKUN = window.__POWERED_BY_QIANKUN__
    const IS_BASE_PATH_SYMBOL = location.path === '/'
    const IS_HAVE_BASE_ROUTER_PATH = getBasePath(location.path, '/') === BASE_ROUTER_PATH
    constIS_ADD_PREFIX = IS_HAVE_QIANKUN && ! IS_JUMP_TO_MICRO_APP && ! IS_BASE_PATH_SYMBOL && ! IS_HAVE_BASE_ROUTER_PATHif (IS_ADD_PREFIX) {
        location.path = `${BASE_ROUTER_PATH}${location.path}`
    }
    return originalPush.call(this, location)
}
Copy the code

4. Record route hops

Hop mode: Route Hop is the same as normal

  • Ps: If the parent application is in History mode and the child application is in hash mode, the child application needs to handle the URL redirection switch history.pushState

Jump to other microapplications

  1. Because the type of separation between our application, so jump external application of routing is separate, if write literal fixed death risk in the project is too big, if a little change in external application, need to change the path in the project will be a nightmare, so we use unified in their respective micro maintain a list of constants to processing records application between the jump, This facilitates unified global management. Here is only my humble opinion, if there are better suggestions, please publish more.
  2. PS: In fact, the constant list of jumps can also be maintained in the base application, but the best choice is to have an operation and maintenance platform to maintain the jump relationship between applications
// micro application/SCR /const/link-micro-app-list
export const LINK_MICRO_APP_LIST = {
    CHILD_VUE: '/child/vue'.// Vue micro application address
    CHILD_REACT: '/child/react'.// react micro applies the address
    USER_INFO: '/user/info' // Jump to the address of the parent application user information page
}
Copy the code

Usage Scenarios:

// Micro applications /router.js

// 1. Determine whether to switch to other applications
const IS_JUMP_TO_MICRO_APP = Object.values(LINK_MICRO_APP_LIST).includes(to.path)

// 2. Obtain the route from the route list address when defining the route
let routes = [
    {
        path: '/about'.name: 'about'.// All alias redirects to other applications should be managed here!!
        redirect: LINK_MICRO_APP_LIST['CHILD_VUE'].component: () = > import('.. /views/About.vue')}]// router.beforeEach
router.beforeEach((to, from, next) = > {
    if (IS_JUMP_TO_MICRO_APP) {
        next(false) // Forbid jumping to other ying yon
    }
  // ... more code
})
Copy the code

How to keep alive between micro applications and routing

  • Why does switching routes cause application reloading in registerMicroApps mode?
    • See “Introducing Qiankun — Registering microapplications in the Main Application” above for more details.
    • When the URL is changed, the route switchover causes the application to be uninstalled and loaded
      • If “A” was A long tail and “B” was A long tail, “A” would be A long tail. If “B” was A long tail, “A” would be A long tail
    • If a child application is mounted on an internal route, route redirects will also trigger an application reload
    • In order to keep the application instance from being loaded, we need to manually control the application registration and destruction
  • Solution a:loadMicroApp
    • Advantages: Multiple microapplications can be mounted simultaneously on a single page
    • Disadvantages: Applications cannot be mounted based on routing matching rules
    • Application scenario: When more than two sub-applications need to be mounted on a page and do not need route matching to mount the sub-applications.
    • PS: When closing the TAB page in the base, you need to manually call the UNmount hook of app to destroy the application. Otherwise, it will still be the previous instance when entering the new TAB again

LoadMicroApp not being able to mount apps according to routing rules is not an issue for Qiankun, it is our issue

Use of the router.afterEachKeep Alive is a solution to loadMicroApp. The idea is to determine the address of the route guard and activate the application if it meets the activation rule

1. The active application router router guard

// Main application/SRC /router/index.js
// 1. The global post-hook calls the microapplication loading method
// In the routing page, the container that is a micro application has mounted the micro application. Sometimes the micro application will be replaced due to the route switch of the author's system, so we use this method to solve the problem
router.afterEach(to= > {
    setTimeout(() = > { // setTimeout is a type of macro task
        microApplicationLoading(to.path) // Pass in the current jump path})})Copy the code

2. Check whether microApplicationLoading is microApplicationLoading

  • Application of table
// **src/const/micro/application-list.js** 
export const microApplicationList [
    {
        name: 'you app name'.// The application name
        entry: '//localhost:7286/'.// The default loading of the HTML parse JS fetch is dynamic execution (subapplications must support cross-domain)
        container: '#yuo-container-container'./ / container id
        activeRule: '/your-prefix'.// Activate the path according to the route
        **isPreload: true./ /!!!!! Enable preloading!!
        isRouteStart: true.// Whether routing is required to start **
        props: { // Deliver the resources of the sub-application
            router: router,
            store: store,
            parentEventHub: parentEventHub
        }
    }
]
Copy the code
  • Load the microapplication method
// SRC /const/micro/qianun-utils.js
// Load the microapplication method
import { loadMicroApp } from 'qiankun'

export async function microApplicationLoading(path) {
    // 1. Load the current application configuration based on the routing address
    let currentActiveMicroConfig = await store.dispatch('d2admin/micro/GET_FIND_MICRO_CONFIG', path)
		
    // 2. Get the list of cached microapplications from vuex
    const microApplicationList = store.getters['d2admin/micro/microApplicationList']

    // 3. If no application configuration is matched, it indicates that the jump is not a micro-application or micro-application configuration does not require the attribute of route startup
    if(! currentActiveMicroConfig || ! currentActiveMicroConfig.isRouteStart) {return
    }

    // 4. Obtain the cached application based on the application configuration
    const cacheMicro = microApplicationList.get(currentActiveMicroConfig.activeRule)
	
    // 5. Check whether the current mount contains content
    const containerNode = getContainerNode(currentActiveMicroConfig.container)
    constisNoTNodeContents = containerNode ! = = -1 && !containerNode
	
    // 6. If there is no DOM node or no cache application configuration, register it
    if(isNoTNodeContents || ! cacheMicro) {// If there is a cache application configuration, but the container does not mount the application, unmount the cache application before registering the micro-application
        if (cacheMicro) {
            cacheMicro.unmount()
            cacheMicro.unmountPromise.then(() = > {
                loadRouterMicroApp(currentActiveMicroConfig)
            })
            return
        }

        // Load the application
        loadRouterMicroApp(currentActiveMicroConfig)
    }
}

// Load the microapplication
export function loadRouterMicroApp(currentApp) {
    const micro = loadMicroApp(currentApp)
    micro.mountPromise.then(() = > {
        // Set the vuex microapp list
        store.dispatch('d2admin/micro/SET_MICRO_APPLICATION_LIST', {
            key: currentApp.activeRule,
            value: micro
        })
    })
}

// Get the container node
export function getContainerNode(container) {
    const containerNode = container && document.querySelector(container)
    
    if (containerNode) {
        return containerNode.childNodes.length
    }
    
    return -1
}
Copy the code

The vuEX method records the registered application object

/ / the main application/SRC/store/modules/d2admin/modules/micro js
import MICRO_CONFIG from '@/const/micro/application-list.js '

export default {
    state: {
        microApplicationList: new Map([]) // List of registered microapplications
    },
    getters: {
        microApplicationList(state) {
            return state.microApplicationList
        }
    },
    actions: {
        // Set the list of microapplications
        SET_MICRO_APPLICATION_LIST({ state, dispatch }, { key, value }) {
            state.microApplicationList.set(key, value)
        },
        // Obtain the microapplication configuration from the path
        GET_FIND_MICRO_CONFIG({ state }, path) {
            return MICRO_CONFIG.find(e= > {
                return getPathPrefix(path, '/') === getPathPrefix(e.activeRule, '/')})}}}/ / get the basis of the current path Such as: getPathPrefix ('/user/age/XXX ', '/') = > '/ user'
export function getPathPrefix(path, prefix = ' ') {
    if(! path)return
    const pathArray = String(path).split('/').filter(item= > item)
    const basePath = prefix + pathArray[0]
    return basePath
}
Copy the code

🚀 Link: More Keep-alive solutions

Sandbox mode

CSS sandbox

The micro front end is not mature enough for style isolation

  • Because in the microfront-end scenario, sub-applications of different technology stacks are integrated into the same runtime, we must ensure that the sub-applications do not interfere with each other’s styles at the framework level. Unlike JavaScript isolation, CSS isolation is not yet fully mature in the industry, but each solution has its own advantages and disadvantages.
  • There are various scenarios for style isolation
    • Block Element Module (BEM) specification
    • Css-modules are built to generate their respective scopes
    • CSS in JS Write CSS in THE JS language
    • Shadow DOM sandbox isolation
    • ExperimentalStyleIsolation give all style selector in front of the current mounted container
    • It has a Dynamic Stylesheet
    • Postcss Adds a namespace
  • But even with so many style isolation schemes, CSS still has a lot of problems to deal with. Such as:
    • Different applications rely on the same UI library, different versions of the situation
    • Child application, style missing or applied to the main project style
    • Microapplication constructs run time out of bounds such as body building DOM scenarios (popovers, drawers, popovers, etc., inserted into the body of the main application) will inevitably result in situations where the DOM being built cannot apply the style of the child application.

The best practice for style isolation adopted in this article is to use contractual isolation and USE CSS namespaces. Alternative: CSS Module, CSS-IN-JS and other engineering means to establish constraints: such as: avoid writing global styles, sub-applications can not invade (such as dynamically increasing global styles, etc.) modify the style outside the application, sub-application styles are written in the sub-application name as a namespace in the class, etc.

  1. The default sandbox

    • Sandbox isolation is enabled by default. By default, sandbox can ensure style isolation between sub-applications in a single instance scenario, but not between the main application and sub-applications or sub-applications in a multi-instance scenario
  2. Sandbox pattern with strict style isolation

    start({
      sandbox: {
        strictStyleIsolation: true // Strict sandbox mode}})Copy the code

    The Qiankun will wrap a ShadowDOM node for each micro-application container to ensure that the style of micro-application will not affect the whole world. Strict style isolation based on ShadowDOM is not a solution that can be used without thinking. In most cases, access applications need to do some adaptation to run properly in ShadowDOM

  3. Qiankun also provides an experimental style experimentalStyleIsolation isolation characteristics

    • When experimentalStyleIsolation is set to true, qiankun will rewrite the child application style for all style rules to increase the added a special selector rules to limit the scope of its impact

  4. BEM (Block Element Module) specification naming constraints

    • B – BlockA separate module, a separate entity that makes sense in and of itself, such as:header,menu,container
    • E – ElementElement: part of a block but has no independent meaning of its own.header title,container input
    • M – ModifierModifiers, some state or attribute of a block or element such as:small,checked

    Specific rule links:

    • ‘-‘ hyphen: Used only as a hyphen to indicate a hyphen between words in a block or child element.
    • __ Double underline: Double underline is used to connect a block to its children
    • _ Single underscore: A single underscore is used to describe the shape of a block or its children
    Block module Multi-words:.header-block module _ Status:.block_modifier module __ Child element:.block__element module __ Child element _ Status:.block__element_modifierCopy the code
  5. CSS Modules

    • This means that we import our CSS code like import JS. Each class name in the code is an attribute of the imported object. In this way, we can specify the referenced CSS style when we use it. And CSS Modules will automatically convert class names to hash values when packaging, completely eliminating CSS class name conflicts.
  6. CSS In JS

    • CSS in JS means to use THE JS language to write CSS, without the need for some separate CSS files, all THE CSS code is placed inside the component, in order to achieve the modular CSS
  7. Postcss Adds a namespace

    npm i postcss-plugin-namespace -D
    Copy the code
    • Configuration postcss
    1. Create the postcss.config.js file in the project root directory

      The plugin prefixes all classes globally and filters out labels from ignore. Ignore can write strings and regular expressions. But it runs before each compilation, so it may increase compilation time

      Note: The /body/ regex will filter out all classes with body, such as el-drawer__body, el-dialog__body, etc.

    module.exports = ctx= > {
        return {
            plugins: [
                require('postcss-plugin-namespace') ('#your-prefix', {
                    ignore: ['html'./body/})]}}Copy the code
    • public/index.html
    <html id="your-prefix"></html>
    Copy the code

The JavaScript sandbox

In order to achieve JavaScript isolation, the Qiankun framework provides three sandboxes for different scenarios: snapshotSandbox, proxySandbox and legacySandbox

  • SnapshotSandbox:qiankunThe snapshot sandbox is based ondiffTo implement, mainly used for unsupportedwindow.ProxyBrowser (IE), and is only suitable for a single subapplication
  • ProxySandbox:qiankunBased on thees6theProxyImplemented two different application scenarios of the sandbox,
    • One is thelegacySandbox(single)
    • One is theproxySandbox(more)
  • PS:The sandbox mode is enabled by default
    • However, qiankun has some drawbacks: adding properties or methods to a built-in object can result in breaking the sandbox and contaminating the global Window property

    • The sandbox is not a panacea, it only has one level of hijacking and changes such as date.prototype.xxx cannot be undone

    / / sample
    // 1. Subapplication - overwrites the setItem method
    window.localStorage.setItem = function() {
      console.log('Hi child')}// 2. Sibling application - call setItem
    console.log(window.localStorage.setItem) ƒ () {console.log('Hi child')

    // 3. Main application - call setItem
    console.log(window.localStorage.setItem) ƒ () {console.log('Hi child')
Copy the code
  • The microapplication mounts the window from the proxy, not the real window, so the modification will be isolated

// 1. Master application
window.user = {
    my: {
        name: 'I m your father'}}// 2. The first output will inherit the parent data
console.log(window.user)  // {my: {name: "I m your father"}}

window.user = { // Change the data
    my: {
        name: 'child'}}console.log(window.user)  // {my: {name: "child"}}

// 3. The sibling application does not affect Windows due to sandbox isolation
console.log(window.user)  // {my: {name: "I m your father"}}
Copy the code

The biggest problem with the micro front end is in the sandbox. Neither CSS nor JavaScript sandboxes are perfect, and we can only avoid the possibility of sandbox problems through various constraints. For example, set up team prefixes, namespace CSS, events, local storage, and cookies to avoid conflicts and clarify ownership.

I’ve sorted out some of the most problematic scenarios.

  1. Due to the defects of the Qiankun sandbox, the Window object is not completely isolated, and the window of the child application is based on the parent application. As a result, the parent application’s dependency library has been attached to the Window, and the child application will report an error when it is mounted
  2. Because of the characteristics of micro front-end, when applications of different technology stacks are gathered in the same “runtime environment”, there will be problems of style interference and dependency version conflict between micro applications
  3. The problem is that the code runs incorrectly in the sandbox, mainly BOM, DOM API use conflicts, because there is no isolation so there will be a crisis of rewriting
  4. The QIANkun will record the JS/CSS content of microapplications in global variables. If the repeatedly mounted applications are not unloaded, too much memory will be occupied and the pages will get stuck.
  5. To the body, such as the document binding, must remove the unmount to cycle, use the document. The body. The addEventListener or document. The body. Add the onClick event will not be a sandbox removed, Will have an impact on other pages
  6. JS files imported by third parties do not take effect. Some JS files themselves are instant-execute functions or create sciPT labels dynamically, but all requests for resources are hijacked, so they will not execute properly and will not mount the corresponding variables under the Window
  7. Because they are the same Window objects, there is no isolation between applications. Objects such as localStorage, sessionStorage, and cookie conflict with each other and overwrite each other
  8. Changing the default behavior of the global variable Window/Location and manipulating the Layout DOM through the Document are not recommended

14, localStorage, sessionStorage application between the use

  • LocalStorage, sessionStorage, and cookies cause overwriting problems because both parent and child applications use the same Window
  • It can be read normally, because regardless of the parent application, the stored information is stored at the address of the parent application.
  • Need to pay attention to data conflicts between microapplications, data coverage problems, here rewrite a setItem getItme to solve this problem

PS:

  • This solution is only for older projects that are hard to change. It is not recommended to change the Window method; if you need to do so, you should separate it into a class or function.
  • Original Window prototype Qiankun’s sandbox cannot handle isolation
  • SessionStorage [” keyName “] = value, sessionStorage.keyName = value is not supported. If you want to use the above method, you can use proxy or defineProperty to rewrite this article
  • Dynamically prefix getItem and setItem methods so that they are not so painful when accessing old projects
// Define a constant to iterate over
const storageMap = [
  {
    storage: sessionStorage,
    method: 'getItem'
  },
  {
    storage: sessionStorage,
    method: 'setItem'
  },
  {
    storage: localStorage.method: 'getItem'
  },
  {
    storage: localStorage.method: 'setItem'}]// Overwrite the method
function formatItem(storage, method) {
  storage[method] = function(key, value, isGlobal = false) { // isGlobal whether to store or search globally
    if (window.__POWERED_BY_QIANKUN__ && ! isGlobal) {// If it is qiankun, append the prefix
      key = BASE_ROUTER_PATH + key
    }
    Object.getPrototypeOf(storage)[method].call(this, key, value) // Minimize changes to the original method}}// the traversal method
storageMap.forEach(({ storage, method }) = > {
  formatItem(storage, method)
})
Copy the code

15. Resource sharing

As for resource sharing between applications, I think this is contradictory to the concept of a micro front end.

Micro front end concept:

  • “Stack independent” : The main framework does not limit the technology stacks that access applications, and the sub-applications have full autonomy
  • Independent development independent deployment: The sub-application repository is independent and can be developed independently. After the deployment is complete, the main framework is automatically updated
  • “Run independently” : States are isolated between each child application, runtime states are not shared, and runtimes are not shared, even if all teams use the same frame. Build a self-contained standalone application. Do not rely on shared state or global variables.

Paradoxical thinking:

  1. “Stack independence” is an architectural guideline that, when it comes to implementation, corresponds to the fact that there should be no direct or indirect stack, dependency, or implementation coupling between applications.
  2. According to the ideal: we hope that the micro front as independent decoupling, but between different micro application there may be a lot of the same repetitive resource dependence, in today’s every minute counts, the cost of each resource to be reckoned with, if some of the reusable resources sharing out directly, it can not efficiently reduce the cost of resources.
  3. As we share resources, we introduce dependency redundancy to the application, and micro-applications introduce future complexity as well as common resources.

1. Share modules

Here’s how the author organized the sharing module,

NPM rely on

  • Extract relevant code (utils, components..) Package it and upload it to the NPM library, and then install the dependency locally or NPM link in the required microapplication to achieve resource sharing in the manner of NPM. But the essence of this is sharing and reuse at the code level, and every application is built with dependencies packaged together
  • And NPM management, every NPM update should be rebuilt and released in each micro-application.

git submodule or git subtree

  • 🚀 Link git submodule
  • Subtree and SubModule are both intended for git repository management. The main difference is that subtree is a copy repository while subModule is a reference repository.
  • They allow you to treat one Git repository as a subdirectory of another Git repository. This allows you to clone another repository into your project and keep your commit relatively independent
  • Create a LIBS project for management and maintenance, which stores various common methods, components, pictures, etc., and synchronize to GitLab
  • git submodulegit subtreeAre good warehouse management solutions, but the disadvantage is that, after each child application change polymerization library also have a synchronous change, considering the warehouse, not all people can use the polymerization when child warehouse independently developed libraries tend not to take the initiative to synchronize to the polymerization, using the library’s classmates often do have synchronous operation, more time-consuming, not so perfect.

webpack Externals

  • 🚀 Link git Externals
  • Configure the webPack output bundle to exclude dependencies. In other words, the output bundle does not have dependencies defined on Externals.
  • Externals must have a CDN or JS file, such as jquery.min.js, that supports umD.
  • By loading the common module in the micro-front-end base application in this form, and removing the Externals that the micro-application references the same module, module sharing can be realized. However, there are diversified and ununified micro-application technology stacks. Some may use Vue3, while others may use React development. But Externals can’t support itCoexistence of multiple versionsIn the case

The Externals of Webpack is recommended to be used to share the dependency library.

Usage Scenarios:

  • If the parent application is usingSame library or package!! (Vue, Axios, VUe-Router, ElementCan be usedexternalsTo reduce the waste of resources caused by loading repeated packages,After being used by one project, the file can be reused by another project without being re-loaded.

Principle of use:

  • qiankunWill subproject the outer chainscriptThe tag, the content request, when it comes in, it goes to a global variable, and the next time it uses it, it gets it from that global variable first. This allows content to be reused, as long as both links are maintainedurlConsistent can.

Usage:

  • Use between microapplications

    • As long as the subproject is configuredwebpack 的 externalsAnd, inindex.htmlIn the use of external chainscriptThese public dependencies are introduced, as long as the URL of these public dependencies is the same, and are preferentially read from the cache when requested, similar to the HTTP cache
  • Microapplications use base dependencies

    • Add public dependencies to microapplicationsignoreProperty (this is a custom property, non-standard property).
    • qiankunThis attribute is ignored when the entry is parsed. Subprojects run independently of thesejs/cssCan still be loaded, so that the “subproject reuse main project dependency”.
module.exports = { 
    configureWebpack: { 
        externals: { 
            'vue': 'Vue'.'vue-router': 'VueRouter'.'vuex': 'Vuex'.'element-ui': 'ELEMENT'}}}Copy the code
<link ignore rel="stylesheet" href="//cnd.com/antd.css">
<script ignore src="//cnd.com/antd.js"></script>
Copy the code

PS: If the primary project uses externals, the subproject can reuse its dependencies, but the subproject that does not reuse dependencies will report an error.

🚀 Link # [Bug] In public dependency extraction, the proxy window access did not access the microapplication window first, and then access the main application window

webpack DLL

  • 🚀 Link webpack DLL
  • The DLL plugin can help us package the installed dependencies directly in node_module, and the add-asset-html-webpack-plugin can help us generate packed JS files and insert them into HTML
  • The use of public dependencies means that all applications using public dependencies must use the same version of dependencies. After extracting public dependencies using DllPlugin, global filters, components and mixins in different sub-applications interact with each other

Use LERNA for management

  • Lerna is a management tool, used to manage the JavaScript contains more than one package (package) project | Lerna Chinese documents

By aggregating directories

  • The aggregate directory is equivalent to an empty directory, in which clone all subrepositories, and.gitignore, subrepository code submission operations in the respective repository directory, so that the aggregate library can avoid synchronization operations.

The above schemes are mature ones in the industry, and developers need to know more about them. The author uses NPM and Webpack External. External and stable components or packages. NPM packages are recommended.

2. Share resources with micro-applications through the active application

The core of the resource delivery for the primary application is that the resource is delivered through props during registration

Props way

  • Dependencies pass when the parent application is registered or loadedpropsPass it to the child application, the child application inbootstrapormountFrom the hook function
  • The main application registers and delivers whatever resources you want, but don’t send resources without thinking about decoupling or running independently in the future.
/ / the main application/SRC/const/micro/application - list. Js
import { layout, assets, config, layout, public } from '/lib'

export default [{
    name: 'you-app-name'.// The application name
    entry: '//localhost:7286/'.// The default loading of the HTML parse JS fetch is dynamic execution (subapplications must support cross-domain)
    container: '#you-app-name-container'./ / container id
    activeRule: '/you-app-name'.// Activate the path according to the route, which must be consistent with the package ==> name file corresponding to the sub-application
    props: { // If the resource is fixed, it can be maintained in the application registry
        hideLayout: true.// Whether to hide the child application sidebar, navigation bar
        defaultPath: ' '.// Default jump address
        commonComponent: {}, // Component to be delivered
        public: public, // The parent applies the public file
        assets: assets, // Parent application resource file
        config: config, // Parent application configuration file
        layout: layout  // Parent application layout component}}]Copy the code

If it is dynamic data can be registered when the delivery

import store from '@/store/index'
import router from '@/router'

// Dynamically deliver the configuration to the props property of the micro-application.currentApp.props = { ... currentApp.props,router: router, // Deliver the parent application route
  store: store // Deliver the parent application vuex
}

loadMicroApp(currentApp) // When registering an application, the child application can be acquired in the micro-application lifecycle
Copy the code
  • Microapplication reception
// microapply mount to receive
import childStore from '@/store/index'
import childRouter from '@/router'

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

// Dynamically mount data using data through this.$root. XXX
function render(props = {}) {
    // Get the resources delivered by the parent application and store them on data
    const { container, router,store,layout, config, assets, public, commonComponent } = props
    instance = new Vue({
        childRouter,
        childStore,
        data() {
            return {
                parentRouter: router, // The parent applies the route
                parentVuex: store, // The parent application vuex
                parentLayout: layout, // Parent application layout component
                parentConfig: config, // Parent application configuration file
                parentAssets: assets, // Parent application resource file
                parentPublic: public, // The parent applies the public file
                parentCommonComponent: commonComponent // Component to be delivered}},render: h= > h(App)
    }).$mount(container ? container.querySelector('#you-micro') : '#you-micro')}Copy the code

The window way

  • Since the main project is loaded first and then the subprojects are loaded, the subprojects usually reuse the components of the main project. The method is also very simple. When the main project is loaded, the components are mounted towindowOn, subprojects can be registered directly
  • However, I do not recommend any method of modifying Windows, because of the sandbox defects, the best arrangement is not to disturb

Main Project Entry File:

import HelloWorld from '@/components/HelloWorld.vue'
window.commonComponent = { HelloWorld };
Copy the code

Subprojects directly use:

components: {
  HelloWorld: window.__POWERED_BY_QIANKUN__ ? window.commonComponent.HelloWorld : import('@/components/HelloWorld.vue'))}Copy the code

Component sharing between projects

The subproject has its own component. When other subprojects have already loaded, it reuses their component. If other subprojects have not loaded, it uses its own component

The scenario is to avoid reloading components that may not be global but are only used by a page. There are three steps:

1. Since global variables are not shared between subprojects, the main project provides a global variable to hold components

  • throughpropsPass to subprojects that need to share components.
/ / the main application
import HelloWorld from '@/components/HelloWorld.vue'

props: {
  commonComponent: {
    HelloWorld
  }
}
Copy the code

2. The subproject gets this variable and mounts it towindow

export async function mount(props) {
  window.commonComponent = props.data.commonComponent
  render(props.data)
}
Copy the code

3. Write the shared component in the subproject as an asynchronous component that returns promise.resolve ()

components: {
   HelloWorld: async() = > {if (!window.commonComponent) {
        window.commonComponent = {} // Independent runtime
      }
      const HelloWorld = window.commonComponent.HelloWorld
      return HelloWorld || (window.commonComponent.HelloWorld = import('@/components/HelloWorld.vue'))}}Copy the code

Application communication

Communication design Principles

  • Cross-application communication: decoupled and easy access
  • Open but not restricted: communication closed, unified management
  • Easy to use: low cost of learning, minimal interface
  • Easy maintenance: module management to avoid communication conflicts
  • Easy to troubleshoot: Strong link monitoring and timely tracing of problems

Micro front end communication mode

  • Based on the URL
    • Simple to use, strong versatility, but weak ability, not suitable for complex business scenarios
  • Based on the Props
    • An application passes values to its child application. This applies to host and child applications sharing components, common method calls, and so on.
  • Publish/subscribe
    • In a one-to-many relationship, the observer and the observed are abstractly coupled. However, data links are difficult to track.
  • Status management mode
    • Unified management, clear links, and easy maintenance
  • Based on thelocalStorage,sessionStorageImplementation of the communication mode
    • Json.stringify () is not recommended because it can cause data loss. It only converts to Number, String, Booolean, Array, undefined, function, NaN, regExp, and Date

The methods based on URL, Props, and LocalStorage are not described. The following are only for publish/subscribe mode and state management mode

Publish/subscribe EventBus

The design pattern of the author here is that the main application registers EventBus and then sends the micro-application through props, so that the micro-application can have both the EventBus of the main application and its own EventBus

  • The main application registers EventBus
Vue.prototype.$eventBus = new Vue()

export const parentEventBus = Vue.prototype.$eventBus
Copy the code
  • Delivered through props
// Dynamically deliver the configuration to the props property of the micro-application.
import { parentEventBus } from '@/main'currentApp.props = { ... currentActiveMicroConfig.props,parentEventBus: parentEventBus // Deliver the EventBus for the main application
}

loadMicroApp(currentApp)
Copy the code
  • The child application accepts and registers
// microapply mount to receive
export async function mount(props) {
    render(props)
}

// Dynamically mount data using data through this.$root. XXX
function render(props = {}) {
    const { parentEventBus } = props
    Vue.prototype.$eventBus = new Vue() // The child app's exclusive EventBus
    Vue.prototype.$parentEventBus = parentEventBus // EventBus delivered by the main application
    // Register operation omitted...
}
Copy the code
  • Use or normal use
this.$parentEventBus.$off('you-event') / / close

this.$parentEventBus.$on('you-event'.data= > { / / to monitor
  // xxxx code action
})

this.$parentEventBus.$emit('you-event', {... })/ / release
Copy the code

Using qiankun initGlobalState

  • The main application
// src/const/micro/actions.js
import { initGlobalState } from 'qiankun'

export const initialState = {}

const actions = initGlobalState(initialState)

export default actions
Copy the code
  • Main application use
import actions from '@/const/micro/actions'

/ / set
actions.setGlobalState({
   xxxxDataKey: xxxValue
})

// listen globally
actions.onGlobalStateChange((state, prev) = > {
  console.log(state, prev, 'Child application state: changed state; Prev status before change ')})Copy the code
  • Micro application
/ / SRC/const/micro/actions. Js encapsulation and introduce use convenient
function emptyAction() {
    // Warning: Indicates that an empty Action is currently in use
    console.warn('Current execute action is empty! ')}class Actions {
    // The default is empty Action
    actions = {
        onGlobalStateChange: emptyAction,
        setGlobalState: emptyAction
    }

    / / set the actions
    setActions(actions) {
        this.actions = actions
    }

    // Map listener
    onGlobalStateChange(. args) {
        return this.actions.onGlobalStateChange(... args) }// Mapping Settings
    setGlobalState(. args) {
        return this.actions.setGlobalState(... args) } }const actions = new Actions()
export default actions
Copy the code
  • Microapplication usage
import actions from './const/micro/actions'

export async function mount(props) {
    actions.setActions(props) // Set the actions object
}

actions.onGlobalStateChange((state, prev) = > {
  // Listen on the state of public applications. Prev Status before change
})
Copy the code

Status management mode

  • The Vuex Store based on the parent application is passed to the child application
// Dynamically deliver the configuration to the props property of the micro-application.
import store from '@/store/index'currentApp.props = { ... currentActiveMicroConfig.props,store: store // Deliver the store of the main application
}

loadMicroApp(currentApp)
Copy the code
  • The child application accepts and uses it
// microapply mount to receive
export async function mount(props) {
    render(props)
}

// Dynamically mount data using data through this.$root. XXX
function render(props = {}) {
    const { container, store } = props
    instance = new Vue({
        childStore,
        data() {
            return {
                parentVuex: store, // The parent application vuex}},render: h= > h(App)
    }).$mount(container ? container.querySelector('#you-micro') : '#you-micro')}Copy the code
  • use
this.$root.parentVuex.state.xxxx / / read

this.$root.parentVuex.commit('xxxx', {}) / / write
Copy the code

Thinking about memory overflow in micro applications

  • The QIANkun will record the JS/CSS content of microapplications in global variables. If the repeatedly mounted applications are not unloaded, too much memory will be occupied and the pages will get stuck
  • Although there is no official mention of the memory overflow problem, the author has crashed several times during the process of development when reloading the application. For security reasons, I would like to use some means to restrain the cost of variables ~
    1. Clear the additional content and DOM elements registered with the micro-application when uninstalling the micro-application
    2. Set a self-destruct time to destroy applications that have been mounted for a long time,
    3. Set the maximum number of applications to run and destroy the first application when the number exceeds the specified number

1. Delete unnecessary instances during uninstallation

export async function unmount() { 
    instance.$destroy() 
    instance.$el.innerHTML = ' ' / / key
    instance = null 
    route = null
    // ... more
}
Copy the code

2. Set the expiration time and maximum number of runs. The content here can be viewed in combination with the above content

// src/const/micro/index.js
export const MAX_RUN_MICRO_NUMBER = 5 // Maximum number of microapplications running
Copy the code
/ / the main application/SRC/const/micro/application - list. Js

export default [{
    name: 'you-app-name'.// The application name
    // ... micro app config
    Or (Infinity = never destroy)
    unmountTime: '300000'
}]
Copy the code

Record the time when you register

// src/const/micro/qianun-utils.js
// Register the application method
export function loadRouterMicroApp(currentApp) {
    // 1. Return the registered application object
    const micro = loadMicroApp(currentApp)
    // 2. The microapplication is mounted
    micro.mountPromise.then(() = > {
        // 3. Add the start time field on the application object to record the micro-application mounting time
        micro.createTime = new Date().getTime()
        // 4. On the application object, add the uninstallation time field to record the application uninstallation time. If the uninstallation time is empty, it will never be destroyed by default
        micro.unmountTime = currentApp.unmountTime || 'Infinity'
        // 5. Set the current application list and record the mounting information of the mounted application. Later, the route matches whether to unmount the application
        store.dispatch('d2admin/micro/SET_MICRO_APPLICATION_LIST', {
            key: currentApp.activeRule,
            value: micro
        })
    })
}
Copy the code

Determine whether uninstallation is required while routing guard

router.afterEach(to= > {
    microApplicationLoading(to.path)
})
Copy the code
// SRC /const/micro/qianun-utils.js

// Load the microapplication method
export async function microApplicationLoading(path) {
    // 1. Load the current application configuration based on the routing address
    let currentActiveMicroConfig = await store.dispatch('d2admin/micro/GET_FIND_MICRO_CONFIG', path)

    // 2. Get the list of microapplications
    const microApplicationList = store.getters['d2admin/micro/microApplicationList']

    // 3. Determine the application running time and destroy the application
    store.dispatch('d2admin/micro/CHECK_UNMOUNT_MICRO', { microApplicationList, currentActiveMicroConfig })

    / /... Code after the registration judgment operation is omitted
}

Copy the code

Determine whether the maximum stack, determine whether timeout destruction

/ / the main application/SRC/store/modules/d2admin/modules/micro js

export default {
    state: {
        microApplicationList: new Map([]),},actions: {
        MicroApplicationList: cache the list of microapplications, currentActiveMicroConfig: microapplication configuration that matches the current URL
        CHECK_UNMOUNT_MICRO({ state, dispatch }, { microApplicationList, currentActiveMicroConfig }) {
            // 1. There is a cache list
            if(! microApplicationList.size) {return
            }
            
            // 2. Get the current time
            const currentTime = new Date().getTime()

            // 3. Iterate through the cache application list to determine whether ~ needs to be destroyed
            Array.from(microApplicationList).forEach(([key, item]) = > {
                // 4. Obtain the application running time
                const runningTime = currentTime - item.createTime
                // 5. Obtain the application uninstallation time
                const unmountTime = item.unmountTime

                // 6. If there is a micro-app configuration, this indicates that the jump is already mounted micro-app, refresh the application time and cancel the application destruction (renew the fee, avoid the repeated activation cost of the destruction).
                if (currentActiveMicroConfig) {
                    item.createTime = new Date().getTime()
                    / /!!!!!! Set the current cache application list, update the application time, determine whether to reach the maximum stack, whether to clear the application!!
                    dispatch('SET_MICRO_APPLICATION_LIST', {
                        key: item.activeRule,
                        value: item
                    })
                    return
                }
                
                // 7. If the runtime is longer than the destruction time, destroy the corresponding application, and not the Infinity keyword
                if(runningTime >= unmountTime && unmountTime ! = ='Infinity') {
                    dispatch('DELETE_MICRO_APPLICATION_LIST', key)
                }
            })
        },
        
        // Delete the list of microapplications
        DELETE_MICRO_APPLICATION_LIST({ state }, key) {
            const micro = state.microApplicationList.get(key)
            micro && micro.unmount()
            state.microApplicationList.delete(key)
        },
        
        // Set the list of microapplications
        SET_MICRO_APPLICATION_LIST({ state, dispatch }, { key, value }) {
            // Determine whether the maximum stack is reached and clear the application
            dispatch('CLEAR_MICRO_STACK')
            state.microApplicationList.set(key, value)
        },
        
        // Check whether the stack needs to be emptied
        CLEAR_MICRO_STACK({ state, dispatch }) {
            // Check whether it is Infinity with no stack limit
            if (MAX_RUN_MICRO_NUMBER === 'Infinity') {
                return
            }

            // Determine whether the maximum stack is reached
            if (state.microApplicationList.size < MAX_RUN_MICRO_NUMBER) {
                return
            }

            // The first application to get the MAP destroys and deletes the VUex information
            const key = state.microApplicationList.keys().next().value
            dispatch('DELETE_MICRO_APPLICATION_LIST', key)
        }
    }
}
Copy the code

18. Multiple applications coexist on the same route

  • This parameter is required if multiple microapplications are displayed on a pageloadMicroAppTo load.
  • If these micro-applications need to redirect routes, they need to be used to ensure that these routes do not interfere with each othermomeryRouting.
  • vue-routeruseabstractMode,react-routerusememory historyMode,angular-routerIs not supported.
  • Vue Router navigation method (push,replace,go) in various routing modes (history,hash 和 abstract).
  • abstractIs the third mode in vUE routing, which itself acts as a fallback in an environment that does not support browser apis. Both hash and history modes work on urls in the browser. Therefore, we use abstract’s browser-separated routing mode to solve the problem of multi-application routing conflicts.
function render({ data = {} , container, defaultPath } = {}) {
    router = new VueRouter({
        mode: 'abstract'.// is not affected by the URL
        routes
    })

    instance = new Vue({
        router,
        store,
        render: h= > h(App)
    }).$mount(container ? container.querySelector('#appVueHash') : '#appVueHash')

    if (defaultPath) {
        router.push(defaultPath)
    }
}
Copy the code

Microapplication development and deployment

Develop and deploy directory recommendations

It is recommended that all microapplications be placed in one directory during development and deployment. Although the Qiankun app only needs to provide the URL of the microapplication, theoretically there is no impact on the project being placed there. However, for management and maintenance purposes, we recommend:

  • Related applications are centrally managed in the same directory
  • All microapplications are standalone projects, standalone repositories, and standalone deployments
└ ─ ─ micro - app - the container # root folder ├ ─ ─ # main/base/main application ├ ─ ─ child / # for all micro application folder | ├ ─ ─ vue - hash / # deposit micro application vue - hash folder | ├ ─ ─ ├─ package.json # Public file index.html Run the command ├─ node_modules/ # Public file dependenciesCopy the code

useNpm-run-all simplifies script configuration

Use NPM -run-all to solve the problem that the NPM run command cannot run multiple scripts at the same time

Npm-run-all has three features:

  • Sequential execution, parallel execution, mixed execution
  • --parallel: Run multiple commands in parallel, for example: NPM -run-all –parallel Lint build
  • --serial: Multiple commands are executed in sequence, for example: NPM -run-all –serial clean lint build:
  • --continue-on-error: Indicates whether to ignore errors. Adding this parameter NPM -run-all automatically exits the command that fails and continues to run normally
  • --race: NPM -run-all terminates all commands if only one command fails to run

Install dependencies

npm install npm-run-all --save-dev
// or
yarn add npm-run-all --dev
Copy the code

Configure the package.json command to install dependencies for all applications in one click

// The install: command ~ can batch execute the same command prefix, can execute the command asynchronously, synchronously
// For example, NPM run install-all installs dependencies for all projects
"scripts": {
    "install:child-hash": "cd child/child-hash && yarn"."install:child-history": "cd child/child-history && yarn"."install:main": "cd main && yarn"."install-all": "npm-run-all install:*".// Global install dependencies

    "start:child-hash": "cd child/child-hash && npm run serve"."start:child-history": "cd child/child-history && npm run serve"."start:main": "cd main && npm run serve"."serve-all": "npm-run-all --parallel start:*".// Start globally

    "build:child-hash": "cd child/child-hash && npm run build"."build:child-history": "cd child/child-history && npm run build"."build:main": "cd main && npm run build"."build-all": "npm-run-all --parallel build:*" // Global packaging
}
Copy the code

Or with scripts you can write a simple shell script

# script/clone-all.sh
#Relevant project Address

#XXX project
git clone http:/xxxxxxx.git

#XXX project
git clone http://xxxxxxxx.git
Copy the code

Added command execution to package.json

/scripts/clone-all.sh" // NPM run clone:allCopy the code

Deployment is the same as development, but it can be used directly in the base application

└ ─ ─ HTML / # root folder | ├ ─ ─ child / # for all the application of micro folder | ├ ─ ─ vue - hash / # deposit micro application vue - hash folder | ├ ─ ─ vue - history / # deposit application vue micro - history ├─ index.html # Main Application Index.html ├─ CSS / # Main application CSS folder ├─ JS / # Main application JS folderCopy the code

In this case, you need to set the microapplication buildpublicPathhistoryRouting of patternsbase“, and then pack it and put it in the corresponding directory. Make sure to change the publicPath address in webPack when building!!

project Routing base publicPath Real access path
vue-hash There is no /child/vue-hash/ http://localhost:8080/child/vue-hash/
vue-history /child/vue-history/ /child/vue-history/ http://localhost:8080/child/vue-history/
  • Vue – micro application history

    1. Micro-application routing Settings:
    base: window.__POWERED_BY_QIANKUN__ ? '/app-vue-history/' : '/child/vue-history/'.Copy the code
    1. Microapplication WebPack publicPath configuration (vue.config.js) :
    module.exports = {
      publicPath: '/child/vue-history/'};Copy the code
    1. Also, the main application’s configuration file entry needs to be changed dynamically, just like the current environment
  • But I think this photo is not elegant, I hope that all the configuration of the micro front end is maintained in a micro front end configuration page, and clearly visible.

  • Therefore, the author’s approach is to pass in an object to determine the method environment, reduce the ugliness and confusion of ternary, and dynamically send the route prefix to the micro-application, but dynamically add the prefix when the micro-application is registered

/ / SRC/main application const/micro/application - list. Js
// Get the entry to different environments
function getEentry({ prodPath, devPath }) {
    const isProduction = process.env.NODE_ENV === 'production'
    return isProduction ? prodPath : devPath
}

// Register the list of microapplications
export default[{name: 'your-name'.// The application name
        // The default loading of the HTML parse JS fetch is dynamic execution (subapplications must support cross-domain)
        entry: getEentry({
            devPath: '//localhost:7286/'.// Address of the development environment
            prodPath: `/child/your-name/` // Production environment address
        }),
        props: { // Download the microapp entry
            routeBase: '/app-vue-history/'.// Dynamically deliver route prefixes}}]Copy the code

conclusion

  • Thank you can see here, here are some of the author in micro front practice experience, because of the space reasons, a lot of technical details we no longer here in this paper, if there is a hope to know more about qiankun principle, or more practical details of a friend, can leave a message at the bottom of the article, practice in this very thank you can give me the opportunity to his elder brother and the elder brother, I hope this article can illuminate the front for you in your micro front end exploration road, thank you for your support, if there are mistakes in this article, welcome to put forward, will be timely repair and improvement, wish you code all the way unimpeded ~