项目A与项目B合并:使用qiankun的实现步骤
本文将介绍如何使用qiankun将一个已开发的项目B合并到项目A中,并解决合并过程中的常见问题。我们将涵盖以下几个方面:
- 配置项目A(主项目)
- 配置项目B(子项目)
- 在特定页面展示项目B
- 通过主项目修改子项目的样式,隐藏菜单、头部等
- 实现样式隔离
- 解决样式隔离后子项目弹框样式消失问题
- 主项目路由切换时注销子项目,避免控制台报错
- 解决路由
/undefined
错误
1. 配置项目A(主项目)
首先,确保主项目安装了qiankun:
npm install qiankun
然后在主项目的页面文件中配置qiankun,手动加载项目B:
<script setup lang="ts">
import { loadMicroApp, start } from 'qiankun'
const renderEl = ref()
const loading = ref(true)
const microApp = ref()
onMounted(() => {
microApp.value = loadMicroApp(
{
name: 'youproject', // 注册子应用名称
entry: '//localhost:8080/vue', // 子应用入口地址
container: renderEl.value, // 渲染容器
}
)
start() // 启动qiankun
})
</script>
<template>
<div ref="renderEl" v-loading="loading" class="micro-app-container"></div>
</template>
<style lang="scss">
.micro-app-container {
height: 600px; // 适配子应用显示的高度
}
</style>
2. 配置项目B(子项目)
在项目B的vue.config.js
中添加qiankun的配置:
const { name } = require('./package.json')
// 注意 要找到指定的配置 添加进去 这里的配置只是你要添加的
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*', // 解决跨域问题
}
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
},
}
在main.js
同级目录创建public-path.js
文件:
这里要强调一下public-path.js很重要啊, 它直接决定了子应用的静态资源路径的前缀,
它一般是入口文件地址,如 entry: '//localhost:8080/vue'
那么window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
就是//localhost:8080/
entry: '//localhost:8080/vue/'
那么
window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
就是//localhost:8080/vue/
注意第二次entry后面多了个斜杠
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
接着在main.js
中引入public-path.js
并配置渲染函数:
//放在第一行
import './public-path'
/*
注意注意, render方法其实就是对你实例化Vue的封装了一下, 如果是在qiankun环境
props会有值的, 能接收到一个container容器其实就是一个dom
然后你的项目会被插入到container容器里面,避免把子项目渲染到父页面去,所以才弄了个render方法
*/
function render(props = {}) {
const { container } = props
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/', // 注意注意!,这里, 如果是乾坤环境路由添加了一个/vue基础路径, 如果你也添加/vue,那么主项目entry 也要添加上
mode: 'history',
routes,
})
app = new Vue({ el: container ? container.querySelector('#app') : '#app', render: h => h(App), router, i18n, store })
}
if (!window.__POWERED_BY_QIANKUN__) {
console.log('独立运行')
render()
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped')
}
export async function mount(props) {
console.log('[vue] props from main framework', props)
render(props)
}
export async function unmount() {
console.log('注销vue app')
app.$destroy()
app.$el.innerHTML = ''
app = null
router = null
console.log('注销成功')
}
3. 在特定页面展示项目B
刚才我也说了 我用的是手动加载路由,也就是说在特定的页面路径下才会加载项目B
比如项目B有一个页面路径是 /vue/about
那么你项目A也要构建出来一个路由 /vue/about 里面的页面文件内容就是调用loadMicroApp方法加载项目B
其中因为/vue是项目B在乾坤环境运行时的基础路径那么 entry 就是 //localhost:8080/vue
4. 通过主项目修改子项目的样式,隐藏菜单和头部
子项目B可能会有自己的菜单和头部,若只需要展示其内容区域,可以通过两种方式隐藏不需要的部分:
方案一:修改子项目代码
在子项目B的代码中,判断是否是qiankun环境,然后隐藏菜单和头部。
方案二:在主项目中注入样式
建议通过主项目注入样式来隐藏这些元素,这样可以确保子项目代码的独立性。
microApp.value = loadMicroApp(
{
name: 'youproject',
entry: '//localhost:8080/vue',
container: renderEl.value,
},
{
sandbox: {
strictStyleIsolation: true, // 启用样式隔离
},
},
{
async beforeLoad(app, global) {
const customStyle = global.document.createElement('style')
customStyle.innerHTML = `
.console__header, .console-side-nav {
display: none;
}
#app .filterable-index-page-template {
background-color: transparent;
}
`
global.document.querySelector('head')?.appendChild(customStyle)
},
}
)
5. 实现样式隔离
在加载子项目时,启用strictStyleIsolation
来进行样式隔离,避免主项目和子项目之间的样式冲突:
microApp.value = loadMicroApp(
{
name: 'youproject',
entry: '//localhost:8080/vue',
container: renderEl.value,
},
{
sandbox: {
strictStyleIsolation: true,
},
}
)
6. 解决样式隔离后子项目弹框样式消失问题
由于样式隔离,子项目的弹框样式可能无法正确显示。解决方法是重新定义body.appendChild
方法,将弹框插入到主应用的body
中:
microApp.value = loadMicroApp(
{
name: 'youproject',
entry: '//localhost:8080/vue',
container: renderEl.value,
},
{
sandbox: {
strictStyleIsolation: true,
},
},
{
async afterMount(app, global) {
const shadowRoot = document.querySelector(`[data-name="${app.name}"]`)?.shadowRoot
const originAppendChild = global.document.body.appendChild
const getComputedStyle = global.window.getComputedStyle
Object.defineProperty(global.window, 'getComputedStyle', {
value(node) {
if (node instanceof ShadowRoot) {
const shadowBody = node.querySelector('div') || document.body
return getComputedStyle.call(this, shadowBody)
} else {
return getComputedStyle.call(this, node)
}
},
})
Object.defineProperty(global.document.body, 'appendChild', {
value(node) {
if (shadowRoot) {
return shadowRoot.querySelector('div')?.appendChild(node)
}
return originAppendChild.call(this, node)
},
})
},
}
)
7. 主项目切换路由时需要手动注销
如果你发现主项目切换路由时出现错误single-spa minified message #31
,这是因为没有在路由切换时正确注销子应用。你可以在路由切换前手动注销子项目:
onBeforeUnmount(() => {
microApp.value?.unmount()
})
8. 解决路由/undefined
错误
如果在项目B的路由跳转时出现/undefined
错误,请参考我的另一篇文章使用qiankun路由跳转报错/undefined进行解决。
好啦,到此所有的问题都解决啦!