qiankun官方文档地址:https://qiankun.umijs.org/zh
yarn add qiankun # yarn安装
npm i qiankun -S # npm安装
适用于 route-based 场景。
通过将微应用关联到一些 url 规则的方式,实现当浏览器 url 发生变化时,自动加载相应的微应用的功能。
main.js 中注册微应用(全局注册)
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'appName1', // 注册的微应用名称,必须唯一
entry: '//localhost:7100', // 微应用入口
container: '#yourContainer', // 微应用的容器节点,即微应用植入的位置
activeRule: '/yourActiveRule', // 微应用的激活规则,路由匹配此规则表示当前微应用会被激活。支持字符串、字符串数组、function
},
{
name: 'appName2',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
start();
适用于需要手动 加载/卸载 一个微应用的场景。
import { loadMicroApp } from 'qiankun'
handleLoadMicroApp(data) {
const baseUserId = this.baseUserId
const path = data.path
// 卸载微应用
this.microApp && this.microApp.unmount()
// 手动加载微应用
this.microApp = loadMicroApp({
name: data.id, // 必选,微应用的名称,微应用之间必须确保唯一
entry: data.url, // 必选,微应用的入口
container: '#content', // 必选,微应用的容器节点的选择器或者 Element 实例
props: { // 可选,初始化时需要传递给微应用的数据
path: path // 路径参数,唯一
baseUserId: baseUserId,
},
})
}
主应用基路由一定要与微应用的基路由保持一致
{
path: '/personalCenter', // personalCenter为例
name: 'personalCenter',
component: () => import('@/views/personalCenter/index'),
meta: {
title: '个人中心',
},
},
{
path: '/personalCenter/*', // personalCenter为基路由。*表示通配,这个一定要配置
name: 'personalCenter',
component: () => import('@/views/personalCenter/index'),
meta: {
title: '个人中心',
},
},
// 判断是否是qiankun环境,一定要写在最上面
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
import router from './router'
// 设置全局变量,用于保存或销毁Vue实例
let instance = null
//不是在qiankun环境下的话,独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
function render(props) {
const { container } = props || {}
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app') // 用于限定当前上下文下的#app,防止与主应用中的#app冲突
}
// 导出相应的生命周期钩子
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('qiankun-bootstrap')
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log('qiankun-mount参数', props)
render(props) // 从qiankun启动
// ReactDOM.render( , props.container ? props.container.querySelector('#root') : document.getElementById('root'))
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
instance.$destroy() // 销毁子应用实例
// ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root') : document.getElementById('root'))
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props)
render(props) // 从qiankun启动
}
import VueRouter from 'vue-router'
import routes from './router';
const router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/personalCenter/' : '/', // qiankun环境配置基路由personalCenter
mode: 'history', // 访问路径不带#号
routes,
});
component在qiankun环境和非乾坤环境不同时,可直接写死路由:
export const routes = [
{
path: '/personalCenter/basic', // 一定要有基路由personalCenter,且一定要有非基路由
component: () => import('@/views/setting/basic'),
},
{
path: '/personalCenter/account',
component: () => import('@/views/setting/account'),
},
{
path: '/personalCenter/third',
component: () => import('@/views/setting/third'),
},
]
component在qiankun环境和非乾坤环境相同时,可动态配置路由:
const base = window.__POWERED_BY_QIANKUN__ ? '/personalCenter' : '' // qiankun环境配置基路由personalCenter
export const routes = [
{
path: `${base}/basic`, // qiankun环境和非qiankun环境,页面都是同一个component不变
component: () => import('@/views/setting/basic'),
},
{
path: `${base}/account`,
component: () => import('@/views/setting/account'),
},
{
path: `${base}/third`,
component: () => import('@/views/setting/third'),
},
]
const base = window.__POWERED_BY_QIANKUN__ ? '/personalCenter' : '' // hash模式下,路由跳转需要带基路由
this.$router.push(`${base}/basic`)
router.beforeEach(async (to, from, next) => {}
且重写了浏览器tab标签的图标和标题时,接入主应用的路由不重写浏览器的图标和标题/*
* permission.js 文件中
*/
import router from './router'
import changeFavicon from '@/utils/get-page-title'
// 路由白名单
const whiteList = [
'/personalCenter/basic',
]
const hasToken = getToken()
// 路由守卫
router.beforeEach(async (to, from, next) => {
if(to.path.indexOf('personalCenter') === -1) {
changeFavicon(to.meta.title) // 不是微应用才修改网站图标和标题
}
if (whiteList.indexOf(to.matched[0].path) !== -1 || hasToken) {
next() // 添加白名单直接进、登录了直接进
}
}
const packageName = require('./package.json').name // 导入packageName
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*', // 允许跨域,允许所有域名的脚本访问该资源
},
},
// 配置打包,一定要配置,不然会报错
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
}
参考地址:https://juejin.cn/post/6992944363798003743
注:上述文档中有写,qiankun自带的沙箱样式隔离存在诸多问题
故主应用目前未开启自带的样式隔离
不过qiankun默认情况切换应用时,会采用动态样式表,子应用之间不会样式污染
解决方案:如子应用未影响主应用页面样式,可不配置插件
使用postcss插件给各自的项目class全局加上一个各自的命名空间
1.添加依赖
npm i postcss-plugin-namespace -D
2.配置postcss
module.exports = (ctx) => {
return {
plugins: [
require('postcss-plugin-namespace')('#lee_project', {
ignore: [
'html', /body/, 'span', 'el-form-item'
]
}),
]
}
}
该插件会将全局所有class前加上统一前缀,并过滤掉ignore内的标签;ignore内可以写字符串,可以写正则表达式。但每次编译前都会运行,所以可能会增加编译时间,所以日常开发环境下可以将此文件名随便改成别的,上线前记得改回来调试一下(如果直接隐藏掉代码的话,只要有postcss.config.js
这个文件webpack会自动帮你执行,并且会提示你的postcss啥也没干,也相当于每次都走了这个脚本)。
注意:如果用/body/
这样的正则,会将所有带body的class都过滤掉,比如el-drawer__body
、el-dialog__body
等。
https://juejin.cn/post/6888695499793268744
解决方案:这里我们可以采用一定的编程约束: