feat: qiankun支持多页签keepalive (#117)

* fix: 判断页面权限的path统一从match中拿路由path

* fix: qiankun主应用不改rootContainer

* fix: 消除initialState为空时的warn

* refactor: modifyCreateHistroy更改为modifyCreateHistory

* fix: qiankun支持多页签keepalive
This commit is contained in:
harrywan 2022-04-20 10:35:16 +08:00 committed by GitHub
parent 2ca3951c3f
commit 41b843396c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 4588 additions and 4410 deletions

View File

@ -4,10 +4,10 @@
对于前端应用来说,权限就是页面、页面元素是否可见。
### 资源
Fes.js 把页面、页面元素统一叫做资源,每个资源都有 `accessId`
- 页面的 `accessId` 默认是页面的路由 `path` 。比如页面 `pages/a.vue` 的路由 `path``/a`。当页面访问 `/a` 时会渲染当前页面,`/a` 也就是页面的 `accessId`
Fes.js 把页面、页面元素统一叫做资源,用资源 ID 来识别区分他们
- 页面的资源 ID 默认是页面的路由 `path` 。比如页面 `pages/a.vue` 的路由 `path``/a`。当页面访问 `/a` 时会渲染当前页面,`/a` 也就是页面的 `accessId`
- 页面元素的 `accessId` 没有默认值,由我们自定义。
- 页面元素的资源 ID 没有默认值,需要自定义。
```vue
<template>
<access :id="accessId"> accessOnepicess1 <input /> </access>

View File

@ -1,4 +1,5 @@
import { plugin, ApplyPluginsType } from '@@/core/coreExports';
// eslint-disable-next-line import/extensions
import { access, install } from './core';
export function onRouterCreated({ router }) {
@ -9,25 +10,35 @@ export function onRouterCreated({ router }) {
initialValue: {}
});
if (to.matched.length === 0) {
if (runtimeConfig.noFoundHandler && typeof runtimeConfig.noFoundHandler === 'function') {
if (
runtimeConfig.noFoundHandler
&& typeof runtimeConfig.noFoundHandler === 'function'
) {
return runtimeConfig.noFoundHandler({
router, to, from, next
router,
to,
from,
next
});
}
return next(false);
}
let path;
if (to.matched.length === 1) {
path = to.matched[0].path;
} else {
path = to.path;
}
const canRoute = await access.hasAccess(path);
// path是匹配路由的path不是页面hash
const canRoute = await access.hasAccess(
to.matched[to.matched.length - 1].path
);
if (canRoute) {
return next();
}
if (runtimeConfig.unAccessHandler && typeof runtimeConfig.unAccessHandler === 'function') {
if (
runtimeConfig.unAccessHandler
&& typeof runtimeConfig.unAccessHandler === 'function'
) {
return runtimeConfig.unAccessHandler({
router, to, from, next
router,
to,
from,
next
});
}
next(false);

View File

@ -3,7 +3,7 @@
main
<FTabs v-model="activeKey">
<FTabPane name="Tab 1" value="1">
<MicroAppWithMemoHistory key="1" name="app1" url="/app1" />
<MicroAppWithMemoHistory key="1" name="app1" url="/app1" a="1" />
</FTabPane>
<FTabPane name="Tab 2" value="2">
<MicroAppWithMemoHistory key="2" name="app1" url="/app1/test" />

View File

@ -32,7 +32,7 @@
"@fesjs/utils": "^2.0.4",
"address": "^1.1.2",
"lodash-es": "^4.17.15",
"qiankun": "^2.4.4"
"qiankun": "^2.7.0"
},
"devDependencies": {
"npm-run-all": "^4.1.5"

View File

@ -26,6 +26,7 @@ export default function (api) {
enableBy: () => isMasterEnable(api)
});
// 避免跟子应用冲突
api.modifyDefaultConfig(config => ({
...config,
mountElementId: defaultMainRootId

View File

@ -11,26 +11,26 @@ import { loadMicroApp } from "{{{QIANKUN}}}";
import {mergeWith} from "{{{LODASH_ES}}}";
// eslint-disable-next-line import/extensions
import { getMasterOptions } from "./masterOptions";
import { onBeforeRouteLeave } from "@@/core/coreExports";
async function unmountMicroApp(microApp) {
if (microApp) {
const status = microApp.getStatus();
if(status === 'MOUNTED'){
await microApp.unmount();
}
function unmountMicroApp(microApp) {
if (!microApp) {
return;
}
const status = microApp.getStatus();
if (status === 'MOUNTED') {
microApp.unmount();
}
return Promise.resolve();
}
export const MicroApp = defineComponent({
props: {
name: {
type: String,
required: true,
required: true
},
settings: Object,
lifeCycles: Object,
props: Object,
lifeCycles: Object
},
setup(props, { attrs }) {
const {
@ -40,8 +40,6 @@ export const MicroApp = defineComponent({
...globalSettings
} = getMasterOptions();
const stateForSlave = reactive({});
// 挂载节点
const containerRef = ref(null);
const microAppRef = ref();
@ -68,6 +66,14 @@ export const MicroApp = defineComponent({
const propsFromParams = attrs;
const propsConfigRef = computed(() => {
return {
...propsFromConfigRef.value,
...props.props,
...propsFromParams
};
});
// 只有当name变化时才重新加载新的子应用
const loadApp = () => {
const appConfig = appConfigRef.value;
@ -75,21 +81,21 @@ export const MicroApp = defineComponent({
// 加载新的
microAppRef.value = loadMicroApp(
{
name: name,
// 保证唯一
name: `${name}_${Date.now()}`,
entry: entry,
container: containerRef.value,
props: {
...propsFromConfigRef.value,
...stateForSlave,
...propsFromParams,
},
props: propsConfigRef.value
},
{
...globalSettings,
...(props.settings || {}),
...(props.settings || {})
},
mergeWith({}, globalLifeCycles || {}, props.lifeCycles || {}, (v1, v2) =>
concat(v1 ?? [], v2 ?? [])
mergeWith(
{},
globalLifeCycles || {},
props.lifeCycles || {},
(v1, v2) => concat(v1 ?? [], v2 ?? [])
)
);
};
@ -106,9 +112,9 @@ export const MicroApp = defineComponent({
updatingPromiseRef.value = updatingPromiseRef.value.then(
() => {
const canUpdate = (app) =>
app?.update && app.getStatus() === "MOUNTED";
app?.update && app.getStatus() === 'MOUNTED';
if (canUpdate(microApp)) {
if (process.env.NODE_ENV === "development") {
if (process.env.NODE_ENV === 'development') {
if (
Date.now() -
updatingTimestampRef.value <
@ -127,11 +133,7 @@ export const MicroApp = defineComponent({
}
// 返回 microApp.update 形成链式调用
return microApp.update({
...propsFromConfigRef.value,
...stateForSlave,
...propsFromParams,
});
return microApp.update(propsConfigRef.value);
}
}
);
@ -152,16 +154,10 @@ export const MicroApp = defineComponent({
loadApp();
});
onBeforeRouteLeave(async () => {
return await unmountMicroApp(microAppRef.value);
});
watch(()=>{
return {...{}, ...propsFromConfigRef.value, ...stateForSlave, ...propsFromParams}
}, () => {
watch(propsConfigRef, () => {
updateApp();
});
return () => <div ref={containerRef}></div>;
},
}
});

View File

@ -15,6 +15,7 @@ export const MicroAppWithMemoHistory = defineComponent({
required: true
},
settings: Object,
props: Object,
lifeCycles: Object,
url: String
},

View File

@ -110,11 +110,7 @@ export function genUpdate() {
export function genUnmount() {
return async (props) => {
const history = getHistory();
history.destroy();
if (cacheAppPromise) {
const app = await cacheAppPromise;
app.unmount();
}
history.destroy(); // 会触发app.unmount
destroyRouter();
const slaveRuntime = getSlaveRuntime();
if (slaveRuntime.unmount) {

View File

@ -14,7 +14,7 @@ export function modifyClientRenderOpts(memo) {
};
}
export function modifyCreateHistroy(memo) {
export function modifyCreateHistory(memo) {
if (history.url) {
return createMemoryHistory
}

View File

@ -29,8 +29,8 @@ export default function (api) {
'render',
// 修改路由
'patchRoutes',
// 修改histror
'modifyCreateHistroy',
// 修改history
'modifyCreateHistory',
// 生成router时触发
'onRouterCreated'
]

View File

@ -27,7 +27,7 @@ const renderClient = (opts = {}) => {
const app = createApp(rootContainer);
// initialState是响应式的后期可以更改
app.provide("initialState", reactive(initialState));
app.provide("initialState", reactive(initialState ?? {}));
plugin.applyPlugins({
key: 'onAppCreated',

View File

@ -14,7 +14,7 @@ export const createRouter = (routes) => {
return router;
}
const createHistory = plugin.applyPlugins({
key: 'modifyCreateHistroy',
key: 'modifyCreateHistory',
type: ApplyPluginsType.modify,
args: {
base: ROUTER_BASE

8869
yarn.lock

File diff suppressed because it is too large Load Diff