Dachang Technology Advanced Front-end Node Advanced
Click on the top programmer's growth guide, pay attention to the public number
Reply 1, join the advanced Node exchange group
Original link: <https://juejin.cn/post/6981339862901194759?
Author: Rainbow on Paper
Introduction to the Technology Stack
micro frontend
qiankun
docker
gitlab-ci/cd
nginx
If you don't understand the article after reading the article, you can cooperate with [video explanation to view this article] Video address: https://www.bilibili.com/video/BV1Qg411u7C9
What is a micro frontend
Micro front-end is a technical means and method strategy for multiple teams to jointly build modern web applications by independently publishing functions.
The micro-frontend architecture has the following core values:
Technology stack independent The main framework does not restrict access to the application technology stack, and micro-applications have full autonomy
Independent development and independent deployment The micro-application warehouse is independent, and the front and back ends can be developed independently. After the deployment is completed, the main framework automatically completes the synchronization update
Incremental upgrade
In the face of various complex scenarios, it is usually difficult for us to upgrade or refactor an existing system in full, and micro-frontend is a very good means and strategy for implementing incremental refactoring.
When running independently, the state between each micro-application is isolated, and the runtime state is not shared
what is qiankun
qiankun is a production-ready micro-frontend framework. It is based on single-spa and has the capabilities required by micro-frontend systems such as js sandbox, style isolation, HTML Loader, and preloading. qiankun can be used in any js framework, and micro-app access is as simple as embedding an iframe system.
qiankun's core design philosophy
Reference address: qiankun.umijs.org/zh/guide
Simple
Since the main application micro-application can be independent of the technology stack, qiankun is just a jQuery-like library for users. You need to call several qiankun APIs to complete the micro-frontend transformation of the application. At the same time, due to the design of qiankun's HTML entry and sandbox, the access of micro-applications is as simple as using iframe.
Decoupling/tech stack agnostic
The core goal of the micro-frontend is to disassemble the monolithic application into several loosely coupled micro-applications that can be autonomous, and many designs of qiankun adhere to this principle, such as HTML entry, sandbox, and inter-application communication. Only in this way can micro-applications truly have the ability to develop and run independently.
Why not use Iframe
Reference address: www.yuque.com/kuitos/gky7…
If you don't consider experience issues, iframes are almost the perfect micro-frontend solution.
The biggest feature of iframe is to provide the browser's native hard isolation solution, whether it is style isolation, js isolation and other problems can be perfectly solved. But his biggest problem is that his isolation cannot be broken, resulting in the inability to share the context between applications, and the resulting development experience and product experience.
The url is out of sync. When the browser refreshes the iframe url, the state is lost, and the back and forward buttons cannot be used.
UI is not synchronized, DOM structure is not shared. Imagine a pop-up box with a mask layer in the iframe in the lower right corner of the screen. At the same time, we require the pop-up box to be displayed in the center of the browser and automatically center when the browser is resized.
The global context is completely isolated, and memory variables are not shared. For the communication and data synchronization requirements of the internal and external systems of the iframe, the cookie of the main application should be transparently transmitted to the sub-applications with different root domain names to achieve the free login effect.
slow. Each entry of a sub-application is a process of rebuilding the browser context and reloading resources.
Some of the problems are easy to solve (Problem 1), some problems we can turn a blind eye to (Problem 4), but some problems are difficult for us to solve (Problem 3) or even impossible to solve (Problem 2) , and these unsolvable problems will bring very serious experience problems to the product, which eventually led us to abandon the iframe solution.
The core value of micro frontend
www.yuque.com/kuitos/gky7…
project idea
Before talking about the specific technical implementation, let's take a look at what we want to achieve.
Micro front-end schematic
Sub-apps are dynamically loaded based on clicks on the main app's navigation
Deployment logic
There are many ideas for deployment, I will talk about the ways I have tried here:
Only use one nginx container, deploy multiple applications by listening to different ports, and then add the corresponding routing proxy to the sub-application in the port of the main application
This method is the simplest but not suitable for automated deployment of gitlab-ci/cd, so I just initially tested the implementation of nginx deployment micro-frontend
Use multiple nginx containers, each container exposes a port, and then add the corresponding routing proxy to the sub-application through the main application
This method can be implemented, but it will expose multiple ports on the server, the security will be reduced, and the external can also directly access the sub-application through the port
Use multiple nginx containers, only expose the port of the main application, the main application connects to the sub-application, and then accesses through the nginx proxy
This method is the most ideal, only one port needs to be exposed, all agents are between the containers, and they are insensitive to the outside world. The following is an illustration of the implementation.
qiankun
install qiankun
$ yarn add qiankun # 或者 npm i qiankun -S
Register the microapp in the main app
import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from 'qiankun';
const apps = [
{
name: 'ManageMicroApp',
entry: '/system/', // 本地开发的时候使用 //localhost:子应用端口
container: '#frame',
activeRule: '/manage',
},
]
/**
* 注册微应用
* 第一个参数 - 微应用的注册信息
* 第二个参数 - 全局生命周期钩子
*/
registerMicroApps(apps,{
// qiankun 生命周期钩子 - 微应用加载前
beforeLoad: (app: any) => {
console.log("before load", app.name);
return Promise.resolve();
},
// qiankun 生命周期钩子 - 微应用挂载后
afterMount: (app: any) => {
console.log("after mount", app.name);
return Promise.resolve();
},
});
/**
* 添加全局的未捕获异常处理器
*/
addGlobalUncaughtErrorHandler((event: Event | string) => {
console.error(event);
const { message: msg } = event as any;
// 加载失败时提示
if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
console.error("微应用加载失败,请检查应用是否可运行");
}
});
start();
After the micro-application information is registered, once the url of the browser changes, the matching logic of qiankun will be automatically triggered, and all the micro-applications matched by the activeRule rule will be inserted into the specified container, and the micro-applications will be called in turn to expose the lifecycle hooks.
If the micro application is not directly associated with the route, you can also choose to manually load the micro application:
import { loadMicroApp } from 'qiankun';
loadMicroApp({
name: 'app',
entry: '//localhost:7100',
container: '#yourContainer',
});
Micro application
Micro-apps can access the qiankun main application without installing any other dependencies.
1. Export the corresponding lifecycle hooks
bootstrap
Micro applications need to export , mount
, and unmount
three lifecycle hooks in their own entry js (usually the entry js of your configured webpack) for the main application to call at the appropriate time.
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;
// 如果子应用独立运行则直接执行render
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
/**
* 渲染函数
* 主应用生命周期钩子中运行/子应用单独启动时运行
*/
function render(props = {}) {
// SharedModule用于主应用于子应用的通讯
// 当传入的 shared 为空时,使用子应用自身的 shared
// 当传入的 shared 不为空时,主应用传入的 shared 将会重载子应用的 shared
const { shared = SharedModule.getShared() } = props;
SharedModule.overloadShared(shared);
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/manage/' : '/',
mode: 'history',
routes
});
// 挂载应用
instance = new Vue({
router,
render: (h) => h(App)
}).$mount('#app');
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('vue app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log('vue mount', props);
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log('vue unmount');
instance.$destroy();
instance = null;
router = null;
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
The above code also references a public-path
file:
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
This mainly solves the problem of incorrect addresses of scripts, styles, pictures, etc. dynamically loaded by micro-applications.
2. Configure the packaging tool for the micro-app
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/', //这里打包地址都要基于主应用的中注册的entry值
output: {
library: 'ManageMicroApp', // 库名,与主应用注册的微应用的name一致
libraryTarget: 'umd', // 这个选项会尝试把库暴露给前使用的模块定义系统,这使其和CommonJS、AMD兼容或者暴露为全局变量。
jsonpFunction: `webpackJsonp_${packageName}`,
},
};
Summary of key points
Configuration when the main application is registered
const apps = [ { name: 'ManageMicroApp', entry: '/system/', // http://localhost/system/ 这里会通过nginx代理指向对应的子应用地址 container: '#frame', activeRule: '/manage', }, ]
When the main application registers the micro application, it
entry
can be a relative path,activeRule
not theentry
same as (otherwise the main application page refreshes and it becomes a micro application)base of vue routing
router = new VueRouter({ base: window.__POWERED_BY_QIANKUN__ ? '/manage/' : '/', mode: 'history', routes });
If it is called by the main application, the base of the route is
/manage/
webpack packaging configuration
module.exports = { publicPath: '/system/', };
For the
webpack
built micro application, thewebpack
package of the micro applicationpublicPath
needs to be configured as/system/
, otherwise the micro applicationindex.html
can be requested correctly, butindex.html
thejs/css
path micro application will not be brought/system/
.
At this point, we have completed the configuration of the micro-frontend, and the next step is the configuration of nginx.
Production environment Nginx configuration
First hang up the nginx configuration of 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;
}
# 前面我们配置的子应用entry是/system/,所以会触发这里的代理,代理到对应的子应用
location /system/ {
# -e表示只要filename存在,则为真,不管filename是什么类型,当然这里加了!就取反
if (!-e $request_filename) {
proxy_pass http://192.168.1.2; # 这里的ip是子应用docker容器的ip
}
# -f filename 如果 filename为常规文件,则为真
if (!-f $request_filename) {
proxy_pass http://192.168.1.2;
}
# docker运行的nginx不识别localhost的 所以这种写法会报502
# proxy_pass http://localhost:10200/;
proxy_set_header Host $host;
}
location /api/ {
proxy_pass http://后台地址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 {
}
}
Take a look at the sub-app
server {
listen 80;
listen [::]:80 default_server;
server_name _2;
root /usr/share/nginx/html;
# 这里必须加上允许跨域,否则主应用无法访问
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_pass http://后台地址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 {
}
}
dockerfile configuration
Here's a look at the sub-application
# 直接使用nginx镜像
FROM nginx
# 把上面配置的conf文件替换一下默认的
COPY nginx.conf /etc/nginx/nginx.conf
# nginx默认目录下需要能看见index.html文件
COPY dist/index.html /usr/share/nginx/html/index.html
# 再回头看一下部署逻辑图和qiankun注意点,必须要把所有的资源文件放到system文件下index.html才能正确加载
COPY dist /usr/share/nginx/html/system
Look at the main application again
# 这里主应用没有直接使用nginx,因为nginx反向代理的/api/会出现404的问题,原因未知!
FROM centos
# 安装nginx
RUN yum install -y nginx
# 跳转到/etc/nginx
WORKDIR /etc/nginx
# 替换配置文件
COPY nginx.conf nginx.conf
# 跳转到/usr/share/nginx/html
WORKDIR /usr/share/nginx/html
# 主应用正常打包,所以直接把包放进去就行
COPY dist .
# 暴露80端口
EXPOSE 80
# 运行nginx
CMD nginx -g "daemon off;"
gitlab-ci/cd configuration
Let's take a look at the sub-application first, and only talk about the key points
image: node
stages:
- install
- build
- deploy
- clear
cache:
key: modules-cache
paths:
- node_modules
- dist
安装环境:
stage: install
tags:
- vue
script:
- npm install yarn
- yarn install
打包项目:
stage: build
tags:
- vue
script:
- yarn build
部署项目:
stage: deploy
image: docker
tags:
- vue
script:
# 通过dockerfile构建项目的镜像
- docker build -t rainbow-system .
# 如果存在之前创建的容器先删除
- if [ $(docker ps -aq --filter name=rainbow-admin-system) ];then docker rm -f rainbow-admin-system;fi
# 通过刚刚的镜像创建一个容器 给容器指定一个网卡rainbow-net,这个网卡是我们自定义,创建方式后面会说,然后给定一个ip
- docker run -d --net rainbow-net --ip 192.168.1.2 --name rainbow-admin-system rainbow-system
清理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
Look at the main application again, omit the repetition, and focus directly on the key points
部署项目:
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
# 给容器指定一个网卡rainbow-net,然后给定一个ip,然后通过--link与之前创建的子应用连通,重点!
- docker run -d -p 80:80 --net rainbow-net --ip 192.168.1.1 --link 192.168.1.2 --name rainbow-admin-main rainbow-admin
The above mentioned docker's custom network card, the generated command is as follows:
$ docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 rainbow-net
Summarize
So far, we have realized the automatic deployment of qiankun+docker and gitlab-ci/cd. We encountered many pits, and then came up with a relatively reasonable solution. If you have any questions, welcome to discuss.
Node 社群
我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。
如果你觉得这篇内容对你有帮助,我想请你帮我2个小忙:
1. 点个「在看」,让更多人也能看到这篇文章2. 订阅官方博客 www.inode.club 让我们一起成长
点赞和在看就是最大的支持❤️