为了业务在原有系统中,集成新项目,咱们常常会使用qiankun这套微前端解决方案,来解决集成诉求。javascript
vue2,vue3在qiankun中的使用实际差异不大,主要路由处理上有些区别,也会在文中指出,会以【vue3】做为标识。html
经过这边文章,能够了解到,如何在vue(2/3)技术栈里使用微前端集成新老项目,主要内容包括:前端
下面是经常使用的两个qiankun API,已给出连接,能够在官网查看具体用法:vue
registerMicroApps: 主应用注册微前端应用java
start: 启动微前端应用webpack
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">
// 在卸载微应用后,为了再把主应用渲染出来,就从新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}`,
},
}
}
复制代码
构建部署能够选择两种方式:web
同域名、不一样域名部署。vue-router
1.能够考虑将子应用打包放在static路径下,做为主应用的静态资源引入。这种处理方式的适用场景包含:子应用不须要独立访问、子应用不须要独立部署。这种方式的好处在于,不用运维配合去Nginx配置,本地打包,并使用原有的部署方式。固然,它同时也失去了微前端,主、子独立部署的优势。vue-cli
2.还有一种方式,能够将打包后的静态资源在同一台服务器进行部署,而后针对于项目新增部署和Nginx相关配置。在主应用访问的时候能够经过域名加载,也能够经过相对路径进行加载(由于咱们同台机器部署)。这个相对于上面的方案,增长了运维成本,但保留了主子独立发布的特性。json
这里的方案和上面的2
差很少,区别在于资源可能放在不一样的服务器上,因此只能经过域名进行资源的加载(也就是registerMicroApps中配置的entry须要是域名,不能是相对路径了,和开发环境差很少),记得处理跨域。
固然也能够根据须要选用相应的部署方式,官方都有给出详细的介绍: 如何部署。
// 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'
}
复制代码
这里咱们和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
// 【vue3】由于子应用为vue3版本,这里是vue2和vue3自己路由处理上的区别,咱们只是使用了vue3的写法加上了须要的前缀app1
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 (!auth.check()) {
vm.$router.push(loginPath);
}
},
],
},);
复制代码
这里官方提供了修改antd前缀的方法,如将ant
改成dida-ant
, 如何确保主应用跟微应用之间的样式隔离。
这里ant-design-vue文档里虽然没有提供prefixCls
的参数,可是可使用的,源码里相应的处理。
这里咱们能够采用官方提供的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
;
咱们能够经过注册微应用时,经过props参数进行数据的传递,在mounted
和render
生命周期中,能够拿到props
数据。
registerMicroApps(
[
{
name: 'app1',
entry: '//localhost:8100',
container: '#app',
activeRule: '/app1',
props: {
id: 1,
},
},
],
);
复制代码
qiankun官方提供了建立主、子应用通讯的定义方法。initGlobalState(state)
,文档连接,初始化后会返回三个方法,onGlobalStateChange
,setGlobalState
,offGlobalStateChange
,分别是监听,设置和移除。
// 主应用
// 初始化 state
// 调用initGlobalState后,会挂在props中进行传递
const initialState = {
userInfo: {}, // 用户信息
};
const actions = initGlobalState(initialState);
actions.onGlobalStateChange((newState, oldState) => {
// newState: 变动后的状态; oldState 变动前的状态
console.log('mainapp: global state changed', newState, oldState);
});
actions.setGlobalState({
userInfo: {
name: 'Zhangsan',
},
});
actions.offGlobalStateChange();
复制代码
export async function mount(props: any) {
render(props);
// props 会注入onGlobalStateChange、setGlobalState方法。
console.log('props :>> ', props);
app.config.globalProperties.$onGlobalStateChange =props.onGlobalStateChange;
app.config.globalProperties.$setGlobalState = props.setGlobalState;
props.onGlobalStateChange((newState: any, oldState: any) => {
// newState: 变动后的状态; oldState 变动前的状态
console.log('microapp: global state changed', newState, oldState);
});
window.document.addEventListener('click', () => {
props.setGlobalState({
userInfo: {
name: Math.random(),
},
});
});
}
复制代码
vue3做为主应用, 原系统做为子应用(vue2)。也就是vue3(主)接vue2(子)。在已有路由或组件中引入子应用。 如图:
官方提供了一种在应用某个路由下面接入方法。
官方提供的方法是在vue2为主应用时接入。因此这里写法上有会一些区别。
{
path: '/huodongguanli',
redirect: '/trace_micro/project/projectList',
component: SideLayout,
name: 'huodongguanli',
meta: {
title: '活动管理',
navPosition: 'side',
icon: () => {
return h(ContactsOutlined);
},
},
children: [
{
path: '/trace_micro/project/projectList', // 须要嵌套在二级菜单,且路径不须要一级的path拼接的,这里写绝对路径(这里为业务逻辑,不用关注)
component: { default: '' }, // 由于这里只是为了声明一个menu项,并不须要组件,(组件内容在子应用中),这里给个含有default的空对象
name: 'lianluliebiao',
meta: {
title: '链路列表',
navPosition: 'side',
},
},
{
path: 'chuangjianjiangzuo', // 主应用中的路由 正常写就能够
name: 'chuangjianjiangzuo',
meta: {
title: '建立讲座',
navPosition: 'side',
},
},
],
},
// 针对于不用在主应用里菜单显示出来的路由,都进行模糊匹配
// 【vue3】
// 由于vue3中,vue-router4.x[取消了通配符的写法](https://next.router.vuejs.org/zh/guide/migration/index.html#%E5%88%A0%E9%99%A4%E4%BA%86-%EF%BC%88%E6%98%9F%E6%A0%87%E6%88%96%E9%80%9A%E9%85%8D%E7%AC%A6%EF%BC%89%E8%B7%AF%E7%94%B1),因此在匹配的`path`的写法上会有一些区别:
{
path: '/trace_micro/:pathMatch(.*)*', // 【重要】这里咱们采用router4.x中提供的写法去匹配子应用的路由
hidden: true,
component: SideLayout,
name: 'projectOperate',
meta: {
title: '建立链路',
navPosition: 'side',
},
}
复制代码
其他的使用方法(start
和挂载节点
等)与官方提供的一致。
官方有提供常见问题文档,遇到问题时,能够先去这里查看一下是否有相应的匹配解决方案。
这里也列举几个,我在接入项目时候遇到的其它的坑:
#app
。在返回主应用时,主应用不显示。这里由于咱们在初始化微前端时,把#app
中的内容替换成了子应用的数据,在返回时,并无从新挂载,目前能够经过reload
简单处理。
#app
。dom看起来挂载成功了,可是样式都没有。这个和第一个又是不一样的状况,主要是由于咱们把子应用的dom结构直接挂载到了主应用上,微前端这一套都没有生效。在mount
生命周期中,咱们能够拿到props,这里面挂载了一个container
属性,就是挂载微前端的dom节点,能够经过props.container.querySelector('#app')
去挂载子应用的vue
。
$router.push
不生效是由于咱们主、子应用使用的router
并非一个实例,注册的路由也不同,因此子应用并不能匹配到主应用的路由,可使用history.push
或者location.href
进行跳转。
js
加载不到,或者<
等语法不对的问题通常因为主应用引入子应用的路径不对,检查路径是否能引到子应用资源。
babel-polyfill
时,有时候会babel-polyfill
引入两次的问题。这个先检查一下是否子应用也单独引入babel-polyfill
了,若是引入了就都统一放到一处去引用。
如今大部分新的应用都是用vue-cli进行生成,并不会单独引入babel-polyfill
,那若是报这个错了,就检查一下是否是,子应用的路径有问题,引入的资源不对。报了引两次的问题,可能也是因为子应用没有引到的问题。和babel-polyfill
自己引入没有关系。
上面也有给出样式隔离的方案,但方案自己仍是有一些问题的。好比咱们提到的popup
挂在document.body
上的问题,须要处理。可能还会有一些其它的问题要处理。
最简单有效的方法仍是在主、子应用中,尽可能使用style scoped
进行组件级别的样式隔离。对于使用同一组件库,不一样配置,能够经过修改组件、样式前缀解决。
以上就是微前端(qiankun)在vue二、3中的实战开发,但愿对各位有所帮助。