What is a micro front end?

Let’s start with two actual scenarios:

1. Reuse other project pages

Typically, our back-end projects look like this:

If our project needs to develop a new feature that has already been developed by another project, we want to reuse it directly. PS: What we need is only the content part of the function page of someone else’s project, not the top navigation and menu of someone else’s project.

A more stupid way is directly copied others project this page code, but one thousand people is not the vue development, or vue version, UI library, such as different, and the others page loading before operation (routing interception, authentication, etc.) we need to copy all of the bigger problem is that others have updated the code, How do we do synchronous updates.

Copying code doesn’t work in the long run, and the bottom line is that we need their code to run on their own environment, while we just “reference” their pages. This environment includes various plug-ins (VUE, VUex, VUE-Router, etc.) as well as pre-load logic (cookie reading, authentication, route blocking, etc.). Private NPMS can share components, but there are still problems with different technology stacks /UI libraries.

2. A free-for-all of the Big MAC projects

  • More and more code, slower packaging, cumbersome deployment and upgrades, some plug-in upgrades and common component changes need to be considered more, it is easy to get caught up in the whole thing
  • The project is too large, the number of people involved makes the code specification difficult to manage, and code conflicts are frequent.
  • The product has a lot of features, but customers often only need some of them. After stripping the unnecessary code, the version needs to be developed independently and maintained independently, which increases the labor cost.

For example, your product has hundreds of pages, fully functional and powerful, the customer only needs part of the page, and you need to provide the source code, at this time to give all the code out is certainly impossible, can only pick out the customer needs, this part of the code needs to develop version maintenance, it is very wasteful.

Common microfront-end scenarios

The birth of the micro front end is also to solve the above two problems:

  1. Reuse (embed) someone else’s project pages, but someone else’s project runs on top of his own environment.
  2. Big MAC apps are broken up into small projects that can be developed independently and sold as a group.

Benefits of using a micro front end:

  1. The technology stack is independent, and each subproject is free to choose its own framework and develop its own specifications.
  2. Fast packaging, independent deployment, mutual interference, easy upgrade.
  3. It is very convenient to reuse the existing function modules to avoid repeated development.

At present, there are two main micro front-end solutions: IFrame solution and single-SPA solution

iframeplan

Iframe is familiar, easy to use, provides natural JS/CSS isolation, but also brings inconvenience to data transfer. Some data cannot be shared (mainly local storage, global variables and public plug-ins), and data transfer between two projects with different sources (cross-domain) depends on postMessage.

Iframe has many potholes, but most of them have solutions:

  1. Page loading problem

The iframe and the home page share the connection pool, and the browser has restrictions on connections to the same domain, which affects the parallel loading of the page and blocks the onLoad event. Each click needs to be reloaded, and while you can use display: None for caching, too much page caching can cause your computer to stall. (Can’t be solved)

  1. Layout problems

Iframe must be given a specified height, otherwise it will collapse.

Solution: The subproject calculates the height in real time and sends it to the home page via postMessage. The home page dynamically sets the iframe height. In some cases, multiple scrollbars appear and the user experience is not good.

  1. Popover and mask layer problems

Popovers can only be vertically centered within the iframe range, not horizontally centered throughout the page.

  • Solution 1: Solve the problem by synchronizing the message with the frame page. Send the pop-up message to the main page, and the main page will pop up, which will greatly change the original project and affect the use of the original project.
  • Solution 2: Modify the style of the popover: Hide the mask layer and modify the position of the popover.
  1. iframeWithin thedivNot full screen

Full screen popover means full screen in the viewable area of the browser. This full screen refers to filling up the user’s screen.

Full screen, native Element method is used. The requestFullscreen (), plug-in: vue – fullscreen. When a page is inside an IFrame, a full-screen error is reported and the DOM structure is distorted.

Iframe allow=”fullscreen

  1. Browser forward/back issues

The iframe and the main page share a browsing history, and the iframe affects how the page moves forward and backward. Most of the time, if the iframe is redirected multiple times, the browser’s forward and backward functions will not work properly. And the iframe page refresh resets (for example, jumping from the list page to the details page, and then refreshing, returning to the list page) because the browser’s address bar has not changed and the SRC of the IFrame has not changed.

  1. iframeLoading failure is difficult to handle

Non-homologous iframes do not support onError events in Firefox or Chrome.

  • Solution 1:onloadEvent inside determine the page title, whether404or500
  • Solution 2: Usetry catchResolve this problem and try to getcontentDocumentWill throw an exception.

