130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
import { fileExists, readFile, writeFile } from "@lib/api/native/fs";
|
|
import { NativeClientInfoModule } from "@lib/api/native/modules";
|
|
import { debounce } from "es-toolkit";
|
|
|
|
import { ModuleFlags, ModulesMapInternal } from "./enums";
|
|
|
|
const CACHE_VERSION = 102;
|
|
const BUNNY_METRO_CACHE_PATH = "caches/metro_modules.json";
|
|
|
|
type ModulesMap = {
|
|
[flag in number | `_${ModulesMapInternal}`]?: ModuleFlags;
|
|
};
|
|
|
|
let _metroCache = null as unknown as ReturnType<typeof buildInitCache>;
|
|
|
|
export const getMetroCache = () => _metroCache;
|
|
|
|
function buildInitCache() {
|
|
const cache = {
|
|
_v: CACHE_VERSION,
|
|
_buildNumber: NativeClientInfoModule.getConstants().Build,
|
|
_modulesCount: Object.keys(window.modules).length,
|
|
flagsIndex: {} as Record<string, number>,
|
|
findIndex: {} as Record<string, ModulesMap | undefined>,
|
|
polyfillIndex: {} as Record<string, ModulesMap | undefined>
|
|
} as const;
|
|
|
|
// Force load all modules so useful modules are pre-cached. Add a minor
|
|
// delay so the cache is initialized before the modules are loaded.
|
|
setTimeout(() => {
|
|
for (const id in window.modules) {
|
|
require("./modules").requireModule(id);
|
|
}
|
|
}, 100);
|
|
|
|
_metroCache = cache;
|
|
return cache;
|
|
}
|
|
|
|
/** @internal */
|
|
export async function initMetroCache() {
|
|
if (!await fileExists(BUNNY_METRO_CACHE_PATH)) return void buildInitCache();
|
|
const rawCache = await readFile(BUNNY_METRO_CACHE_PATH);
|
|
|
|
try {
|
|
_metroCache = JSON.parse(rawCache);
|
|
if (_metroCache._v !== CACHE_VERSION) {
|
|
_metroCache = null!;
|
|
throw "cache invalidated; cache version outdated";
|
|
}
|
|
if (_metroCache._buildNumber !== NativeClientInfoModule.getConstants().Build) {
|
|
_metroCache = null!;
|
|
throw "cache invalidated; version mismatch";
|
|
}
|
|
if (_metroCache._modulesCount !== Object.keys(window.modules).length) {
|
|
_metroCache = null!;
|
|
throw "cache invalidated; modules count mismatch";
|
|
}
|
|
} catch {
|
|
buildInitCache();
|
|
}
|
|
}
|
|
|
|
const saveCache = debounce(() => {
|
|
writeFile(BUNNY_METRO_CACHE_PATH, JSON.stringify(_metroCache));
|
|
}, 1000);
|
|
|
|
function extractExportsFlags(moduleExports: any) {
|
|
if (!moduleExports) return undefined;
|
|
|
|
const bit = ModuleFlags.EXISTS;
|
|
return bit;
|
|
}
|
|
|
|
/** @internal */
|
|
export function indexExportsFlags(moduleId: number, moduleExports: any) {
|
|
const flags = extractExportsFlags(moduleExports);
|
|
if (flags && flags !== ModuleFlags.EXISTS) {
|
|
_metroCache.flagsIndex[moduleId] = flags;
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
export function indexBlacklistFlag(id: number) {
|
|
_metroCache.flagsIndex[id] |= ModuleFlags.BLACKLISTED;
|
|
}
|
|
|
|
/** @internal */
|
|
export function indexAssetModuleFlag(id: number) {
|
|
_metroCache.flagsIndex[id] |= ModuleFlags.ASSET;
|
|
}
|
|
|
|
/** @internal */
|
|
export function getCacherForUniq(uniq: string, allFind: boolean) {
|
|
const indexObject = _metroCache.findIndex[uniq] ??= {};
|
|
|
|
return {
|
|
cacheId(moduleId: number, exports: any) {
|
|
indexObject[moduleId] ??= extractExportsFlags(exports);
|
|
|
|
saveCache();
|
|
},
|
|
// Finish may not be called by single find
|
|
finish(notFound: boolean) {
|
|
if (allFind) indexObject[`_${ModulesMapInternal.FULL_LOOKUP}`] = 1;
|
|
if (notFound) indexObject[`_${ModulesMapInternal.NOT_FOUND}`] = 1;
|
|
|
|
saveCache();
|
|
}
|
|
};
|
|
}
|
|
|
|
/** @internal */
|
|
export function getPolyfillModuleCacher(name: string) {
|
|
const indexObject = _metroCache.polyfillIndex[name] ??= {};
|
|
|
|
return {
|
|
getModules() {
|
|
return require("@metro/internals/modules").getCachedPolyfillModules(name);
|
|
},
|
|
cacheId(moduleId: number) {
|
|
indexObject[moduleId] = 1;
|
|
saveCache();
|
|
},
|
|
finish() {
|
|
indexObject[`_${ModulesMapInternal.FULL_LOOKUP}`] = 1;
|
|
saveCache();
|
|
}
|
|
};
|
|
}
|