Vue project ‘micro front end’ Qiankun.js combat walkthrough

This paper introduces

Large concept about micro front-end should all heard too much, here I will illustrate, vernacular three vue such as our new project a, b, c, a, is responsible for the navigation module, b is responsible for the list page, c is responsible for the details page, and then we can through micro front-end technology combined them together to form a complete project.

This article won’t go into the details, but it will cover the whole process from setup to launch, and if you have that in mind then other issues won’t be too much of a hindrance.

It is important to be clear that micro front ends are not applicable in many scenarios. Do not impose this technique. In this article, I will go through what scenarios do not apply and why.

1. The front endqiankun.jsShort and simple

Qiankun.js is currently the most outstanding one of the micro front end implementation library, he helps us achieve CSS isolation, JS isolation, project correlation and other functions, the article will be involved in the back of the actual let’s start it now.

2. Project structureA main two attached

A total of threeVue project, the firstcontainerThe project is responsible for the navigation module, number twoweb1The thirdweb2.containerThere’s one in the projectsubappFolder, which containsweb1 & web2Two items, so we can add them laterweb3,web4....In thesubappFolder will do.

3. The installationqiankunConfigure project load rules

Install Qiankun in our container project container as follows:

$yarn add qiankun # or NPM I qiankun-s

Open the container project app.vue file and redefine the navigation: /w1 and /w2 routing addresses activate the Web1 project and the Web2 project respectively.

 <div id="nav">
  <router-link to="/">Home</router-link> |
  <router-link to="/w1">web1</router-link> |
  <router-link to="/w2">web2</router-link> |
</div>

We’ll add an element with the id “box”, and the Web1 project we’ll introduce will be inserted into this element.

<div id="box"></div>
<router-view />

Change the home. vue page code to:

<template> <div class="home" container 'container' </div> </template> <script> export default {name: "home",}; </script> <style> .home { font-size: 23px; } </style>

The page should look like this:

Open the Main.js file in the Container project and write the configuration.

import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'vueApp2',
    entry: '//localhost:8083',
    container: '#box',
    activeRule: '/w2',
  },
  {
    name: 'vueApp1',
    entry: '//localhost:8082',
    container: '#box',
    activeRule: '/w1',
  },
]);

start();

Parameter analysis:

  1. name: The names of micro-applications that must be unique between micro-applications to facilitate later differentiation of project sources.
  2. entry: The entry point of the microapplication is the address of the target microapplication that I want to activate when the criteria are met (it can be other forms such ashtmlFragment, but this article focuses on the form of URL addresses).
  3. container: Where do we put the target app when we activate the appWith id 'box'The element.
  4. activeRule: Activation rule for microapplications (written in many ways or even in functional form). The above code is used when the routing address is/w1When activated.

4. Configurationsubprojectsthemain.js

As an example of configuring a Web1 project, Web2 similarly exports its own lifecycle functions in main.js.

