微前端接入方案:qiankun
常用api:
主、子应用都为hash模式改造:
开发环境:
主应用改造:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps(
[
{
// 微前端应用名
name: 'app1',
// 微前端启动地址
entry: '//localhost:8100/',
// 微前端挂载dom
container: '#app',
// 微前端触发路由
activeRule: '#/app1',
// 主应用向子应用传递的静态值
props: {
name: 'yuxiaoyu',
},
},
],
);
start();
子应用改造:
// 入口文件main.js
// 子应用并不用引入qiankun,只要暴露响应的声明周期钩子给主应用使用就ok
// 挂载实例
function render(props: any = {}) {
const { container } = props;
app = createApp(App);
app.use(router);
app.use(store);
router.isReady().then(() => {
app.mount(container ? container.querySelector('#container') : '#container');
});
}
// 微应用在主应用运行时,主应用会在微应用中挂载window.__POWERED_BY_QIANKUN__,可以用于判断环境
// 官方提供了下面这个webpack注入publicPath的方法, 开发环境我们这么使用,生产改到vue.config.js中,后面再介绍。
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// 如果是独立运行 window.__POWERED_BY_QIANKUN__=undefined 直接render
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 最后暴露的三个方法是固定的,加载渲染以及销毁
export async function bootstrap() { }
export async function mount(props: any) {
render(props);
}
export async function unmount() {
app.unmount();
app._container.innerHTML = "";
app = null;
// 这里reload的原因: 因为这个项目微应用和主应用没有共用导航等信息
// 相当于一个两个独立的页面,所以就共用的<div id="app"></div>一个节点
// 在卸载微应用后,为了再把主应用渲染出来,就重新reload了一遍。
location.reload();
}
// router改造
const router = createRouter({
history: createWebHashHistory(),
routes,
});
// 因为主应用在激活子应用时,有一个activeRule前缀,所以在hash模式下,我们需要给每个一级路由都添加activeRule的前缀,主应用为'#/app1',那么子应用前我们就加'/app1'就可以了。
export default [
{
path: '/app1/fujidaohang',
redirect: '/app1/fujidaohang/zijidaohang',
component: BothLayout,
name: 'fujidaohang',
meta: {
title: '父级导航',
navPosition: 'top',
},
children: [
{
path: 'zijidaohang',
component: () => import('@/apps/fujidaohang/views/zijidaohang.vue'),
name: 'zijidaohang',
meta: {
title: '子级导航',
navPosition: '',
},
},
],
},
{
path: '/app1/course',
component: Course,
name: 'course',
children: [],
}
];
// vue.config.js改造
const packageName = require('./package.json').name;
module.exports = {
...
// 用于主应用识别子应用,固定写法
configureWebpack: {
output: {
library: 'app1',
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
}
}
生产构建部署:
构建部署可以选择两种方式:
同域名和不同域名部署。因为考虑到接口跨域和不用域名部署需要运维资源,Nginx相关的配置,所以我们选用同域名部署。部署在主应用构建之后的静态资源目录里,这样前端就可以处理部署流程,不需要后端及运维的支持。当然也可以根据需要选用响应的部署方式,官方都有给出详细的介绍: 如何部署。
主应用改造:
// main.js
// 注册微应用
registerMicroApps(
[
{
name: 'app1',
// 路径改为部署后,微应用要存放在主应用的目录,其余不变
entry: '/static/index.html',
container: '#app',
activeRule: '#/app1',
props: {
name: 'kuitos',
},
},
],
);
微应用改造:
// main.js
// 去掉qiankun的__webpack_public_path__注入
// if (window.__POWERED_BY_QIANKUN__) {
// __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
// }
// vue.config.js
module.exports = {
...
// 这里新增打包资源存放路径,与上面主应用相对应
publicPath: '/static'
}
主、子应用打包后存放如图所示,之后就可以走运维的部署流程了。
主、子应用都为history模式改造:
开发模式:
这里我们和hash-dev模式进行对比,只列举差异的部分。
// 主应用
// main.js, 注册微应用有变化
registerMicroApps(
[
{
name: 'app1',
entry: '//localhost:8101',
container: '#app',
// 改变点:激活路由由hash模式变为history模式
activeRule: '/app1',
props: {
name: 'yuxiaoyu',
},
},
],
);
// router.js
const router = new VueRouter({
// 由hash改为history模式
mode: 'history',
base: process.env.BASE_URL,
routes,
});
// 子应用
// router/index.js
const router = createRouter({
// 改为history模式,并且加activeRule前缀
history: createWebHistory('/app1'),
routes,
});
// router.js
// 去掉这里的app1前缀
export default [
{
path: '/fujidaohang',
redirect: '/fujidaohang/zijidaohang',
component: BothLayout,
name: 'fujidaohang',
meta: {
title: '父级导航',
navPosition: 'top',
},
children: [
{
path: 'zijidaohang',
component: () => import('@/apps/fujidaohang/views/zijidaohang.vue'),
name: 'zijidaohang',
meta: {
title: '子级导航',
navPosition: '',
},
},
],
},
{
path: '/course',
component: Course,
name: 'course',
children: [],
}
];
生产构建部署:
这里与hash模式改动点是一致的
主应用改造:
// main.js
// 注册微应用
registerMicroApps(
[
{
// 路径改为部署后,微应用要存放在主应用的目录,其余不变
entry: '/static/index.html',
},
],
);
微应用改造:
// main.js
// 去掉qiankun的__webpack_public_path__注入
// if (window.__POWERED_BY_QIANKUN__) {
// __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
// }
// vue.config.js
module.exports = {
...
// 这里新增打包资源存放路径,与上面主应用相对应
publicPath: '/static'
}
鉴权
当进入微应用时,我们鉴定微应用是否登录时,我们可以考虑在微应用做鉴权,也可以在主应用做鉴权。
qiankun里在注册微应用时,registerMicroApps提供了第二个参数,lifeCycles- 可选,全局的微应用生命周期钩子
registerMicroApps(
[{}], {
// 这里采用在进入微前端之前进行鉴权,确保进入微前端时,已经登陆。
beforeLoad: [
() => {
if (!vm.$auth.check()) {
vm.$router.push(vm.$auth.options.loginPath);
location.reload();
}
},
],
},);
样式隔离
1.主、子应用都是用antd
这里官方提供了修改antd前缀的方法,如将ant改为dida-ant, 如何确保主应用跟微应用之间的样式隔离。
这里ant-design-vue文档里虽然没有提供prefixCls的参数,但是可以使用的,源码里相应的处理。
2.主应用自定义样式与子应用冲突
这里我们可以采用官方提供的start(options?),将微应用放入浏览器所支持的shadow dom中。
start({
sandbox: {
// 主应用 & 子应用样式隔离
strictStyleIsolation: true, // 放入shadow dom中
}
});
这里我们可以看到微前端被放入了shadow-root里,对于shadow dom可以通过这里进行了解。
这样隔离后,在我们使用ant design这种外部库时会有一些问题,例如popup组件,原本实现是挂在document.body中的,我们将子应用放到了shadow dom中,那就需要将popup也挂进去。ant design官方提供了方法,搜索getPopupContainer;