Catch error if iframe SRC fails to load

single-spaMicrofront-end solution

In the spa single-page app era, our pages only have an HTML file called index.html, and this file contains only a content tag

, which acts as a container for other content, and other content is generated by JS. That is, we simply take the subproject’s container

and the content generating JS and insert it into the main project to render the content of the subproject.

<link href=/css/app.c8c4d97c.css rel=stylesheet>
<div id=app></div>
<script src=/js/chunk-vendors.164d8230.js> </script>
<script src=/js/app.6a6f1dda.js> </script> 
Copy the code

We just need to take the top four tags of the subproject and insert them into the HTML of the main project to expose the subproject in the parent project.

There is a problem here. Because the content tag of the subproject is dynamically generated, the img/video/audio resource files and the js/ CSS routing page loaded on demand are relative paths. In the index.html of the subproject, it can be requested correctly. In index.html, the main project, it does not.

For example, suppose our main project’s url is www.baidu.com, our sub-project’s url is www.taobao.com, and the sub-project’s index.html contains an image . The full address of the image is www.taobao.com/logo.jpg. Now that the img tag of the image is generated into the parent project’s index.html, the address of the image request is www.baidu.com/logo.jpg. This image is not available on the parent project server.

Solution:

  1. Here belowjs/css/img/videoAnd so on are relative paths. Can we get throughwebpackPackage, package all of these paths as absolute paths, right? This solves the problem of failed file requests.
  2. Can you do it manually (or with helpnode) copy all subproject files to the main project server,nodeListen for updates to subproject files, copy them automatically, and pressjs/css/imgFolder merge
  3. Can you likeCDNSimilarly, when a server is down, it will go to other servers to request corresponding files. Or file sharing between servers, a failed file request on the main project is automatically found on the child server and returned.

The common practice is to dynamically modify the publicPath packaged by WebPack, which can then automatically inject prefixes to these resources.

Single-spa is a micro-front-end framework with the basic principles described above. On the basis of the above presented sub-projects, there are also new life cycles such as bootstrap, mount, and unmount.

In contrast to iframe, single-SPA makes parent and child projects belong to the same document, which has both advantages and disadvantages. The advantage is that data/files can be shared, common plug-ins can be shared, sub-projects load faster, the disadvantage is js/ CSS pollution.

Single-spa isn’t easy to get started with, it doesn’t work out of the box, and development and deployment requires a lot of webPack configuration changes and a lot of subprojects.

qiankunplan

Qiankun is ant Financial’s open source framework, which is based on Single-SPA. He built on single-SPA and implemented it out of the box, making it easy for subprojects to be plugged in with only a few necessary changes. If Single-SPA is a bicycle, Qiankun is a car.

There are two common entry files for the micro front-end neutron project: JS Entry and HTML entry

Pure single-SPA uses JS entry, while Qiankun supports BOTH JS entry and HTML entry.

JS entry has strict requirements:

(1) Package CSS into JS

(2) Remove chunk-vendor.js,

(3) Delete the hash value of the file name

(4) Place the single-SPA mode entry file (app.js) in the index. HTML directory, other files remain unchanged, because the path of app.js is intercepted as a publicPath

APP entry advantages disadvantages
JS entry Can cooperate withsystemJs, load public dependencies on demand (vue , vuex , vue-routerEtc.) It requires a variety of packaging configurations and cannot be preloaded
HTML entry The packaged configuration does not require much modification and can be preloaded One more layer request, you need to request firstHTMLFile, and then match it with the rejscss

Qiankun also supports Config Entry:

{
   entry: {
        scripts: [
          "app.3249afbe.js"
          "chunk-vendors.75fba470.js",].styles: [
          "app.3249afbe.css"
          "chunk.75fba470.css",].html: ` 
        ... `}}Copy the code

It is recommended to use HTML Entry, which is as simple to use as iframe but has a much better user experience than iframe. After requesting the index.html of the sub-project, qiankun would first use the regex to match the JS/CSS tags in the sub-project, and then replace them. It needed to load js and run them by itself, remove HTML /head/body tags, and insert the remaining content into the sub-project’s container as is:

Benefits of using Qiankun:

  1. qiankunBring their ownjs/cssSandbox function,singles-spaCan be solvedcssPollution, but need sub-project coordination
  2. single-spaSolution only supportsJS entryThe features limit its support onlyvuereactangularSuch technology development projects, to somejQueryOld programmes are not.qiankunThere is no limit
  3. qiankunSupport sub-project pre-request function.

jsThe sandbox

Js/CSS contamination is inevitable and can be a major or minor problem. Like a time bomb, do not know when there will be a problem, screening is also troublesome. As a basic framework, addressing these two pollutants is too important to be developed solely by “specification”.

The principle of the JS sandbox is to take a snapshot of the Window object before the subproject is loaded, and restore this snapshot when the subproject is unloaded, as shown in the figure below:

So how to monitor the changes of Window objects? It is obviously not feasible to directly make a deep copy of window objects and then compare each attribute in depth. The Qiankun framework uses ES6’s new feature, proxy proxy method. Specific how to operate, the previous article has written (link at the end of the article), will not repeat.

Proxy, however, is not compatible with IE11. For compatibility, lower versions of IE use the diff method: shallowly copy the Window object and compare each property.

CSS sandbox

Qiankun CSS sandbox principle is rewriting HTMLHeadElement. The prototype. The appendChild events, new style record component runtime/link tag, uninstall the component to remove these tags.

In single-SPA, I used the idea of skin changing to solve CSS pollution: First, CSS-scoped solves most of the contamination. For some global styles, the sub-project gives the body/ HTML a unique ID /class (for normal deployment), and then precedes the global style with the ID /class. Single-spa mode adds the unique ID /class to the body/ HTML during the mount cycle and removes it during the unmount cycle, ensuring that the global CSS only works for this project.

The fatal point of both solutions is that they cannot solve CSS pollution when multiple sub-projects are running at the same time, and the sub-projects can pollute the CSS of the main project.

Although it is not common to say that two items are running at the same time, if you want to implement keep-alive, you need to use display: None to hide the subitems. The subitems do not need to be uninstalled, so there are two subitems running at the same time, but one of them is not visible to the user.

Another idea of the CSS sandbox is to limit the styles of subitems to the scope of the subitems’ containers, so that different subitems can be given different containers. However, there is a new problem with this. The append to Body popover in the subproject will not work. So style contamination needs to be regulated by the convention of class naming prefixes.

Micro front end solution practice

In my previous articles (linked at the end of the article), single-SPA and Qiankun’s demo have been implemented, and the development and deployment process are also in place. The next step is to learn from practice.

The existing projects areqiankunsubprojects

Since we are a VUE technology stack, I will take the transformation of a VUE project as an example to show that the principle of the other technology stacks is the same.

  1. insrcAdding files to a directorypublic-path.js:
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Copy the code
  1. Modify the container for the project initialization in index.html. Do not use #app to avoid conflicts with other projects

  2. Modify the entry file main.js:

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

Vue.use(VueRouter)
Vue.config.productionTip = false

let router = null;
let instance = null;
function render(parent = {}) {
  const router = new VueRouter({
    // The histroy route needs to set base, app-history-vue depends on the project name
    base: window.__POWERED_BY_QIANKUN__ ? '/app-history-vue' : '/'.mode: 'history'.// Hash mode does not require the above two lines
    routes: []
  })
  instance = new Vue({
    router,
    store,
    render: h= > h(App),
    data(){
      return {
        parentRouter: parent.router,
        parentVuex: parent.store,
      }
    },
  }).$mount('#appVueHistory');
}
// Global variables to determine the environment, independent runtime
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('vue app bootstraped');
}
export async function mount(props) {
  console.log('props from main framework', props);
  render(props.data);
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = ' ';
  instance = null;
  router = null;
}
Copy the code

The main changes are as follows: Three life cycles of modifying publicPath files and export are introduced.

Note:

  • webpackpublicPathThe value can only be changed in the entry file. The reason for writing it to a separate file and introducing it at the beginning of the entry file is so that all of the following code can use it.
  • Routing file needsexportRoute data instead of instantiated route objects, the hook function of the route also needs to be moved to the entry file.
  • inmountLifecycle, you can get the data from the parent project,routerRoutes to jump to the main project/other subprojects,storeIs instantiated by the parent projectVuex(Other data can also be passed).
  1. Modifying the packaging Configurationvue.config.js:
const { name } = require('./package');

module.exports = {
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': The '*',}},// Customize the WebPack configuration
  configureWebpack: {
    output: {
      library: `${name}-[name]`.libraryTarget: 'umd'.// Package the child application into the UMD library format
      jsonpFunction: `webpackJsonp_${name}`,}}};Copy the code

