- 作者:老汪软件技巧
- 发表时间:2024-09-23 00:01
- 浏览量:
前言
我们公司是做hrsaas的,前几个月和别的公司有合作,他们要求我们使用 qiankun 的方案作为子应用接入他们的系统。我:啥是qiankun啊? 微前端是啥,没接触过啊。没辙看看文档,硬着头皮接吧。跟着文档接入难度不大,开个页面一看惨不忍睹啊,发生了下面一系列问题,记录一下,省的忘了。
问题一:子应用的字体静态资源404问题
独立运行好好的,接入qiankun之后字体图标/图片都加载不出来了。就像这样:
导致这个问题的原因大概率是因为作为子应用运行时请求的 url 发生了变化,打开控制台可以发现路径是省略域名的,这就是导致错误的原因。
独立运行的项目的ip是 :7777/ ,省略域名请求的路径是 :7777/static/img/src/assets/images/loginBackground.png,可以拿到资源。
而主应用运行地址是 :8080/ ,请求的是 :8080/static/img/src/assets/images/loginBackground.png,自然就拿不到资源了。
所以我们只需要把路径修改正确即可,方法有很多种
我们可以在打包配置里面进行配置。例如 webpack 的 publicPath
设置完之后可以看到路径已经变成了一个完整的url,资源可以正常加载。
可以通过设置limit的大小把资源作为base64引入,缺点就是会增大打包后的体积。
直接把资源的路径写死,例如引入阿里云播放器插件时,通过网络远程下载 css 是相对路径。
在主应用时会造成播放图标加载失败,我就直接把 css 复制到本地一份把路径写死解决了。
问题二:You need to export lifecycle functions in sso entry
这个错误就是说没有找到子应用导出的生命周期,如果你确定已经在入口文件声明了并且配置都正确。那就重点检查一下打包后的html文件,检查的生命周期所在的 js 是不是最后一个加载的脚本。如果不是,需要移动顺序将其变成最后一个加载的 js,或者在 html 中将入口 js 手动标记为 entry。
我是因为分包配置,导致生命周期文件不是位于最后导致的报错
问题三:子应用如何跳转到主应用/其他子应用通过主应用的$router跳转
主应用内跳转其他子应用直接带上子应用的路由前缀跳转即可,如果我们可以把主应用的路由实例传递给子应用,子应用就可以采用相同的方式进行路由跳转了。
主应用将router实例通过props传递给子应用,子应用在render中接收。
// 主应用的main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './routers'
import './index.css'
Vue.use(VueRouter)
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'training-front',
entry: '//localhost:8081/training-front',
container: '#container',
activeRule: '/training-front',
props: {}
},
{
name: 'sso',
entry: '//localhost:7777/sso',
container: '#container',
activeRule: '/sso',
props: {}
},
{
name: 'reactApp',
entry: '//localhost:5173',
container: '#container',
activeRule: '/reactApp',
props: {}
}
],{
// qiankun 生命周期钩子 - 加载前
beforeLoad: (app) => {
console.log('加载子应用前,加载进度条=',app, app.name)
app.props.mainRouter = router
return Promise.resolve()
},
});
// 主应用跳转子应用 this.$router.push({ path: '/sso/home' }); sso是路由base,home为具体路径。
// 如果sso项目独立运行内部跳转 this.$router.push({ path: '/home' })
子应用在render中接收props
function render(props={}) {
// 主应用传的的props 可以在这里接收
const { container,mainRouter} = props;
// 这个是项目自己封装的 等同于new Vue()
createDesktopApp(
{
basePath: router.base,
mixins:[commons],
menus: [],
store,
routes: [],
modules: router.options.routes,
routerModule: {
router: Router,
beforeHooks: [beforeRoute, ...router.beforeHooks]
}
},
container ? container.querySelector('#app') : '#app'
);
// 重点是这里,mainRouter即为主应用的路由实例,全局存储之后就可以用于跳转了
Vue.prototype.$parentRouter = mainRouter
}
// 在业务中
//
pushState跳转
在子应用跳转主应用或其他子应用也可以使用 pushState 方法,pushState(state, unused, url)
div onClick={()=>window.history.pushState(null,'','/sso/home')}>通过pushState跳转到sso
window.location.href 直接跳转
这种跳转是最直接的,刷新页面跳转不容易出问题,但是体验相对不好。
window.location.href = '主应用的域名' + '/sso/home' //
问题四:主应用全局样式污染
主应用和子应用都是用element的组件库,主应用全局对element的样式进行了定制化样式覆盖。然后发现主应用的全局样式影响了子应用。
可行方案:使用postcss 插件替换css前缀
需要下载 postcss-change-css-prefix借助该插件把主应用的公共类名替换掉。
// postcss.config.js
const addCssPrefix = require('postcss-change-css-prefix')
module.exports = {
plugins: [
addCssPrefix({
prefix: 'el-',
replace: 'gp-',
}),
],
}
使用前:
使用后的效果:
qiankun提供了两种样式隔离方案,但是都不理想。
start({
// sandbox : { strictStyleIsolation: true } // 严格隔离
// sandbox : { experimentalStyleIsolation: true } // 试验性隔离
});
strictStyleIsolation 严格隔离,开启后子应用会被放入Shadow DOM 从而达到样式隔离的目的。
Shadow DOM 说明
它的隔离效果是非常好的,问题在于它会使子应用挂载到主应用 body 元素上的弹窗(element/antd的组件)找不到挂载对象。
放弃该方案
experimentalStyleIsolation 试验性隔离
开启该属性后,它会为子应用的每个样式都加上属性选择器,类似 Vue 的 scoped 。
使用发现有如下问题:
个人认为它只能达到子应用的样式不影响主应用,不能隔离主应用的全局样式。
如果弹窗挂载到了主应用的body上,还会导致样式丢失。
可能有兼容性问题,如果使用了类似:root的伪类,还会导致样式混乱。
坑有点多故放弃该方案
问题五:各个应用之间如何通信props
适合初始化时进行传值,问题三有具体用法。
onGlobalStateChange setGlobalState initGlobalState
可以动态的监听值的变化。
适用场景:
主应用设置换肤主题值发生变化,同步给子应用,子应用接收值之后进行换肤逻辑。
主应用进行退出登录发送消息给子应用,子应用进行注销token。
子应用和主应用都可以订阅或发送消息
主应用
// actions.js
import {
initGlobalState
} from 'qiankun';
// 注册微应用通信示例
const initialState = {};
const actions = initGlobalState(initialState);
export {
actions, // 导出通信示例
}
更换主题的业务逻辑
// 触发事件就发送消息
changeTheme(){
if(this.theme === 'dark'){
this.theme = 'light'
actions.setGlobalState({theme: 'light'})
}else{
this.theme = 'dark'
actions.setGlobalState({theme: 'dark'})
}
},
子应用
封装的目的是为了让整个项目只要import,就可以直接使用。
//actions.js
function emptyAction(...args) {
// 警告:提示当前使用的是空 Action
console.warn("Current execute action is empty!");
}
class Actions {
// 默认值为空 Action
actions = {
onGlobalStateChange: emptyAction,
setGlobalState: emptyAction,
};
/**
* 设置 actions
*/
setActions(actions) {
this.actions = actions;
}
/**
* 映射
*/
onGlobalStateChange(...args) {
return this.actions.onGlobalStateChange(...args);
}
/**
* 映射
*/
setGlobalState(...args) {
return this.actions.setGlobalState(...args);
}
}
const actions = new Actions();
export default actions;
子应用 main.js
function render(props = {}) {
const { container } = props;
if(container){
// 订阅消息
actions.setActions(props)
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log("state, prev", state, prev)
if(state.theme !== prev.theme){
// 换肤逻辑
}
})
}
// ... new Vue()
}
render初始化的时候,props的值
console.log("state, prev", state, prev) 输出结果如下:
问题六:js隔离问题
子应用在window添加全局变量,其他应用无法直接通过window获取,因为qiankun的js隔离机制。需要通过window.proxy.xxx 去获取。
问题七:vite + react 怎么接入qiankun
qiankun 不支持vite,大部分的解决方案都是引入 vite-plugin-qiankun
改造方案不困难 vite-plugin-qiankun 接入文档
按照文档对 react 配置完之后,在开发环境发现有下面的报错。
vite接入qiankun react需要额外处理 因为
vite.config.ts 注释掉 react()
export default defineConfig({
plugins: [
// react(), 注释掉
qiankun('reactApp', {
useDevMode: true
}),
...(
useDevMode ? [] : [
reactRefresh()
]
),
],
base: '/reactApp',
})
然后发现报错。
这是因为 react() 引入了@vitejs/plugin-react 它可以让我们在tsx中不必声明React。去掉之后我们还要在每个jsx文件都引入 import React from 'react' 。才能正常运行
还有个解决方案我没尝试但是可以参考下: /2023/10/15/…
结尾
真是踩了不少的坑才把这些项目接完,后续遇到问题再补充,希望能对大家有所帮助。