依旧从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隔离机制
乾坤沙箱实现原理
网友评论