最近负责的新项目用到了qiankun
,写篇文章分享下实战中遇到的一些问题和思考。
示例代码: github.com/fengxianqi/…。
在线demo:qiankun.fengxianqi.com/
单独访问在线子应用:
为什么要用qiankun
项目有个功能需求是需要内嵌公司内部的一个现有工具,该工具是独立部署的且是用React
写的,而我们的项目主要技术选型是vue
,因此需要考虑嵌入页面的方案。主要有两条路:
iframe
方案qiankun
微前端方案
两种方案都能满足我们的需求且是可行的。不得不说,iframe
方案虽然普通但很实用且成本也低,iframe
方案能覆盖大部分的微前端业务需求,而qiankun
对技术要求更高一些。
技术同学对自身的成长也是有强烈需求的,因此在两者都能满足业务需求时,我们更希望能应用一些较新的技术,折腾一些未知的东西,因此我们决定选用qiankun
。
项目架构
后台系统一般都是上下或左右的布局。下图粉红色是基座,只负责头部导航,绿色是挂载的整个子应用,点击头部导航可切换子应用。
参考官方的examples代码,项目根目录下有基座main
和其他子应用sub-vue
,sub-react
,搭建后的初始目录结构如下:
├── common //公共模块
├── main // 基座
├── sub-react // react子应用
└── sub-vue // vue子应用
基座是用vue
搭建,子应用有react
和vue
。
基座配置
基座main采用是的Vue-Cli3搭建的,它只负责导航的渲染和登录态的下发,为子应用提供一个挂载的容器div,基座应该保持简洁(qiankun官方demo甚至直接使用原生html搭建),不应该做涉及业务的操作。
qiankun这个库只需要在基座引入,在main.js
中注册子应用,为了方便管理,我们将子应用的配置都放在:main/src/micro-app.js
下。
const microApps = [
{
name: 'sub-vue',
entry: '//localhost:7777/',
activeRule: '/sub-vue',
container: '#subapp-viewport', // 子应用挂载的div
props: {
routerBase: '/sub-vue' // 下发路由给子应用,子应用根据该值去定义qiankun环境下的路由
}
},
{
name: 'sub-react',
entry: '//localhost:7788/',
activeRule: '/sub-react',
container: '#subapp-viewport', // 子应用挂载的div
props: {
routerBase: '/sub-react'
}
}
]
export default microApps
然后在src/main.js
中引入
import Vue from 'vue';
import App from './App.vue';
import { registerMicroApps, start } from 'qiankun';
import microApps from './micro-app';
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');
registerMicroApps(microApps, {
beforeLoad: app => {
console.log('before load app.name====>>>>>', app.name)
},
beforeMount: [
app => {
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
},
],
afterMount: [
app => {
console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name);
}
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
},
],
});
start();
在App.vue
中,需要声明micro-app.js
配置的子应用挂载div(注意id一定要一致),以及基座布局相关的,大概这样:
<template>
<div id="layout-wrapper">
<div class="layout-header">头部导航</div>
<div id="subapp-viewport"></div>
</div>
</template>
这样,基座就算配置完成了。项目启动后,子应用将会挂载到<div id="subapp-viewport"></div>
中。
子应用配置
一、vue子应用
用Vue-cli在项目根目录新建一个sub-vue
的子应用,子应用的名称最好与父应用在src/micro-app.js
中配置的名称一致(这样可以直接使用package.json
中的name
作为output)。
- 新增
vue.config.js
,devServer的端口改为与主应用配置的一致,且加上跨域headers
和output
配置。
// package.json的name需注意与主应用一致
const { name } = require('../package.json')
module.exports = {
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
}
},
devServer: {
port: process.env.VUE_APP_PORT, // 在.env中VUE_APP_PORT=7788,与父应用的配置一致
headers: {
'Access-Control-Allow-Origin': '*' // 主应用获取子应用时跨域响应头
}
}
}
- 新增
src/public-path.js
(function() {
if (window.__POWERED_BY_QIANKUN__) {
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-undef
__webpack_public_path__ = `//localhost:${process.env.VUE_APP_PORT}/`;
return;
}
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
})();
src/router/index.js
改为只暴露routes,new Router
改到main.js
中声明。- 改造
main.js
,引入上面的public-path.js
,改写render,添加生命周期函数等,最终如下:
import './public-path' // 注意需要引入public-path
import Vue from 'vue'
import App from './App.vue'
import routes from './router'
import store from './store'
import VueRouter from 'vue-router'
Vue.config.productionTip = false
let instance = null
function render (props = {}) {
const { container, routerBase } = props
const router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL,
mode: 'history',
routes
})
instance = new Vue({
router,
store,
render: (h) => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
if (!window.__POWERED_BY_QIANKUN__) {
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 () {
instance.$destroy()
instance.$e