umijs / umi

A framework in react community ✨
https://umijs.org
MIT License
15.32k stars 2.65k forks source link

[Bug] createBrowserHistory 无法透传 opts,导致在 qiankun 场景下 history 的全局监听事件无法移除 #12484

Open asyncguo opened 3 months ago

asyncguo commented 3 months ago

What happens?

qiankun 场景下,多个子应用切换时到会导致 window.addEventListener(popstate) 无法移除,导致内存溢出。

主要原因是:umi通过 createHistory 生成路由时(history.tpl),history 模式会执行 createBrowserHistory ,而 createBrowserHistory 未接受传入的 opts,导致 history 在初始化路由时,window 值取了默认值 document.defaultView(createBrowserHistory)。

这种情况在 qiankun 场景下,createBrowserHistory 获取 document.defaultView 时触发沙箱逃逸(qiankun 内部主子应用会共享 document),实际取值为主应用的 window 对象,从而导致多个子应用的监听都会订阅到主应用上,即使子应用卸载后,事件监听还会存在,来回切换子应用时会不停的在主应用上累计监听,导致子应用会内存泄漏。

umi临时文件 .umi/core/history.ts

export function createHistory(opts: any) {
  let h;
  if (opts.type === 'hash') {
    h = createHashHistory();
  } else if (opts.type === 'memory') {
    h = createMemoryHistory(opts);
  } else {
   // 期望透传 opts 给 history 的 createBrowserHistory
    h = createBrowserHistory();
  }
  if (opts.basename) {
    basename = opts.basename;
  }

// ...

  return h;
}

historycreateBrowserHistory 源码:

// remix-run/history
export function createBrowserHistory(
  options: BrowserHistoryOptions = {}
): BrowserHistory {
  let { window = document.defaultView! } = options;
  let globalHistory = window.history;

  // ...
  window.addEventListener(PopStateEventType, handlePop);
  // ...
}

期望解法:createBrowserHistory 可以接收 opts 参数,让用户传递子应用代理后的 window 参数(qiankun 内部会对 addEventListener 打补丁,可以保证子应用卸载时移除事件订阅)。

Context

fz6m commented 3 months ago

可以估计多少次切换子应用后会导致页面崩溃吗,因为内存泄漏很容易在各种代码里发生,在一定程度上是可以容许泄露的,但这确实是一个问题。

asyncguo commented 3 months ago

可以估计多少次切换子应用后会导致页面崩溃吗,因为内存泄漏很容易在各种代码里发生,在一定程度上是可以容许泄露的,但这确实是一个问题。

页面崩溃的case比较难测,除了history的监听泄漏,qiankun 沙箱还存在其他场景下的泄漏比如:深度对象的代理修改会触发沙箱逃逸,也会存在一定的内存泄漏。 目前 history 的问题主要是因为 createBrowserHistory 时没有接收 opts 参数,不知道是否可以支持用户手动传入的参数,比如: window。

fz6m commented 3 months ago

我认为如果此问题不解决,最多只是一定程度的内存泄漏,但并没有人发生应用的崩溃,因为很多应用都存在内存泄露,在一定程度是允许的,所以这个问题不是致命的。

PeachScript commented 1 month ago

关键代码位置: https://github.com/umijs/umi/blob/88416e9900db7c8adaeb794fb2eb5f8a029dfa45/packages/preset-umi/templates/history.tpl#L9 https://github.com/umijs/umi/blob/88416e9900db7c8adaeb794fb2eb5f8a029dfa45/packages/preset-umi/templates/history.tpl#L13

@sorrycc 看 blame 是你,还能想起来原因吗