Previous articles are recommended

Gitlab-cicd + Docker + Vue + Django

Introduction to Docker, this one is enough

Introduction to Technology Stack

  • Micro front-end
  • qiankun
  • Take a look at my previous introduction and you will understand it at a glance
  • Gitlab-ci/CD Here is the knowledge of automated deployment, you can understand
  • nginx

It is recommended to cooperate with video explanation for faster understanding

What is a micro front end

The micro front end is a kind of technical means and method strategy that multiple teams jointly build modern Web applications by releasing functions independently.

The micro front-end architecture has the following core values:

  • Technology stack independent main framework does not limit access to the application stack, microapplications have full autonomy

  • Independent development, independent deployment of micro application warehouse independent, the front and back end can be independently developed, after the deployment of the main framework automatically complete synchronization update

  • The incremental upgrade

    In the face of a variety of complex scenarios, it is usually difficult to upgrade or reconstruct the existing system completely, and the micro front end is a very good means and strategy to implement the gradual reconstruction

  • Independent run time States are isolated between each microapplication and run time states are not shared

What is qiankun

Qiankun is a productionable micro-front-end framework based on Single-SPA, with JS sandbox, style isolation, HTML Loader, preloading and other capabilities required by micro-front-end systems. The Qiankun can be used with any JS framework and microapplication access is as simple as embedding an IFrame system.

The core design concept of Qiankun

Reference address: qiankun.umijs.org/zh/guide

  • simple

    As the main application and micro application can be stack independent, Qiankun is just a library like jQuery for users. You need to call several apis of Qiankun to complete the micro front end transformation of the application. Meanwhile, thanks to THE HTML entry and sandbox design of Qiankun, accessing microapplications is as simple as using IFrame.

  • Decoupling/technology stack independent

    The core goal of the micro front end is to break up the boulder application into several loosely coupled micro-applications that can be autonomous. Many of the designs of Qiankun are based on this principle, such as HTML Entry, sandbox and inter-application communication. Only in this way can we ensure that microapplications can be developed independently and run independently.

Why not use Iframe

Reference address: www.yuque.com/kuitos/gky7…

Iframe is pretty much the perfect microfront-end solution, if you leave aside the experience issues.

The most important feature of iframe is that it provides a browser native hard isolation scheme, no matter the style isolation, JS isolation and other problems can be solved perfectly. However, its biggest problem is that its isolation can not be broken, leading to the application context can not be shared, resulting in the development experience, product experience problems.

  1. The URL is not synchronized. The browser refresh iframe URL status is lost, and the back forward button is unavailable.
  2. The UI is not synchronized and the DOM structure is not shared. Imagine an iframe with a mask layer in the bottom right corner of the screen, and we want it to be centered in the browser and automatically centered when the browser resize.
  3. The global context is completely isolated and memory variables are not shared. Iframe internal and external system communication, data synchronization and other requirements, the cookie of the main application should be transparently transmitted to the sub-applications with different root domain names to achieve the effect of free registration.
  4. Slow. Each child application entry is a process of browser context reconstruction and resource reloading.

Some problems better solve the problem of (1), some problems we can turn a blind eye (question 4), but some problems we are difficult to solve the problem of (3) even unable to solve the problem of (2), and these can’t solve the problem it will bring very serious to product experience problems, finally lead us to abandon the iframe solution.

Core values of the micro front end

www.yuque.com/kuitos/gky7…

Project concept

Before we get into the technical implementation, let’s take a look at what we want to implement.

Microfront diagram

The master application manages login status and displays navigation

Child apps load dynamically based on the click of the main app navigation

Deployment of logic

There are many ways to deploy, but here’s what I tried:

  • Using only one Nginx container, deploy multiple applications by listening on different ports, and add routing proxies to child applications in the ports of the primary application

    This approach is the simplest but not suitable for automated deployment of Gitlab-CI/CD, so I will initially test the implementation of the nGINx deployment micro front end

  • Multiple Nginx containers are used, each container exposes a port, and the corresponding routing proxy is added to the child application through the primary application

    This approach can be implemented, but multiple ports are exposed on the server, reducing security, and external applications can be accessed directly through the ports

  • With multiple Nginx containers, only the ports of the master application are exposed, and the master application communicates with the child application, which is then accessed through the Nginx proxy

    Ideally, only one port is exposed, all agents are in between containers, and are insensitive to the outside world. The following is an illustration of the implementation

qiankun

Install qiankun

$ yarn add qiankun # or NPM I Qiankun-s
Copy the code

Register microapplications in the main application

import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from 'qiankun';

const apps = [
  {
    name: 'ManageMicroApp'.entry: '/system/'.// Use //localhost for local development
    container: '#frame'.activeRule: '/manage',},]/** * Register microapplication * first parameter - registration information for microapplication * second parameter - global lifecycle hook */
registerMicroApps(apps,{
  // Qiankun Lifecycle Hook - micro application before loading
  beforeLoad: (app: any) = > {
    console.log("before load", app.name);
    return Promise.resolve();
  },
  // Qiankun Lifecycle Hook - micro application after mount
  afterMount: (app: any) = > {
    console.log("after mount", app.name);
    return Promise.resolve(); }});/** * Adds a global uncaught exception handler */
addGlobalUncaughtErrorHandler((event: Event | string) = > {
  console.error(event);
  const { message: msg } = event as any;
  // Load failed
  if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
    console.error("Microapplication failed to load. Please check whether the application is running."); }}); start();Copy the code

Once the url of the browser changed after the microapplication information was registered, the matching logic of qiankun would be automatically triggered. All microapplications matched by activeRule rules would be inserted into the specified Container and the lifecycle hooks exposed by the microapplication would be called in turn.

If the microapplication is not directly associated with routing, you can also manually load the microapplication:

import { loadMicroApp } from 'qiankun';


loadMicroApp({
  name: 'app'.entry: '//localhost:7100'.container: '#yourContainer'});Copy the code

Micro application

The microapplication does not require any additional dependencies to be installed to access the Qiankun master application.

1. Export the appropriate lifecycle hooks

Microapplications need to export the bootstrap, mount, and unmount lifecycle hooks in their own entry JS (usually the entry JS of your webpack configuration) for the host application to call when appropriate.

import Vue from 'vue';
import VueRouter from 'vue-router';

import './public-path';
import App from './App.vue';
import routes from './routes';
import SharedModule from '@/shared'; 

Vue.config.productionTip = false;

let instance = null;
let router = null;
// Execute render directly if the child application is running independently
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

/** * Render functions * run in the main application lifecycle hook/run when the child application starts alone */
function render(props = {}) {
  // SharedModule is used for communication between the master application and its children
  // If the shared passed in is empty, use the child's own shared
  // If the shared passed in is not empty, the shared passed in by the main application overloads the shared of the child application
  const { shared = SharedModule.getShared() } = props;
  SharedModule.overloadShared(shared);

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

  // Mount the application
  instance = new Vue({
    router,
    render: (h) = > h(App)
  }).$mount('#app');
}

