美文网首页js css html
121.乾坤js隔离机制

121.乾坤js隔离机制

作者: wo不是黄蓉 | 来源:发表于2022-11-17 22:35 被阅读0次
乾坤js隔离机制.jpg

依旧从loadApp函数入手

 export async function loadApp<T extends ObjectType>(
  app: LoadableApp<T>,
  configuration: FrameworkConfiguration = {},
  lifeCycles?: FrameworkLifeCycles<T>,
): Promise<ParcelConfigObjectGetter> {
      const {
        singular = false,
        sandbox = true,
        excludeAssetFilter,
        globalContext = window,
        ...importEntryOpts
      } = configuration;
    if (sandbox) {
        sandboxContainer = createSandboxContainer(
          appInstanceId,
          // FIXME should use a strict sandbox logic while remount, see https://github.com/umijs/qiankun/issues/518
          initialAppWrapperGetter,
          scopedCSS,
          useLooseSandbox,
          excludeAssetFilter,
          global,
          speedySandbox,
        );
        // 用沙箱的代理对象作为接下来使用的全局对象
        global = sandboxContainer.instance.proxy as typeof window;
        mountSandbox = sandboxContainer.mount;
        unmountSandbox = sandboxContainer.unmount;
      }`
}

createSandboxContainer方法,创建沙箱容器,根据浏览器环境判断和配置项决定使用哪种沙箱模式,支持proxy的浏览器,如果没有明确配置使用单应用沙箱则使用ProxySandbox

export function createSandboxContainer(
  appName: string,
  elementGetter: () => HTMLElement | ShadowRoot,
  scopedCSS: boolean,
  useLooseSandbox?: boolean,
  excludeAssetFilter?: (url: string) => boolean,
  globalContext?: typeof window,
  speedySandBox?: boolean,
      ) {
          let sandbox: SandBox;
          //判断浏览器是否支持proxy,如果支持使用proxy,不支持使用快照沙箱
          if (window.Proxy) {
              //LegacySandbox:支持单应用的沙箱,ProxySandbox:支持多应用的沙箱
            sandbox = useLooseSandbox ? new LegacySandbox(appName, globalContext) : new ProxySandbox(appName, globalContext);
          } else {
             //快照沙箱
            sandbox = new SnapshotSandbox(appName);
          }      
          
          return {
              instance:sandbox,
              async mount(){
                  //启动沙箱
                 sandbox.active(); 
                  //...省略一些副作用处理代码
              },
              async unmount(){
              //关闭沙箱
                sandbox.inactive();  
            },
          }
      }

等到子应用挂载的时候执行mount方法,此时由于loadApp中使用的是沙箱作为代理对象的,因此执行的mount方法即createSandboxContainer的返回结果,在返回的mount函数中启动沙箱。

关于快照沙箱的实现原理可以参考这篇文章,或者直接参考杨艺韬大佬写的乾坤系列源码的文章

总结来说就是通过active 和Inactive来控制沙箱的生效和失效控制沙箱的启动和关闭,本质还是对window上属性的操作,区别在于直接操作还是通过代理操作。

而单应用和多应用的区别ProxySandbox为支持多应用的代理沙箱 ,LegacySandbox仅仅允许页面同时运行一个微应用 。

关于代理沙箱,单应用和多应用的区别没有看太懂?同样都是使用proxy实现的代理对象也是window为什么proxySandbox就可以实现多应用的隔离呢?

我想应该是因为这段代码,通过rawObjectDefineProperty实现对globalContext的深拷贝,这样每次新创建一个ProxySandbox实例就会重新初始化一个fakeWindow window环境,以此来达到多应用相互隔离的效果。

//Object.defineProperty直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
const rawObjectDefineProperty = Object.defineProperty;
function createFakeWindow(globalContext: Window) {
  // map always has the fastest performance in has check scenario
  // see https://jsperf.com/array-indexof-vs-set-has/23
  const propertiesWithGetter = new Map<PropertyKey, boolean>();
  const fakeWindow = {} as FakeWindow;

  //过滤不可配置的属性,
  Object.getOwnPropertyNames(globalContext)
    .filter((p) => {
      const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
      return !descriptor?.configurable;
    })
    .forEach((p) => {
      const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
      if (descriptor) {
        const hasGetter = Object.prototype.hasOwnProperty.call(descriptor, 'get');
        if (
          p === 'top' ||
          p === 'parent' ||
          p === 'self' ||
          p === 'window' ||
          (process.env.NODE_ENV === 'test' && (p === 'mockTop' || p === 'mockSafariTop'))
        ) {
          descriptor.configurable = true;
          if (!hasGetter) {
            descriptor.writable = true;
          }
        }
        if (hasGetter) propertiesWithGetter.set(p, true);
        rawObjectDefineProperty(fakeWindow, p, Object.freeze(descriptor));
      }
    });

  return {
    fakeWindow,
    propertiesWithGetter,
  };
}

LegacySandbox是直接操作目标对象的,从这边设置属性的代码可以看出来。

export default class LegacySandbox implements SandBox {
  private setWindowProp(prop: PropertyKey, value: any, toDelete?: boolean) {
    //设置的值不存在,删除window上的属性
    if (value === undefined && toDelete) {
      // eslint-disable-next-line no-param-reassign
      delete (this.globalContext as any)[prop];
    } else if (isPropConfigurable(this.globalContext, prop) && typeof prop !== 'symbol') {
      //window上存在的属性并且可操作,给属性设置值
      Object.defineProperty(this.globalContext, prop, { writable: true, configurable: true });
      // eslint-disable-next-line no-param-reassign
      (this.globalContext as any)[prop] = value;
    }
  }
}

乾坤框架系列学习
01.学习微前端架构-乾坤
02.资源加载过程分析
03.乾坤css加载机制
04.乾坤js隔离机制
乾坤沙箱实现原理

相关文章

网友评论

    本文标题:121.乾坤js隔离机制

    本文链接:https://www.haomeiwen.com/subject/gweuxdtx.html