123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
/**
|
|
* @author Kuitos
|
|
* @since 2019-02-26
|
|
*/
|
|
|
|
import type { Entry, ImportEntryOpts } from 'import-html-entry';
|
|
import { importEntry } from 'import-html-entry';
|
|
import { isFunction } from 'lodash';
|
|
import { getAppStatus, getMountedApps, NOT_LOADED } from 'single-spa';
|
|
import type { AppMetadata, PrefetchStrategy } from './interfaces';
|
|
|
|
declare global {
|
|
interface NetworkInformation {
|
|
saveData: boolean;
|
|
effectiveType: string;
|
|
}
|
|
}
|
|
|
|
// RIC and shim for browsers setTimeout() without it
|
|
const requestIdleCallback =
|
|
window.requestIdleCallback ||
|
|
function requestIdleCallback(cb: CallableFunction) {
|
|
const start = Date.now();
|
|
return setTimeout(() => {
|
|
cb({
|
|
didTimeout: false,
|
|
timeRemaining() {
|
|
return Math.max(0, 50 - (Date.now() - start));
|
|
},
|
|
});
|
|
}, 1);
|
|
};
|
|
|
|
declare global {
|
|
interface Navigator {
|
|
connection: {
|
|
saveData: boolean;
|
|
effectiveType: string;
|
|
type: 'bluetooth' | 'cellular' | 'ethernet' | 'none' | 'wifi' | 'wimax' | 'other' | 'unknown';
|
|
};
|
|
}
|
|
}
|
|
|
|
const isSlowNetwork = navigator.connection
|
|
? navigator.connection.saveData ||
|
|
(navigator.connection.type !== 'wifi' &&
|
|
navigator.connection.type !== 'ethernet' &&
|
|
/([23])g/.test(navigator.connection.effectiveType))
|
|
: false;
|
|
|
|
/**
|
|
* prefetch assets, do nothing while in mobile network
|
|
* @param entry
|
|
* @param opts
|
|
*/
|
|
function prefetch(entry: Entry, opts?: ImportEntryOpts): void {
|
|
if (!navigator.onLine || isSlowNetwork) {
|
|
// Don't prefetch if in a slow network or offline
|
|
return;
|
|
}
|
|
|
|
requestIdleCallback(async () => {
|
|
const { getExternalScripts, getExternalStyleSheets } = await importEntry(entry, opts);
|
|
requestIdleCallback(getExternalStyleSheets);
|
|
requestIdleCallback(getExternalScripts);
|
|
});
|
|
}
|
|
|
|
function prefetchAfterFirstMounted(apps: AppMetadata[], opts?: ImportEntryOpts): void {
|
|
window.addEventListener('single-spa:first-mount', function listener() {
|
|
const notLoadedApps = apps.filter((app) => getAppStatus(app.name) === NOT_LOADED);
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
const mountedApps = getMountedApps();
|
|
console.log(`[qiankun] prefetch starting after ${mountedApps} mounted...`, notLoadedApps);
|
|
}
|
|
|
|
notLoadedApps.forEach(({ entry }) => prefetch(entry, opts));
|
|
|
|
window.removeEventListener('single-spa:first-mount', listener);
|
|
});
|
|
}
|
|
|
|
export function prefetchImmediately(apps: AppMetadata[], opts?: ImportEntryOpts): void {
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.log('[qiankun] prefetch starting for apps...', apps);
|
|
}
|
|
|
|
apps.forEach(({ entry }) => prefetch(entry, opts));
|
|
}
|
|
|
|
export function doPrefetchStrategy(
|
|
apps: AppMetadata[],
|
|
prefetchStrategy: PrefetchStrategy,
|
|
importEntryOpts?: ImportEntryOpts,
|
|
) {
|
|
const appsName2Apps = (names: string[]): AppMetadata[] => apps.filter((app) => names.includes(app.name));
|
|
|
|
if (Array.isArray(prefetchStrategy)) {
|
|
prefetchAfterFirstMounted(appsName2Apps(prefetchStrategy as string[]), importEntryOpts);
|
|
} else if (isFunction(prefetchStrategy)) {
|
|
(async () => {
|
|
// critical rendering apps would be prefetch as earlier as possible
|
|
const { criticalAppNames = [], minorAppsName = [] } = await prefetchStrategy(apps);
|
|
prefetchImmediately(appsName2Apps(criticalAppNames), importEntryOpts);
|
|
prefetchAfterFirstMounted(appsName2Apps(minorAppsName), importEntryOpts);
|
|
})();
|
|
} else {
|
|
switch (prefetchStrategy) {
|
|
case true:
|
|
prefetchAfterFirstMounted(apps, importEntryOpts);
|
|
break;
|
|
|
|
case 'all':
|
|
prefetchImmediately(apps, importEntryOpts);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|