仍旧从loadApp函数入手
export async function loadApp<T extends ObjectType>( app: LoadableApp<T>, configuration: FrameworkConfiguration = {}, lifeCycles?: FrameworkLifeCycles<T>,): Promise<arcelConfigObjectGetter> { 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<ropertyKey, 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隔离机制
乾坤沙箱实现原理 |