一 什么是微前端
3、qiankun :基于Single-Spa,阿里系开源微前端框架
和 single-spa 一样,qiankun 也能给我们提供类似单页应用的用户体验。qiankun 是在 single-spa 的基础上做了二次开发,在框架层面解决了使用 single-spa 时需要开发人员自己编写子应用加载、通信、隔离等逻辑的问题,是一种比 single-spa 更优秀的微前端方案。
优点:
切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验;
相比 single-spa,解决了子应用加载、应用隔离、子应用通信等问题,使用起来相对简单;
完全和技术栈无关;
多个子应用可并存;
缺点:
需要对原有应用进行改造,应用要兼容接入 qiankun 和独立使用;
有额外的学习成本;
相同资源重复加载;
启动应用时,要先启动基座应用;
1. 安装qiankun
npm i qiankun -S
2. 在主应用中注册微应用
src/qiankun/index.ts
文件,进行单独的抽离import { registerMicroApps,setDefaultMountApp} from "qiankun";
const microApps = [
{
// - 必选,微应用的名称,微应用之间必须确保唯一
name: "vue-app",
// - 必选,微应用的入口
entry: import.meta.env.MODE === 'development'? '//localhost:9001/' : '/subPath/',
// - 必选,微应用的容器节点的选择器或者 Element 实例
container: "#subApp",
// - 必选,微应用的激活规则
//支持直接配置字符串或字符串数组,如 activeRule: '/app1' 或 activeRule: ['/app1', '/app2'],当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。
//支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 location => location.pathname.startsWith('/app1')。
activeRule: "/subPath", //匹配所有以/subPath开头的为子应用
//loader - (loading: boolean) => void - 可选,loading 状态发生变化时会调用的方法。
//可选,主应用需要传递给微应用的数据。
props: {
_parent_base: '/subPath/',
msg:'这是主应用传给子应用的消息'
},
},
];
// 乾坤提供的子应用生命周期钩子 可用于加载loading
const mount={
beforeLoad: () => {
//开始加载loading
console.log('子应用加载前')
},
beforeMount: () => {
console.log('子应用挂载前')
},
afterMount: () => {
//结束加载loading
console.log('子应用挂载后')
},
beforeUnmount: () => {
console.log('子应用卸载前')
},
afterUnmount: () => {
console.log('子应用卸载后')
},
}
//注册子应用
registerMicroApps(microApps,mount);
//设置默认启动子应用
setDefaultMountApp('/subPath');
//启动qiankun | 不可重复启动 | 如果子应用入口在app.vue里可以在这启动否则会报错找不到子应用结点
//start()
main.ts
导入import "./qiankun"
//其他配置不变
<el-main>
<router-view>router-view>
<div id="subApp">div>
el-main>
onMounted(() => {
start()
})
import { defineConfig } from 'vite'
export default defineConfig(({ command, mode }) => {
return {
//········
server: {
host: '0.0.0.0',
port: 9003,
hmr: true,
disableHostCheck: true,
proxy: {
'/api': { //主服务代理
target: 'http://192.168.3.101:1171/', //服务
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
'/subApi': { //子应用代理
target: 'http://192.168.3.101:3434/', //服务
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
}
})
3. 子应用中使用
vite-plugin-qiankun
npm install vite-plugin-qiankun
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'
export default ({ mode }) => {
return defineConfig({
/**
* 代理的问题:微应用打包的 base 必须跟主应用中的代理地址 entry 值一致
* 但是,加上 '/subPath 之后,就必须部署 '/subPath 目录,否则无法独立访问。
*/
base: mode === 'development' ? 'http://localhost:9001/' : '/subPath/',
plugins: [
vue(),
qiankun('vue-app', { // 微应用名字,与主应用注册的微应用名字保持一致
useDevMode: true
})
],
server: {
headers: {
'Access-Control-Allow-Origin': '*', //防止主应用访问跨域
},
host: '0.0.0.0',
port: 9001,
origin: 'http://localhost:9001', //防止去主应用请求静态资源出现404
hmr: true,
disableHostCheck: true,
proxy: {
'/api': {
target: 'http://192.168.3.101:3434/', //服务
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
}
})
main.ts
import { App as VueApp, createApp } from 'vue'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
let app: VueApp<Element>
//判断是否为qiankun环境
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
//自立独立环境正常挂在加载
// router(''),独立运行,路由前缀为空
createApp(App).use(router('')).use(store).use(iweb).use(dispatchEventStrage).mount('#app')
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
createApp(App).component(key, component)
}
createApp(App).directive('load', vLoading)
} else {
//qiankun环境
renderWithQiankun({
mount(props) {
//props为主应用传过来的data
app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.directive('load', vLoading)
//正常注册 最后挂载在主应用上
//路由前缀添加router(props._parent_base),_parent_base是从主应用中传过来的
app
.use(router)
.use(store(props._parent_base))
.use(iweb)
.use(dispatchEventStrage)
.mount(
(props.container
? props.container.querySelector('#app') //挂载在主应用
: document.getElementById('app')) as Element
)
},
bootstrap() {
console.log('--app 01 bootstrap')
},
update() {
console.log('--app 01 update')
},
unmount() {
console.log('--app 01 unmount')
app?.unmount() //离开时卸载
},
})
}
axios.ts
中代理import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
let baseURL: string = ''
if (!qiankunWindow.__POWERED_BY_QIANKUN__) { //独立运行
baseURL = '/api'
} else {
baseURL = '/subApi' //微应用与主应用中配置的子应用代理一致
}
主应用nginx/web
子应用nginx/web/subPath
(subPath
为子应用打包base对应的目录)
server { #主应用
listen 88;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root web;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
#主应用代理
location /api/ {
proxy_pass http://192.168.3.101:1171/;
}
#代理到子应用
location /p1800/ {
proxy_pass http://localhost:89/p1800/;
}
#子应用的代理 1
location /subApi/ {
proxy_pass http://192.168.3.101:3434/;
}
}
server { #子应用
listen 89;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location /p1800 { #对应文件夹名称
root web;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /api/ {
proxy_pass http://192.168.3.101:3434/;
}
}