Note: This name is obtained from package.json by default and can be customized as long as it is the same as the name registered with the parent project.

This configuration is mainly two, one is to allow cross-domain, the other is packaged in UMD format. Why package in UMD format? It was to let Qiankun get the life cycle function of its export. Take a look at its packaged app.js:

The three life cycles are based on the name value you gave when registering the application. If the name value is inconsistent, the life cycle function will not be available

Some considerations for sub-project development

  1. All resources (pictures/audio/video, etc.) should be includedsrcDirectory, do not putpublicorstatic

Resources stored in the SRC directory will be processed by Webpack and can be uniformly injected into publicPath. Otherwise it will be 404 in the main project.

Reference: Vue-CLI3 official documentation description: when to use -public-folder

The configuration file config.js exposed to the operations staff can be placed in the public directory because the index.html URL of JS/CSS resources with relative links will be prefixed by Qiankun.

  1. Please giveaxiosInstance adds interceptors instead ofaxiosobject

Later on, subprojects are considered to share common plug-ins, so you need to avoid the contamination of common plug-ins

// Add interceptors to axios instances
const instance = axios.create();
instance.interceptors.request.use(function () {/ *... * /});
// Error: Add interceptor directly to axios object
axios.interceptors.request.use(function () {/ *... * /});
Copy the code
  1. avoidcsspollution

Css-scoped for in-component styles is required.

For some popovers inserted into the body, you cannot use scoped. Do not use the original class to modify the style. Add your own class to modify the style.

.el-dialog{
  /* It is not recommended to use the component's original class */
}
.my-el-dialog{
  /* It is recommended to use a custom component's class */
}
Copy the code
  1. Use cautionPosition: fixed

In the parent project, this location may not be accurate, should try to avoid using, relative to the browser window positioning requirements, you can use position: sticky, but there will be compatibility problems (IE does not support). If the location uses bottom and right, this is not a problem.

Alternatively, positions can be written as dynamically bound styles:

<div :style="{ top: isQiankun ? '10px' : '0'}">
Copy the code
  1. tobodydocumentWait for the bound event, please seeunmountCycle to remove

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

qiankunFaqs and Solutions

qiankunCommon error

  1. Component is notexportRequired lifecycle functions

First check the entry file of the subproject for export lifecycle functions, then check the package of the subproject, and finally check whether the requested subproject file is correct.

  1. Container not rendered properly when subproject loaded

Check if the container div is written to a route that does not match any unloaded routes. If only a routing page loads a subproject, you can register the subproject in the Mounted cycle of the page and start it.

The main project route can only be usedhistoryPattern?

Since the Qiankun used location. Pathname value to determine which subproject should be loaded at present, it needed to inject different routing path for each subproject, while the hash mode subproject routing jump did not change path, so there was no impact. You can set the Base attribute for the history subproject route.

If the main project uses hash mode, then the location.hash value is used to determine which subproject should be loaded, and all subprojects should be hash mode. Add a prefix to all routes of the subproject, and change the route jump of the subproject if path was used previously. The name jump does not.

If the main project is in Hash mode and the sub-project is in History mode, you cannot jump to another sub-project in History mode and return to the page of the main project after jumping to the sub-project.

It’s also easy to change vue project hash mode to history mode:

  1. new RouterSet whenmodehistory

  1. webpackPackaged configuration (vue.config.js ) :

  1. Some resources report 404 with relative paths changed to absolute paths:<img src="./img/logo.jpg">Instead of<img src="/img/logo.jpg">Can be

cssContamination problems and loadingbug

  1. qiankunIt can only solve the style pollution between the sub-projects, not the sub-project style pollution of the main project style

The main project should not be polluted by the style of the project, the sub-project is vue technology, the style can be written CSS -scoped, what if the sub-project is jQuery technology? Therefore, the id/class of the main project itself needs to be special. It cannot be too simple to match the quilt project.

  1. Of the main project page when jumping from the sub-project page to the main project page itselfcssNot loadedbug

The reason for this problem is that the unloading of the child project takes a little time when it jumps to the parent project. During this time, the parent project loads and inserts CSS, but the CSS sandbox of the quilt project is recorded and removed. Event listening for the parent project is the same, so you need to wait until the child project is uninstalled before jumping. I originally wanted to determine in the route hook function whether the subproject is unloaded, and then jump to the route. However, if the route does not jump, the subproject will not unload at all.

