mirror of
https://gitee.com/WeBank/fes.js.git
synced 2024-12-02 03:37:57 +08:00
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:
parent
d37d6b480b
commit
c09dfeadde
@ -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();
|
||||
```
|
||||
|
@ -301,7 +301,7 @@ import { access, useAccess } from '@fesjs/fes';
|
||||
```js
|
||||
api.addCoreExports(() => [
|
||||
{
|
||||
specifiers: ['getRoutes', 'getRouter', 'getHistory', 'destroyRouter'],
|
||||
specifiers: ['getRoutes'],
|
||||
source: absCoreFilePath
|
||||
}
|
||||
]);
|
||||
|
@ -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 | - |
|
@ -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;
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="haizekuo">
|
||||
app1 - index
|
||||
<input />
|
||||
</div>
|
||||
</template>
|
||||
<config>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="haizekuo">
|
||||
app1 - test
|
||||
<input />
|
||||
</div>
|
||||
</template>
|
||||
<config>
|
||||
|
@ -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" />
|
||||
|
@ -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,
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -14,6 +14,7 @@ export const MicroAppWithMemoHistory = defineComponent({
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
cacheName: String,
|
||||
settings: Object,
|
||||
props: Object,
|
||||
lifeCycles: Object,
|
||||
|
@ -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} />;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,7 @@ const beforeRender = async ({rootElement}) => {
|
||||
console.error(e);
|
||||
}
|
||||
app.unmount();
|
||||
app._container.innerHTML = '';
|
||||
}
|
||||
return initialState;
|
||||
};
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user