/** * 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 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 mount', props);
  render(props);
}
/** * The method that is called each time the application is cut/unloaded, usually here we unload the application instance of the microapplication */
export async function unmount() {
  console.log('vue unmount');
  instance.$destroy();
  instance = null;
  router = 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

There is also a public-path file referenced in the above code:

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

This is mainly to solve the micro application dynamic loading of scripts, styles, images and other incorrect addresses.

2. Configure the micro-application packaging tool

In addition to exposing the corresponding lifecycle hooks in the code, in order for the main application to correctly identify some of the information exposed by the micro-application, the micro-application packaging tool needs to add the following configuration:

Webpack:

const packageName = require('./package.json').name;


module.exports = {
  publicPath: '/system/'.// The package address is based on the entry value registered in the main application
  output: {
    library: 'ManageMicroApp'.// The library name is the same as the name of the microapplication registered by the main application
    libraryTarget: 'umd'.// This option attempts to expose the library to the previous module-defining system, which makes it compatible with CommonJS and AMD, or exposes it as a global variable.
    jsonpFunction: `webpackJsonp_${packageName}`,}};Copy the code

Summary of Key points

  • Configuration during registration of the primary application

    const apps = [
      {
        name: 'ManageMicroApp'.entry: '/system/'.// http://localhost/system/ will point to the corresponding child application address via the nginx proxy
        container: '#frame'.activeRule: '/manage',},]Copy the code

    When a master application registers a microapplication,entryCan be the relative path,activeRuleCan not andentrySame (otherwise the main app page refresh becomes a micro app)

  • Vue route base

    router = new VueRouter({
      base: window.__POWERED_BY_QIANKUN__ ? '/manage/' : '/'.mode: 'history',
      routes
    });
    Copy the code

    If it is called by the main application then the base of the route is/manage/

  • Webpack packages the configuration

    module.exports = {
      publicPath: '/system/'};Copy the code

    forwebpackBuild microapplications, microapplicationswebpackpackagedpublicPathNeed to be configured as/system/, otherwise microappliedindex.htmlCan request correctly, but micro applicationindex.htmlThe inside of thejs/cssThe path doesn’t take/system/.

Now that we have the microfront-end configuration done, the next step is to configure nginx.

Nginx configuration in production environment

Let’s suspend the nginx configuration for the main application

    server {
        listen       80;
        listen[: :] :80 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;

        location / {
            try_files $uri $uri/ /index.html;
            index index.html;
        }
				# The subapplication entry we configured above is /system/, so it will trigger the proxy here, proxy to the corresponding subapplication
        location /system/ {
    				 # -e: true as long as filename exists, no matter what type filename is. He took the
             if(! -e $request_filename) {proxy_pass http://192.168.1.2; The IP here is the IP of the child application docker container
             }
    				 # -f filename True if filename is a regular file
             if(! -f $request_filename) {proxy_pass http://192.168.1.2;
             }
             # docker run nginx does not recognize localhost so this will be reported as 502
             # proxy_pass http://localhost:10200/;
             proxy_set_header Host $host;
         }

        location /api/ {
            proxy_passhttp://background IP address IP/;proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
Copy the code

Let’s look at what’s applied all at once

server {
    listen       80;
    listen[: :] :80 default_server;
    server_name_2.root         /usr/share/nginx/html;

    You must add allow cross-domain, otherwise the main application cannot access
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

    location / {
        try_files $uri $uri/ /index.html;
        index index.html;
    }

    location /api/ {
        proxy_passhttp://background IP address IP/;proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}
Copy the code

Dockerfile configuration

So let’s look at what’s applied all at once

# Use nginx images directly
FROM nginx
# replace the default conf file configured above
COPY nginx.conf /etc/nginx/nginx.conf
# nginx default directory should be able to see the index.html file
COPY dist/index.html /usr/share/nginx/html/index.html
Note that all resource files must be placed in the system file index.html to load correctly
COPY dist /usr/share/nginx/html/system
Copy the code

Let’s take a look at the main application

Nginx reverse proxy/API / 404 error (unknown)
FROM centos
# to install nginx
RUN yum install -y nginx
Go to /etc/nginx
WORKDIR /etc/nginx
Replace the configuration file
COPY nginx.conf nginx.conf
Go to /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
The main app is packaged normally, so just put the package in it
COPY dist .
Expose port 80
EXPOSE 80
# run nginx
CMD nginx -g "daemon off;"
Copy the code

Gitlab – ci/CD configuration

First look at a draught of application, only say the key

image: node

stages:
  - install
  - build
  - deploy
  - clear

cache:
  key: modules-cache
  paths:
    - node_modules
    - dist

Installation environment:
  stage: install
  tags:
    - vue
  script:
    - npm install yarn
    - yarn install

Package items:
  stage: build
  tags:
    - vue
  script:
    - yarn build

Deployment project:
  stage: deploy
  image: docker
  tags:
    - vue
  script:
  	Build a mirror of the project from dockerfile
    - docker build -t rainbow-system .
    Delete the container if it exists
    - if [ $(docker ps -aq --filter name=rainbow-admin-system) ]; then docker rm -f rainbow-admin-system; fi
    Create a rainbow-net network adapter for the container. Create a rainbow-net network adapter for the container
    - docker run -d --net rainbow-net --ip 192.1681.2. --name rainbow-admin-system rainbow-system

Clean up the docker:
  stage: clear
  image: docker
  tags:
    - vue
  script:
    - if [ $(docker ps -aq | grep "Exited" | awk '{print $1 }') ]; then docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }'); fi
    - if [ $(docker ps -aq | grep "Exited" | awk '{print $1 }') ]; then docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }'); fi
    - if [ $(docker images | grep "none" | awk '{print $3}') ]; then docker rmi $(docker images | grep "none" | awk '{print $3}'); fi
Copy the code

Take a look at the main application again, skip the repetitions and get straight to the point

Deployment project:
  stage: deploy
  image: docker
  tags:
    - vue3
  script:
    - docker build -t rainbow-admin .
    - if [ $(docker ps -aq --filter name=rainbow-admin-main) ]; then docker rm -f rainbow-admin-main; fi
    Give the container a network card, rainbow-net, and give it an IP address. Then connect to the child application created by --link.
    - docker run -d -p 80: 80 --net rainbow-net --ip 192.1681.1. --link 192.1681.2. --name rainbow-admin-main rainbow-admin
Copy the code

The following command is generated for the docker custom nic:

$docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 rainbow-netCopy the code

conclusion

Up to now, we have achieved the automatic deployment of Qiankun + Docker with Gitlab-CI/CD. We encountered many difficulties in the process and came up with a relatively reasonable solution. Welcome to discuss any questions.