Temporary solution: HTMLHeadElement copy at first. The prototype. The appendChild and window addEventListener, routing hook function beforeEach judgment, if the current routing is a subproject, and routing is the parent project, Restore the two objects.

const childRoute = ['/app-vue-hash'.'/app-vue-history'];
const isChildRoute = path= > childRoute.some(item= > path.startsWith(item))
const rawAppendChild = HTMLHeadElement.prototype.appendChild;
const rawAddEventListener = window.addEventListener;
router.beforeEach((to, from, next) = > {
  // Jump from subproject to main project
  if(isChildRoute(from.path) && ! isChildRoute(to.path)){ HTMLHeadElement.prototype.appendChild = rawAppendChild;window.addEventListener = rawAddEventListener;
  }
  next();
});
Copy the code

Route jump problem

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

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

However, there is a defect, so you can only jump through JS, the jump link can not use the browser’s own right button menu (as shown in the picture: Chrome’s own right button menu)

Project communication problems

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

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

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

// The main project is initialized
import { initGlobalState } from 'qiankun';
const actions = initGlobalState(state);
// Main project project monitor and modify
actions.onGlobalStateChange((state, prev) = > {
  // state: state after the change; Prev Status before change
  console.log(state, prev);
});
actions.setGlobalState(state);

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

Data transfer between VUE projects is more convenient using Vuex, a shared parent component, and communication with other technology stack projects using GlobalState provided by Qiankun.

How are common plug-ins shared between subprojects

If both the main project and the subproject use the same version of Vue/Vuex/ VUE-Router, it is wasteful to load the subproject once after the main project is loaded.

To reuse common dependencies, the premise is that the subproject must configure externals so that dependencies are not packaged into chunk-vendors. Js, so that existing common dependencies can be reused.

Introducing public dependencies on demand has two levels:

  1. Unused dependencies are not loaded
  2. Large plug-ins load only what is needed, for exampleUIOn-demand loading of component libraries,echarts/lodashLoad on demand.

Externals for Webpack is introduced on demand to support large plug-ins:

subtract : {
   root: ['math'.'subtract']}Copy the code

Subtract can be accessed through an attribute under the global Math object (such as Window [‘ Math ‘][‘ Subtract ‘]).

single-spaCommon dependencies for subprojects can be introduced as needed

Single-spa uses systemJs to load subprojects and public dependencies. You can load public dependencies on demand by configuring them in the systemJs configuration file importmap.json: importmap.json

{
 "imports": {
   "appVueHash": "http://localhost:7778/app.js"."appVueHistory": "http://localhost:7779/app.js"."single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js"."vue": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"."vue-router": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js"."echarts": "https://cdn.bootcss.com/echarts/4.2.1-rc1/echarts.min.js"}}Copy the code

qiankunHow do you introduce public dependencies on demand

The big MAC app’s public dependencies and public functions are used by too many pages, making it difficult to upgrade and change. Using a micro front end allows each subproject to have its own dependencies without interfering with each other. And we want to reuse common dependencies, which goes against the idea of a microfront end.

So my idea is that the parent project provides public dependencies, and the children are free to use them or not.

The parent project loads the dependencies first, and then passes Vue/Vuex/ vuE-Router through props when registering the subprojects. The subprojects can choose to use it or not.

Main projects:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { registerMicroApps, start } from 'qiankun';
import Vuex from 'vuex';
import VueRouter from 'vue-router';

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

registerMicroApps([
  { 
    name: 'app-vue-hash'.entry: 'http://localhost:1111'.container: '#appContainer'.activeRule: '/app-vue-hash'.props: { data : { store, router, Vue, Vuex, VueRouter } }
  },
]);

start();
Copy the code

Components:

import Vue from 'vue'

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

export async function mount(props) {
  console.log('props from main framework', props);
  const { VueRouter, Vuex } = props.data;
  Vue.use(VueRouter);
  Vue.use(Vuex);
  render(props.data);
}

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

This is not very feasible for two reasons:

  1. When the subprojects are running independently,Vue-Router/VuexWhere do these dependencies come from? Subprojects are deployed in one copy and can be run independently or byqiankunIntegration.
  2. A parent project can only pass dependencies it already has. How do you determine which dependencies a child project needs? Requirements introduced on demand are not met