import Vue from "vue"; import App from "./App.vue"; import router from "./router"; Vue.config.productionTip = false; let instance = null; function render() { instance = new Vue({ router, render: H => h(App)}).$mount('#web1').$mount('#web1').$mount('#web1'). The next time the micro-application reenters, it will call the mount hook directly. Bootstrap will not be triggered again. This is usually where we can do initialization of global variables, such as application-level caches that are not destroyed during the unmount phase. */ export async function bootstrap() { console.log('bootstrap'); } /** */ export async function mount() {render()} /** */ / mount() {render(); */ export async function unmount() {instance.$destroy()}

Change the ID of the DIV element in web1 >public >index.html from app to web1. It is better not to duplicate the ID of the DIV element.

<! DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta Name ="viewport" content="width=device-width,initial-scale=1.0"> </head> <body> <div id="web1"></div> </body> </ HTML >

The vue web1. Config. Js

Module. exports = {devServer: {port: 8082, // exports: 8083},}

Now we’re going to run the yarn serve command in container, web1, and web2 separately, but it’s a real hassle to run the command this way, so I’ll introduce a more engineering way of doing it.

5. npm-run-all

NPM -run-all is a plug-in that executes multiple statements by executing a single statement.

$ npm install npm-run-all --save-dev

# or 

$ yarn add npm-run-all --dev

Modify the package.json file in our container project.

  "scripts": {
    "serve": "npm-run-all --parallel serve:*",
    "serve:box": "vue-cli-service serve",
    "serve:web1": "cd subapp/web1 && yarn serve",
    "serve:web2": "cd subapp/web2 && yarn serve",
    "build": "npm-run-all --parallel build:*",
    "build:box": "vue-cli-service build",
    "build:web1": "cd subapp/web1 && yarn build",
    "build:web2": "cd subapp/web2 && yarn build"
  },

Let me explain: The run: serve system executes all the scripts with the header serve: in it, so it implements a single command to run three projects. The build command is also written here.

Other Extended Gameplay:

  1. Serial: Multiple commands are executed in sequential order, e.g. NPM -run-all –serial clean lint build:**
  2. Continue-on-error: Whether to ignore an error, adding this parameter NPM -run-all will automatically exit the erroneous command and continue with normal operation
  3. Race: After adding this parameter, npm-run-all will terminate all commands if one of them runs wrong
With the above preparations all done, we can start the project and give it a try.

6. The requestsubprojectsUnexpectedly cross-domain

Error: error: error: error: error:

Vue.config.js (web1, web2, vue.config.js)

DevServer: {port: 8082, headers: {' access-control-allow-origin ': "*"}, headers: {' access-control-allow-origin ': "*"}, headers: {port: 8082, headers: {' access-control-allow-origin ': "*"},

The reason for this cross-domain error is that Qiankun internally uses the resources requested by the FETCH. After all, three different Node services are currently started, and external HTML pages requesting their resources will still be cross-domain, so all sources need to be allowed.

We provideweb1withweb2Set the style and the result is as follows:





  • But that was only the beginning, for the problems began immediately.

7. Distinguish whether it is in the main app or not

Sometimes we need to develop Web1 separately. We don’t rely on the container project at this point, so we need to modify main.js:

import Vue from "vue"; import App from "./App.vue"; import router from "./router"; Vue.config.productionTip = false; let instance = null; function render() { instance = new Vue({ router, render: h => h(App) }).$mount('#web1') } if (window.__POWERED_BY_QIANKUN__) { window.__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } if (! window.__POWERED_BY_QIANKUN__) { render() } export async function bootstrap() { console.log('bootstrap'); } export async function mount() { render() } export async function unmount() { instance.$destroy() }

Explain sentence by sentence:

  1. window.__POWERED_BY_QIANKUN__: Whether the current environment isqiankun.jsTo provide.
  2. window.__webpack_public_path__: equivalent tooutput.publicPathConfiguration options, but it is dynamic.
  3. window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__:qiankun.jsPublic path for injection.

If it is in a container of Qiankun, then you need to set the publicPath, because Qiankun needs to separate each child application into the container project, so we can develop the Web1 project separately.

8. The child applicationRoutes jump withvue-routerThe asynchronous component has a small bug

When configuring the Router, we often write the page as an asynchronous load:

component: () => import(/* webpackChunkName: "about" */ '.. /views/About.vue'),

On the Home page of the Web2 project, I added a button to jump to the About page:

<template> <div class="home"> <button @click="jump"> </button> </div> </template> <script> export default { methods: { jump() { // this.$router.push("/web2/about"); window.history.pushState(null, null, "/w2/about"); ,}}}; </script>

If this.$router. Push state should not be used directly, it will conflict with qiankun.js routing assignment.

But this is written in the current versionqiankun.jsThere may be the following errors:

(web2->vue.config.js) :(web2->vue.config.js)

publicPath: `//localhost: 8083`

The page should load normally:

Refresh the current URL directly at this point can also be displayed correctlyaboutPage.

9. Distinguish between development and packaging

The previous several are development-related Settings, here we are going to begin to introduce the configuration of packaging, here will introduce the principle and practice, will not do very fine so the specific project development or better encapsulate some.

I’ll just configure nginx briefly to make the package work.

location /ccqk/web1 {
    alias   /web/ccqk/web1;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
}

location /ccqk/web2 {
    alias   /web/ccqk/web2;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
}

location /ccqk {
    alias   /web/ccqk/container;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
}

Since I had a project on the server before and just wrote a prefix CCQK to distinguish it, now my goal is clear. I need to type a folder called CCQK, which contains three packages: container, web1, and web2.

Step 1: Establish the packing path
  • container -> vue.config.js

    module.exports = {
    outputDir: './ccqk/container',
    publicPath: process.env.NODE_ENV === "production" ? `/ccqk` : '/',
    };
  • web1 -> vue.config.js
const packageName = require('./package.json').name; const port = 8082 module.exports = { outputDir: '.. /.. /ccqk/web1', publicPath: process.env.NODE_ENV === "production" ? '/ccqk/web1' : `//localhost:${port}`, devServer: { port, headers: { 'Access-Control-Allow-Origin': "*"}}, configureWebpack: {// output: {library: '${packageName}-[name]', libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${packageName}`, }, }, chainWebpack: config => { config.plugin("html").tap(args => { args[0].minify = false; return args; }); }};
  • web2 -> vue.config.json

    const packageName = require('./package.json').name; const port = 8083 module.exports = { outputDir: '.. /.. /ccqk/web2', publicPath: process.env.NODE_ENV === "production" ? '/ccqk/web2' : `//localhost:${port}`, devServer: { port, headers: { 'Access-Control-Allow-Origin': "*" } }, configureWebpack: { output: { library: `${packageName}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${packageName}`, }, }, chainWebpack: config => { config.plugin("html").tap(args => { args[0].minify = false; return args; }); }};

    1. What do you mean by this?

  1. output.library: Configure the name of the export library iflibraryTargetSet it to ‘var’ and the main application can be accessed directly from window.
  2. output.libraryTarget: Set toumdAccessible after AMD or CommonJS require.
  3. output.jsonpFunction: The JSONP function that Webpack uses to load chunk asynchronously.
  4. chainWebpack: Used to modifywebpackConfiguration is not compressed.
Step 2: Configure the routing path
  • web2 -> router ->index.js

    const router = new VueRouter({
    mode: "history",
    base: process.env.NODE_ENV === "development" ? '/w2' : '/ccqk/w2',
    routes,
    });

10. CSS isolation



The isolation here is not perfect, and for more details check out my previous postTaking you into – > Shadow DOM & Browser native component development (Web Components API)”And you’ll fully understand why it’s not perfect.

11. Js isolation

In multiple application scenarios, each micro-application sandbox is isolated from each other, which means that each micro-application’s global impact is limited to its own scope. For example, application A adds A test property to the window. This property can only be obtained by application A in its own scope through window.test.

Qiankun will give a proxy window object to the child application when it is activated. Every step of the user’s operation of the window object will be recorded to facilitate the recovery of the global window object when uninstalling the child application. If you want to know how to replace a window object, it is done with with and evel. If JQ passes a window object to a function for efficiency before execution, then it is ok to pass the proxy window directly to the function.

So in fact, the use of micro front-end technology solutions is to pay a certain cost, the code speed is certainly reduced.

Conway’s Law

  • The first law of organizational communication will be expressed through the design of the system.
  • The Second Law No matter how much time you have to do something, you can never do it perfectly, but there is always time to do it.
  • The third law is that there is a potential heterogeneity and homomorphism between linear systems and linear organizations.
  • Fourth law large system organization is always more inclined to decompose than small system.

For example, a team wants to try the micro front end. In fact, if you are a mobile mall project, there is no need to use the micro front end. If it is a small or medium-sized backend system, it is not recommended, unless you are a long-term maintenance and have many modules. Or if you want to start another project on top of this one, then the micro front end will be a magic weapon.

end.

This is it, I hope to make progress with you.