packages/core/loader/src/requireEnsure.ts (105 lines of code) (raw):
import { v4 as uuidv4 } from 'uuid';
import { globalModule } from './module';
import { Module, Record } from './module/Module';
import { IBundleOption } from './type';
/**
* 更新模块的加载状态,如果加载执行成功,loaded 为 true,则设置 promise 状态 done
* @param id
*/
function resolveRecord(id: string) {
const record = Module.record.get(id);
if (record) {
// 加载完成时会通过全局 hook 注册,成功后设置 loaded 为 true
if (record.loaded) {
record.resolve();
if (id.endsWith('_scripts_')) {
Module.record.delete(id);
}
} else {
// 当记录中,该模块加载状态为 false,清除该记录
record.reject(record.error);
Module.record.delete(id);
}
}
}
/**
* handle script loaded
* @param id
* @param script
* @param timeout
*/
function onScriptComplete(id: string, script: HTMLScriptElement, timeout: number) {
script.onerror = null;
script.onload = null;
clearTimeout(timeout);
resolveRecord(id);
}
/**
* handle script load error
* @param id
* @param script
* @param timeout
*/
function onScriptError(id: string, script: HTMLScriptElement, timeout: number) {
const record = Module.record.get(id);
onScriptComplete(id, script, timeout);
if (record) {
record.reject(new Error('script load fail'));
}
}
/**
* append script
* @param {IBundleOption} bundle
*/
function jsonpRequire(id: string, url: string, uuid: string) {
const script = document.createElement('script');
script.charset = 'utf-8';
script.src = url;
script.crossOrigin = 'anonymous';
script.setAttribute('nonce', '');
// 用于加载的脚本判断来源
script.setAttribute('data-from', 'alfa');
script.setAttribute('data-uuid', uuid);
const timeout = window.setTimeout(() => {
onScriptError(id, script, timeout);
}, 120000);
script.onerror = () => {
onScriptError(id, script, timeout);
};
script.onload = () => {
onScriptComplete(id, script, timeout);
};
document.head.appendChild(script);
}
export async function xmlRequire(id: string, url: string, transform: (source: string) => string) {
const resp = await fetch(url);
const code = await resp.text();
// eslint-disable-next-line no-eval
window.eval(`__CONSOLE_OS_GLOBAL_HOOK__('${id.replace('_scripts_', '')}', function(require, module, exports, {window, location, history, document}){
with(window.__CONSOLE_OS_GLOBAL_VARS_){
${transform(code)}
}
})`);
resolveRecord(id);
}
// if module has been resolved
function isModuleResolved(bundle: IBundleOption) {
return !bundle.noCache && globalModule.resolved(bundle.id);
}
/**
* async require the bundle from url
* @param bundle {IBundleOption}
*/
export async function requireEnsure<T>(bundle: IBundleOption) {
const transform = bundle.transform || ((source: string) => source);
if (isModuleResolved(bundle)) {
// if loader contains the context(window, location)
// then get the new export using new context
if (bundle.context) {
return globalModule.requireIsolateWithContext(bundle.id, bundle.context);
}
// return the cached module
return globalModule.require(bundle.id);
}
const promises: Array<Promise<T>> = [];
let chunkRecord: Record<T> = Module.record.get(bundle.id);
let pending = false;
if (!chunkRecord || !chunkRecord.loaded) {
if (chunkRecord) {
promises.push(chunkRecord.promise);
pending = true;
} else {
const promise = new Promise<T>((resolve, reject) => {
chunkRecord = new Record();
chunkRecord.resolve = resolve;
chunkRecord.reject = reject;
chunkRecord.context = bundle.context;
chunkRecord.deps = bundle.deps;
chunkRecord.uuid = uuidv4();
Module.record.set(bundle.id, chunkRecord);
});
chunkRecord.promise = promise;
promises.push(promise);
if (bundle.xmlrequest) {
xmlRequire(bundle.id, bundle.url, transform);
} else {
jsonpRequire(bundle.id, bundle.url, chunkRecord.uuid);
}
}
}
await Promise.all(promises);
// pending 的 chunk 需要重新执行
if (isModuleResolved(bundle) && pending) {
// if loader contains the context(window, location)
// then get the new export using new context
if (bundle.context) {
return globalModule.requireIsolateWithContext(bundle.id, bundle.context);
}
}
return globalModule.require(bundle.id);
}