Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] patchHTMLDynamicAppendPrototypeFunctions方法逻辑存在问题 #3019

Open
missmess opened this issue Oct 29, 2024 · 1 comment
Open

Comments

@missmess
Copy link

问题描述

我是直接查看的源码,发现了patchHTMLDynamicAppendPrototypeFunctions这个函数的代码逻辑有些问题。(所以就没有提供最小可复现仓库了,因为直接用的官方demo,打断点调试就行。)

这个函数的逻辑,我简化一下并且只看appendChild方法,方便观察:

function patchHTMLDynamicAppendPrototypeFunctions(isInvokedByMicroApp, containerConfigGetter) {
  const rawHeadAppendChild = HTMLHeadElement.prototype.appendChild;
    
  if (rawHeadAppendChild[overwrittenSymbol] !== true) {
    function appendChildNew() {
      // 使用rawHeadAppendChild, isInvokedByMicroApp, containerConfigGetter
    }
    appendChildNew[overwrittenSymbol] = true;
    HTMLHeadElement.prototype.appendChild = appendChildNew;
  }
  
  return function unpatch() { // 当应用全部unmount时,会调用
    HTMLHeadElement.prototype.appendChild = rawHeadAppendChild;
  }
}

分析一下:

  1. 当第一个子应用加载时,会调用这个函数。
  2. rawXXX对象存储原生append。随后head的原型append被设置为新append。并且新append覆盖标志设为true。
  3. 当第二个子应用加载时。也会调用该函数。
  4. 此时仍然从原型中取,导致rawXXX对象变为新append。由于新append覆盖标志为true,所以跳过if。
  5. 当这两个子应用全部卸载时,调用unpatch()。此时会将原型append设置为新append(等于没变)。

这里面存在两个问题:

  • 只要有2个以上的子应用加载过,原生append就找不回来了。unpatch()函数也没有任何作用。

    image
  • 新append只会在首次加载时,创建一次。

    所以这个函数内用到的isInvokedByMicroAppcontainerConfigGetter就是第一个子应用在调用patchHTMLDynamicAppendPrototypeFunctions时提供的,并且不再改变。

    对于LooseSandbox这个问题很严重。因为它提供的isInvokedByMicroAppcontainerConfigGetter都是从函数作用域内取的参数。

    function patchLooseSandbox(appName: string, /* ... */) {
      const unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions(
        // 注意这个appName是从函数参数取到的
        () => checkActivityFunctions(window.location).some((name) => name === appName),
        () => ({ appName, /* ... */ }),
      );
    }

    假如第一个加载的子应用名叫vue-app,它调用patchHTMLDynamicAppendPrototypeFunctions,将原型append设置为新append新append内部使用的isInvokedByMicroApp参数由vue-app提供。因此这个appName将会永远都是vue-app

    不管当前是哪个应用处于激活状态,只要这个应用内调用了appendChild方法,就会拿当前window.locationvue-app进行比较。可想而知,答案是错的。

致谢

感谢qiankun的技术大佬,我已经尽量把问题写的很清晰。如果有什么错误,请及时指正。如果我提的问题确实存在,辛苦尽快调整~

相关环境信息

  • qiankun 版本:master版本,v2.10.16
@MaJiaXuan
Copy link

同问,在内存的时候发现了类似的问题
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants