遇饮酒时须饮酒,得高歌处且高歌
文章目录
什么是前端微服务
一个主应用(基座),内部嵌套了多个子应用
前端微服务是一种创新的技术手段与方法策略,其核心在于通过多个团队独立发布功能,共同构建现代化 Web 应用。通俗来说,就是一个 Web 应用中独立运行另一个 Web 应用的效果,极大地提升了应用开发的灵活性与可维护性。
主流框架概述
无界 - 腾讯
腾讯在维护的,后起之秀,非常轻量,4k star,无痛接入 vite。
官网: https://wujie-micro.github.io/doc/
- 优势:
- 低成本: 主应用和子应用的适配成本都极低。
- 高速度: 支持静态资源预加载和子应用预执行,提升首屏打开速度和运行速度。
- 原生隔离: 通过Web Components + Shadow DOM实现css原生隔离,通过iframe实现js原生隔离。
- 强大功能: 支持子应用保活、多应用激活、去中心化通信、vite框架支持、应用共享等。
- 劣势:
- 相对较新: 相较于 Qiankun.js 和 MicroApp,Wujie 较为新颖,社区和生态系统尚在发展中。
- 文档和支持: 由于其新颖性,文档和支持可能不如 Qiankun.js 完善。
乾坤 - 阿里
该框架是蚂蚁在维护的,15.7k star,目前官方使用的是 webpack 作为构建工具,没有明确表示支持 vite,社区有 vite-plugin-qiankun 插件支持。
官网: https://qiankun.umijs.org/zh/
- 优势:
- 成熟度高: Qiankun.js 基于 Single-SPA,并针对中国开发者进行了优化和本地化,已经得到了广泛的应用和验证。
- 生态系统完善: 提供了丰富的插件和工具链,支持快速集成和上手。
- 灵活性强: 可以自由选择是否使用沙箱隔离、应用加载策略等,满足不同场景需求。
- 社区活跃: 有较多的社区资源和支持,问题解决较快。
- 劣势:
- 复杂度高: 由于其功能强大,配置和使用相对复杂,对开发者的技术要求较高。
- 性能开销: 在某些场景下,沙箱隔离机制会带来一定的性能开销。
- 侵入性: 对子应用改造成本较大,从 webpack、代码、路由等等都要做一系列的适配。
Micro-app
由京东开发,star 5.5k, 是一个基于WebComponents的前端微服务框架,支持多种前端框架。
官网: https://jd-opensource.github.io/micro-app/
- 优势:
- 轻量级: MicroApp 体积小,性能较好,适合对性能要求较高的项目。
- 简单易用: 上手简单,API 设计清晰,开发成本低。
- 灵活性强: 提供灵活的加载和卸载机制,支持动态应用加载。
- 劣势:
- 功能较少: 功能相对较少,不支持某些高级特性。
- 兼容性差: 对于不支持WebComponents的浏览器没有做降级处理。
Vue3项目引用
⑴. 项目依赖安装
# 安装依赖
npm i wujie-vue3 -S
⑵. main.ts 文件配置
修改 main.ts
文件:
// 引入wujie
import WujieVue from 'wujie-vue3'
...
app.use(WujieVue)
...
⑶. 路由配置
修改 router.ts
文件:
# 略
⑷. 页面设置
修改 index.vue
文件,嵌入子应用:
<template>
<WujieVue
width="100%"
height="100%"
name="subApp"
:url="subAppUrl"
:props="{ proxy: true }"
:preload="true"
/>
</template>
<script lang="tsx" setup>
import WujieVue from 'wujie-vue3'
const subAppUrl= ref('http://localhost:3001')
</script>
隐藏子应用菜单及顶部信息栏
在代码中添加如下判断逻辑,可隐藏主应用不需要的子应用模块
if ((window as any).__POWERED_BY_WUJIE__) {
// 处理子应用 - 可以隐藏主应用不需要的模块
...
}
子应用样式冲突问题
通过组合使用 Shadow DOM 隔离、CSS 作用域重写和命名约束,可系统性规避样式冲突
<template>
<WujieVue
width="100%"
height="100%"
name="biShengApp"
:url="biShengUrl"
:props="{
proxy: true,
sandbox: {
strictStyleIsolation: true, // 启用严格样式隔离
scopedCSS: true, // 启用CSS作用域
experimentalStyleIsolation: true, // 实验性样式隔离
shadowDOM: false, // 是否使用Shadow DOM隔离(可能有兼容性问题)
}
}"
:preload="true"
/>
</template>
- strictStyleIsolation: 确保基本的样式隔离机制仍然有效。
- scopedCSS : 将子应用的样式限制在其容器内,防止样式泄漏到主应用或影响其他子应用。
- experimentalStyleIsolation: 实验性的样式隔离功能,提供额外的样式隔离保障。
- shadowDOM: 一种更强大的隔离方式,但可能会有兼容性问题,默认关闭。如果其他隔离方式不起作用,可以尝试将其设为 true。
虚拟路由
⑴. 路由
修改 router.ts
文件:
# 略
⑵. 页面
添加路由监听逻辑,根据路由变化动态设置子应用地址
<template>
<WujieVue
width="100%"
height="100%"
name="subApp"
:url="subAppUrl"
:props="{ proxy: true }"
:preload="true"
/>
</template>
<script lang="tsx" setup>
import WujieVue from 'wujie-vue3'
const route = useRoute()
const subAppUrl= ref('http://localhost:3001')
onBeforeMount(() => {
// 监听路由变化
if (route.path == '/subApp(router配置的url前缀)/') {
subAppUrl.value = `http://localhost:3001`
} else {
// 将router中配置的路由拼接成子应用的路由地址
subAppUrl.value = `http://localhost:3001/${route.path.replace('/subApp(router配置的url前缀)/', '')}`
}
})
</script>
跨域问题解决方案
开发环境
主应用
修改 vite.config.ts
文件:
...
return {
base: env.VITE_BASE_PATH,
root: root,
// 服务端渲染
server: {
port: env.VITE_PORT, // 端口号
host: "0.0.0.0",
open: env.VITE_OPEN === 'true',
// 本地跨域代理. 目前注释的原因:暂时没有用途,server 端已经支持跨域
// proxy: {
// ['/admin-api']: {
// target: env.VITE_BASE_URL,
// ws: false,
// changeOrigin: true,
// rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''),
// },
// },
},
子应用
- 服务端:
- 接口跨域:接受跨域请求
- 接口凭证:‘true’改为‘false’
生产环境
主应用:
编辑 nginx.conf
文件:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 9527;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
# 主应用接口转发
location ^~/admin-api/ {
proxy_pass http://localhost:48080/;
client_max_body_size 2000m;
proxy_send_timeout 1800s;
send_timeout 1800s;
# 增强headers配置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 优化cookie处理
proxy_cookie_domain localhost:48080 $host;
proxy_cookie_path / "/; secure; HttpOnly"; # 确保安全属性
# 禁用缓存,确保每次请求都到达后端
proxy_no_cache $cookie_session $http_pragma $http_authorization;
proxy_cache_bypass $cookie_session $http_pragma $http_authorization;
# 增强跨域配置
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE' always;
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,Set-Cookie' always;
add_header Access-Control-Expose-Headers 'Set-Cookie' always;
# OPTIONS请求处理
if ($request_method = 'OPTIONS') {
return 204;
}
# 日志配置
access_log /var/log/nginx/admin-api.access.log;
error_log /var/log/nginx/admin-api.error.log debug;
# 增加请求体缓冲,防止大请求被截断
proxy_request_buffering on;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
子应用:
配置静态资源缓存、反向代理、跨域等相关内容
编辑 nginx.conf
文件:
# 在http区域内一定要添加下面配置, 支持websocket
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 3001;
server_name localhost;
#root /usr/share/nginx/html/platform;
# docker host html files,使用外部文件,开发调试时用
root /usr/share/nginx/hosthtml/platform;
gzip on;
gzip_comp_level 2;
gzip_min_length 1000;
gzip_types text/xml text/css;
gzip_http_version 1.1;
gzip_vary on;
gzip_disable "MSIE [4-6] \.";
# 静态资源缓存配置,同时包含CORS头
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public";
add_header Access-Control-Allow-Origin "*" always;
add_header Content-Security-Policy "frame-ancestors *";
}
# 根路径,包含CORS头
location / {
try_files $uri $uri/ /index.html =404;
add_header Access-Control-Allow-Origin "*" always;
add_header Content-Security-Policy "frame-ancestors *";
}
# 后端服务反向代理,不需要包含CORS头,因为后端服务中已经开启跨域
location /api {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "$http_origin" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Max-Age 1728000 always;
add_header Vary "Origin" always;
add_header Content-Length 0 always;
add_header Content-Type "text/plain; charset=utf-8" always;
return 204; # 返回 204 No Content
}
proxy_pass http://backend:7860;
proxy_read_timeout 300s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
client_max_body_size 50m;
# 添加必要的CORS头
add_header Access-Control-Allow-Origin "$http_origin" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
add_header Vary "Origin" always;
add_header Content-Security-Policy "frame-ancestors *";
}
location /workspace/ {
alias /usr/share/nginx/html/client/;
index index.html index.htm;
try_files $uri $uri/ /workspace/index.html;
add_header Access-Control-Allow-Origin "*" always;
add_header Content-Security-Policy "frame-ancestors *";
}
location /workspace/api {
rewrite ^/workspace(/.*)$ $1 break;
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "$http_origin" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Max-Age 1728000 always;
add_header Vary "Origin" always;
add_header Content-Length 0 always;
add_header Content-Type "text/plain; charset=utf-8" always;
return 204; # 返回 204 No Content
}
proxy_pass http://backend:7860;
proxy_read_timeout 300s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
client_max_body_size 50m;
# 添加必要的CORS头
add_header Access-Control-Allow-Origin "$http_origin" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
add_header Vary "Origin" always;
add_header Content-Security-Policy "frame-ancestors *";
}
location ~ ^/(workspace/subApp|subApp|tmp-dir)/ {
rewrite ^/workspace(/.*)$ $1 break;
proxy_pass http://minio:9000;
add_header Access-Control-Allow-Origin "*" always;
add_header Content-Security-Policy "frame-ancestors *";
}
}
Nginx转发(未实现)
实现思路:通过主应用配置子应用的url地址(例:/subAppUrl),Nginx监测到该地址时,将静态资源及api进行转发到真实地址(例:http://xxx.xxx.com)。
主应用
修改 nginx.conf
文件:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 性能优化
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
server {
listen 9527;
server_name localhost;
# 全局 CORS 配置
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header Access-Control-Max-Age 1728000 always;
# 处理预检请求
location ~* ^/ {
if ($request_method = 'OPTIONS') {
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location ^~ /admin-api {
client_max_body_size 100M;
client_body_buffer_size 100M;
# 修正代理路径
proxy_pass http://localhost:48080/admin-api;
# 完整的代理头设置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# 关键:传递所有客户端请求头
proxy_set_header Accept-Encoding "";
proxy_set_header Origin "";
proxy_pass_header Set-Cookie;
# 禁用缓存
proxy_buffering off;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 150s;
# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 调试日志
access_log logs/admin-api.access.log;
error_log logs/admin-api.error.log debug;
}
# 微前端子应用代理配置
location ^~ /subAppUrl {
# 禁用缓存,确保实时获取内容
proxy_buffering off;
add_header X-Proxy-Location "sub-app";
# 重要的代理头设置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket支持(如果微前端需要)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 调试日志
access_log logs/sub-app.access.log;
error_log logs/sub-app.error.log debug;
# 转发请求到微前端应用服务器
rewrite ^/subAppUrl(.*) $1 break;
proxy_pass http://你的真实地址:端口号;
}
# 静态资源缓存配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 7d;
add_header Cache-Control "public";
}
}
}
页面
修改 index.vue
文件:
<template>
<WujieVue
width="100%"
height="100%"
name="subApp"
:url="subAppUrl"
:props="{ proxy: true }"
:preload="true"
/>
</template>
<script lang="tsx" setup>
import WujieVue from 'wujie-vue3'
const route = useRoute()
// Nginx 配置(location^~ /subAppUrl)一致
const subAppUrl= ref()
watch(
() => route.fullPath,
(newPath) => {
subAppUrl.value = newPath
// url地址为: /subapp/、/sbuapp/system、/subapp/log...
},
{ immediate: true }
)
</script>