After externals of webpack is configured, when the subproject runs independently, the sources of these dependencies are the outer linked script tags in index.html and only.

Under this premise, the same server file is used when the vUE versions of the subproject and the main project are the same. You can do HTTP caching even if you can’t share it.

Can a dependency be loaded and then reused? For example, if subproject A requests vUE version 2.6 on the server and switches to subproject B, which also uses the vUE file, can it be reused without loading it again?

In fact, it is possible. It can be seen that Qiankun will record the script tag of the external chain of the sub-project into a global variable after receiving the request. Next time he will use it again, he will fetch it from this global variable first. This allows content to be reused, as long as the urls of the two links are consistent.

const fetchScript = scriptUrl= > scriptCache[scriptUrl] ||
		(scriptCache[scriptUrl] = fetch(scriptUrl).then(response= > response.text()));
Copy the code

So as long as the subproject is configured with webpack externals and uses externals in index.html to import these public dependencies, as long as these public dependencies are on the same server, it can implement the on-demand import of the public dependencies of the subproject. Another project can reuse this file without reloading it.

qiankunMore perfect on demand introduction

While Qiankun does not repeatedly request the same URL for a common dependency, this is only a bit better than HTTP caching.

The drawbacks are:

  1. Common dependencies in the main project are not recorded in this cache and are not reused by other projects
  2. It’s just that the request is not repeated, and it still needs to be repeated. Can not execute, directly reuse? .jsThe sandbox is removed when the subproject is uninstalledwindow, andwebpackexternalsIt is precisely these public dependencies that are mounted inwindowIs it possible to remove these public dependencies as appropriate?
  3. The same version of the dependency reuse, different versions but use the same, can do reuse? (Different versionsurlBut there may be some questions here, since there is no difference, why not upgrade the plug-in?

These issues may require changes to the source code of Qiankun.

jQueryResource loading problems for old projects

After the content tag of the subproject is inserted into the index.html of the parent project, the paths of the resources (img, video, audio, etc.) are relative. As a result, the resources cannot be displayed correctly. I’ve listed three solutions above.

Generally speaking, jQuery projects are not packaged with WebPack, so you can’t inject path prefixes by modifying publicPath. The latter two methods are more cumbersome, or should be addressed from the framework itself rather than the other way around. So I came up with the following three schemes:

Scheme 1: Dynamic insertion<base>The label

HTML has a native tag , which can only be placed inside the , and whose href attribute is a URL value. MDN address: base document root URL element

After the tag is set, all links and urls on a page are based on its href. For example, if the page access address is https://www.taobao.com, set Page in the original figure < img SRC = “. / img/jQuery1. PNG “Alt =” “> the actual request of address will be https://www.baidu.com/img/jQuery1.png, Links on the page < a > : < a href = “/ about” > < / a >, after click page will jump to: https://www.baidu.com/about

The tag has the same effect as webpack’s publicPath, so can we assign the address of the jQuery project to the tag and insert it into the tag before the jQuery project loads? This solves the problem of loading resources for jQuery projects.

It is also very easy to do in the beforeLoad life cycle provided by Qiankun to determine whether the current is a jQuery project:

beforeLoad: app= > {
   if(app.name === 'purehtml') {const baseTag = document.createElement('base');
       baseTag.setAttribute('href',app.entry);
       console.log(baseTag);
       document.head.appendChild(baseTag); }},beforeUnmount: app= > {
   if(app.name === 'purehtml') {const baseTag = document.head.querySelector('base');
      document.head.removeChild(baseTag); }}Copy the code

This will load the subproject resources correctly, but the tag is so powerful that all routes will not jump properly. When jumping to another subproject, the link is based on and will jump to a route that does not exist in the jQuery subproject. One bug is solved, and a new bug appears. So this is a very unlikely scenario.

Plan two: hijack the label insertion function

The plan has two steps:

  1. forHTMLAlready in theimg/audio/videoSuch as label,qiankunSupport to rewritegetTemplateFunction that can be used to import filesindex.htmlThe static resource path in
  2. For dynamically insertedimg/audio/videoWait for the tag, hijackingappendChildinnerHTMLinsertBeforeTo replace the relative path of a resource with an absolute path

tag and its content will be matched with the regex tag. link/style/script/meta tags. It is then inserted into the parent project’s container.

We can pass a getTemplate function to change the relative path of the image to an absolute path, which will be used when processing the template:

start({
  getTemplate(tpl,... rest) {
    // In order to see the effect directly, so the writing is dead, the actual need to use the re match
    return tpl.replace('<img src="./img/jQuery1.png">'.'<img src="http://localhost:3333/img/jQuery1.png">'); }});Copy the code

For dynamically inserted tags, hijack their DOM insertion function to inject the prefix.

Suppose the subproject inserts a graph dynamically:

const render = $ => {
  $('#purehtml-container').html('<p>Hello, render with jQuery</p><img src="./img/jQuery2.png">');
  return Promise.resolve();
};
Copy the code

Main project hijacks jQuery HTML method:

beforeMount: app= > {
   if(app.name === 'purehtml') {// jQuery's HTML method is a very complex function
       $.prototype.html = function(value){
          const str = value.replace('<img src="/img/jQuery2.png">'.'<img src="http://localhost:3333/img/jQuery2.png">')
          this[0].innerHTML = str; }}}Copy the code

Of course, there is a simple and crude way to write the image path of jQuery project as an absolute path, but this is not recommended, it will not work on a different server deployment.

Plan three: HerejQueryThe project withwebpackpackaging

The feasibility of this scheme is not high, it is an old project, there is no need to do so.

Summary of resource loading for old projects

Qiankun itself is weak in accessing jQuery multi-page application. Generally, a large project only accesses one or several pages. In this case, scheme 2 is more reasonable.

qiankunUsed to summarize

  1. To enable preloading when there is only one subproject, you must use start({prefetch: ‘all’})

  2. The JS sandbox does not eliminate all JS pollution. For example, if I add a click event to with onclick or addEventListener, the JS sandbox does not eliminate its impact, so it is up to code specification and self-awareness

  3. The solution to CSS/JS pollution is to remove the CSS tag inserted by the sub-project and hijack the Window object to restore it to the way it was before the sub-project was loaded. This is against the keep-alive requirement: Keep-alive requires that these be preserved, just style hiding.

  4. Qiankun does not fit well into some older projects

While Qiankun supports the older jQuery project, it doesn’t seem to have a good solution for multi-page applications. It is expensive and cumbersome to modify every page, but using iframe to embed these old projects is convenient.

  1. Safety and performance issues

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

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

  1. Micro front-end debugging, each time need to enter the sub-project and the main project respectively to run and package, very troublesome, can be usednpm-run-allPlug-in to implement: a command that runs all projects.
{
  "scripts": {
    "install:hash": "cd app-vue-hash && npm install"."install:history": "cd app-vue-history && npm install"."install:main": "cd main && npm install"."install:purehtml": "cd purehtml && npm install"."install-all": "npm-run-all install:*"."start:hash": "cd app-vue-hash && npm run serve "."start:history": "cd app-vue-history && npm run serve"."start:main": "cd main && npm run serve"."start:purehtml": "cd purehtml && npm run serve"."start-all": "npm-run-all --parallel start:*"."serve-all": "npm-run-all --parallel start:*"."build:hash": "cd app-vue-hash && npm run build"."build:history": "cd app-vue-history && npm run build"."build:main": "cd main && npm run build"."build-all": "npm-run-all --parallel build:*"}}Copy the code

The –parallel parameter indicates parallelism. Without this parameter, the next command is executed after the previous command has been executed.

At the end

Don’t be prejudiced against iframe, it’s also a way of implementing a micro front end, and it works well if there are no pop-ups, no full screen, etc. Configure caching and CDN acceleration. If Intranet access is used, it will not be slow.

Iframe and Qiankun can coexist. It is good for jQuery multi-page application to use Iframe to access, which scheme should be used when and in what scene, and the specific situation will be analyzed.

Finally, the article has any question or error welcome to point out, thank you!

The content of this article has some additions, but there is no more room for this article. Please see: Summary of Qiankun Micro front-end Practice (II)

The appendix

How to realize single-SPA and Qiankun’s demo and analyze some principles can be seen in my three articles:

  1. Implementing a front-end microservice from 0 (part 1)
  2. Implementing a Single-SPA front-end Microservice from 0 (middle)
  3. Implementing a Single-SPA Front-end Microservice from 0 (part 2)

PS: The third article was written in March of this year. The source code involved in the article was Version 1.0. Version 2.0 was released in April, but the basic principle remained roughly the same.

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

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

The online case of Qiankun

  1. tech.antfin.com/partners
  2. www.zstack.io/