Fix qiankun 内存泄漏问题 (#126)

* fix: 修复乾坤插件内存问题

* fix: 多页签时关闭页面需要清除缓存

* feat: 乾坤增加entry配置,用于区别同一name不同位置用法

* docs: 去掉文档

* fix: preset-build-in的route api 兼容

* fix: 修复问题

* docs: 更新w文档

* fix: 变更api
This commit is contained in:
harrywan 2022-06-07 17:29:24 +08:00 committed by GitHub
parent d37d6b480b
commit c09dfeadde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 91 additions and 38 deletions

View File

@ -74,7 +74,6 @@ Fes.js 路由基于 [Vue Router 4.0](https://next.router.vuejs.org/introduction.
返回当前 `router` 实例。
```js
import { getRouter } from "@fesjs/fes";
const router = getRouter();
router.push();
```

View File

@ -301,7 +301,7 @@ import { access, useAccess } from '@fesjs/fes';
```js
api.addCoreExports(() => [
{
specifiers: ['getRoutes', 'getRouter', 'getHistory', 'destroyRouter'],
specifiers: ['getRoutes'],
source: absCoreFilePath
}
]);

View File

@ -269,3 +269,24 @@ export default {
- 主应用使用 props 的模式传递数据(参考主应用装载子应用配置一节)
- 子应用在生命周期钩子中获取 props 消费数据(参考子应用运行时配置一节)
### MicroApp
| 属性 | 说明 | 类型 | 默认值 |
| ---- | ----------- | ------------- | ---------- |
| name | 子应用名称,传入`qiankun.main.apps`配置中的`name` | String | - |
| settings | 子应用配置信息 | Object | {} |
| props | 传入子应用的参数 | Object | {} |
| lifeCycles | 子应用生命周期钩子 | Object | {} |
| cacheName | 子应用缓存名称,配置后根据`name`+`cacheName`缓存子应用实例 | Object | - |
### MicroAppWithMemoHistory
| 属性 | 说明 | 类型 | 默认值 |
| ---- | ----------- | ------------- | ---------- |
| name | 子应用名称,传入`qiankun.main.apps`配置中的`name` | String | - |
| settings | 子应用配置信息 | Object | {} |
| props | 传入子应用的参数 | Object | {} |
| lifeCycles | 子应用生命周期钩子 | Object | {} |
| cacheName | 子应用缓存名称,配置后根据`name`+`cacheName`缓存子应用实例 | Object | - |
| url | 子应用的路由地址 | String | - |

View File

@ -31,8 +31,11 @@
</template>
</FTabs>
<router-view v-slot="{ Component, route }">
<keep-alive>
<component :is="Component" :key="getPageKey(route)" />
<keep-alive :include="keepAlivePages">
<component
:is="getComponent(Component, route, true)"
:key="getPageKey(route)"
/>
</keep-alive>
</router-view>
</template>
@ -68,11 +71,12 @@ export default {
return {
path: _route.path,
route: _route,
name: _route.meta.name,
name: _route.meta.name ?? _route.name,
title: computed(() => transTitle(title)),
key: getKey()
};
};
const keepAlivePages = ref([]);
const route = useRoute();
const router = useRouter();
@ -122,6 +126,12 @@ export default {
}
list.splice(index, 1);
pageList.value = list;
const _keepAlivePages = [...keepAlivePages.value];
const keepIndex = _keepAlivePages.indexOf(selectedPage.name);
if (keepIndex !== -1) {
_keepAlivePages.splice(keepIndex, 1);
}
keepAlivePages.value = _keepAlivePages;
};
const reloadPage = (path) => {
const selectedPage = findPage(path || unref(route.path));
@ -132,6 +142,7 @@ export default {
const closeOtherPage = (path) => {
const selectedPage = findPage(path || unref(route.path));
pageList.value = [selectedPage];
keepAlivePages.value = [selectedPage.name];
};
const getPageKey = (_route) => {
const selectedPage = findPage(_route.path);
@ -151,10 +162,10 @@ export default {
default:
}
};
const keepAlivePages = ref([]);
const getComponent = (Component, _route) => {
if (_route.meta['keep-alive']) {
const name = _route.meta?.name || _route.name;
const getComponent = (Component, _route, isKeep = false) => {
if (isKeep || _route.meta['keep-alive']) {
const name = _route.meta?.name ?? _route.name;
if (name) {
// name
Component.type.name = name;

View File

@ -1,6 +1,7 @@
<template>
<div class="haizekuo">
app1 - index
<input />
</div>
</template>
<config>

View File

@ -1,6 +1,7 @@
<template>
<div class="haizekuo">
app1 - test
<input />
</div>
</template>
<config>

View File

@ -1,9 +1,10 @@
<template>
<div>
main
<input />
<FTabs v-model="activeKey">
<FTabPane name="Tab 1" value="1">
<MicroAppWithMemoHistory key="1" name="app1" url="/app1" a="1" />
<MicroAppWithMemoHistory key="1" style="background: red" name="app1" url="/app1" a="1" />
</FTabPane>
<FTabPane name="Tab 2" value="2">
<MicroAppWithMemoHistory key="2" name="app1" url="/app1/test" />

View File

@ -2,11 +2,11 @@ import { defaultHistoryType } from '../constants';
function getMicroApp(options) {
const {
key, microAppName, masterHistoryType, base, namespace, ...normalizedRouteProps
key, microAppName, cacheName, masterHistoryType, base, namespace, ...normalizedRouteProps
} = options;
return `(() => {
const { getMicroAppRouteComponent } = require('@@/${namespace}/getMicroAppRouteComponent');
return getMicroAppRouteComponent({key: '${key}', appName: '${microAppName}', base: '${base}', masterHistoryType: '${masterHistoryType}', routeProps: ${JSON.stringify(normalizedRouteProps)} })
return getMicroAppRouteComponent({key: '${key}', appName: '${microAppName}', cacheName: '${cacheName}', base: '${base}', masterHistoryType: '${masterHistoryType}', routeProps: ${JSON.stringify(normalizedRouteProps)} })
})()`;
}
@ -19,6 +19,7 @@ function modifyRoutesWithAttachMode({
if (route.meta && route.meta.microApp) {
route.component = getMicroApp({
key: route.path,
cacheName: route.meta.cacheName ?? route.path,
microAppName: route.meta.microApp,
masterHistoryType,
base,

View File

@ -12,13 +12,13 @@ import {mergeWith} from "{{{LODASH_ES}}}";
// eslint-disable-next-line import/extensions
import { getMasterOptions } from "./masterOptions";
function unmountMicroApp(microApp) {
async function unmountMicroApp(microApp) {
if (!microApp) {
return;
}
const status = microApp.getStatus();
if (status === 'MOUNTED') {
microApp.unmount();
await microApp.unmount();
}
}
@ -28,6 +28,7 @@ export const MicroApp = defineComponent({
type: String,
required: true
},
cacheName: String,
settings: Object,
props: Object,
lifeCycles: Object
@ -64,26 +65,26 @@ export const MicroApp = defineComponent({
return {};
});
const propsConfigRef = computed(() => {
return {
...propsFromConfigRef.value,
...props.props,
...attrs
};
});
// 只有当name变化时才重新加载新的子应用
const loadApp = () => {
const appConfig = appConfigRef.value;
const { name, entry } = appConfig;
const { name, cacheName } = appConfig;
// 加载新的
microAppRef.value = loadMicroApp(
const app = loadMicroApp(
{
// 保证唯一
name: `${name}_${Date.now()}`,
name: `${name}_${props.cacheName || ''}`,
entry: entry,
container: containerRef.value,
props: {...propsConfigRef.value}
props: {...propsConfigRef.value, ...attrs}
},
{
...globalSettings,
@ -96,6 +97,12 @@ export const MicroApp = defineComponent({
(v1, v2) => concat(v1 ?? [], v2 ?? [])
)
);
['loadPromise', 'bootstrapPromise', 'mountPromise', 'unmountPromise'].forEach((key)=>{
app[key].catch((e)=>{
console.warn("[@fesjs/plugin-qiankun]", e)
})
})
microAppRef.value = app;
};
// 当参数变化时update子应用
@ -131,7 +138,7 @@ export const MicroApp = defineComponent({
}
// 返回 microApp.update 形成链式调用
return microApp.update({...propsConfigRef.value});
return microApp.update({...propsConfigRef.value, ...attrs});
}
}
);

View File

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

View File

@ -3,10 +3,11 @@ import { MicroApp } from './MicroApp';
export function getMicroAppRouteComponent({
key,
appName,
cacheName,
base,
masterHistoryType,
routeProps
}) {
return <MicroApp key={key} base={base} masterHistoryType={masterHistoryType} name={appName} {...routeProps} />;
return <MicroApp key={key} base={base} masterHistoryType={masterHistoryType} name={appName} cacheName={cacheName} {...routeProps} />;
}

View File

@ -1,4 +1,4 @@
import { plugin, ApplyPluginsType, getRouter, getHistory, destroyRouter } from '@@/core/coreExports';
import { plugin, ApplyPluginsType } from '@@/core/coreExports';
{{#HAS_PLUGIN_MODEL}}
import { setModelState } from './qiankunModel';
{{/HAS_PLUGIN_MODEL}}
@ -18,6 +18,7 @@ function isPromise(obj) {
let render = () => {};
let cacheAppPromise = null;
let hasMountedAtLeastOnce = false;
const cache = {};
export default () => defer.promise;
@ -91,6 +92,11 @@ export function genMount(mountElementId) {
defer.resolve();
}
hasMountedAtLeastOnce = true;
cacheAppPromise.then((app)=>{
if(!cache[props.name]) {
cache[props.name] = app;
}
})
};
}
@ -109,9 +115,15 @@ export function genUpdate() {
// 子应用生命周期钩子Unmount
export function genUnmount() {
return async (props) => {
const history = getHistory();
history.destroy(); // 会触发app.unmount
destroyRouter();
if (cache[props.name]) {
setTimeout(() => {
const app = cache[props.name];
app.unmount();
app._container.innerHTML = '';
delete cache[props.name];
cacheAppPromise = null;
}, 0);
}
const slaveRuntime = getSlaveRuntime();
if (slaveRuntime.unmount) {
await slaveRuntime.unmount(props);

View File

@ -1,5 +1,5 @@
import { createMemoryHistory, getHistory } from '@@/core/coreExports';
import qiankunRender, { clientRenderOptsStack, history } from './lifecycles';
import { createMemoryHistory } from '@@/core/coreExports';
import qiankunRender, { clientRenderOptsStack, history as historyConfig } from './lifecycles';
export const render = oldRender => qiankunRender().then(oldRender);
@ -15,19 +15,18 @@ export function modifyClientRenderOpts(memo) {
}
export function modifyCreateHistory(memo) {
if (history.url) {
if (historyConfig.url) {
return createMemoryHistory
}
return memo;
}
export function onRouterCreated({ router }) {
if(history.url) {
const memoryHistory = getHistory();
memoryHistory.push(history.url)
export function onRouterCreated({ router, history }) {
if(historyConfig.url) {
history.push(historyConfig.url)
}
if(history.onRouterInit){
history.onRouterInit(router)
if(historyConfig.onRouterInit){
historyConfig.onRouterInit(router)
}
}

View File

@ -61,6 +61,7 @@ const beforeRender = async ({rootElement}) => {
console.error(e);
}
app.unmount();
app._container.innerHTML = '';
}
return initialState;
};

View File

@ -10,9 +10,6 @@ const ROUTER_BASE = '{{{ routerBase }}}';
let router = null;
let history = null;
export const createRouter = (routes) => {
if (router) {
return router;
}
const createHistory = plugin.applyPlugins({
key: 'modifyCreateHistory',
type: ApplyPluginsType.modify,
@ -36,7 +33,7 @@ export const createRouter = (routes) => {
plugin.applyPlugins({
key: 'onRouterCreated',
type: ApplyPluginsType.event,
args: { router },
args: { router, history },
});
return router;