diff --git a/src/lib/utils/lazy.ts b/src/lib/utils/lazy.ts new file mode 100644 index 0000000..19330cd --- /dev/null +++ b/src/lib/utils/lazy.ts @@ -0,0 +1,40 @@ +const factorySymbol = Symbol.for("bunny.lazyFactory"); +const cacheSymbol = Symbol("bunny.lazyCache"); +const dummySymbol = Symbol("bunny.lazyDummy"); + +const unconfigurable = ["arguments", "caller", "prototype"]; +const isUnconfigurable = (key: PropertyKey) => typeof key === "string" && unconfigurable.includes(key); + +const lazyHandler: ProxyHandler = { + ...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(fnName => { + return [fnName, (target: any, ...args: any[]) => { + // @ts-ignore + const func = s => Reflect[fnName](s, ...args); + return func(target[factorySymbol]()) ?? func(target[dummySymbol]); + }]; + })), + ownKeys: target => { + const cacheKeys = Reflect.ownKeys(target[factorySymbol]() ?? target[dummySymbol]); + unconfigurable.forEach(key => isUnconfigurable(key) && cacheKeys.push(key)); + return cacheKeys; + }, + getOwnPropertyDescriptor: (target, p) => { + if (isUnconfigurable(p)) return Reflect.getOwnPropertyDescriptor(target, p); + + const descriptor = Reflect.getOwnPropertyDescriptor(target[factorySymbol]() ?? target[dummySymbol], p); + if (descriptor) Object.defineProperty(target, p, descriptor); + return descriptor; + }, +}; + +/** + * Lazy proxy that will only call the factory function when needed (when a property is accessed) + * @param factory Factory function to create the object + * @returns A proxy that will call the factory function only when needed + */ +export function proxyLazy(factory: () => T, dummy: any = () => void 0): T { + dummy[factorySymbol] = () => dummy[cacheSymbol] ??= factory(); + dummy[dummySymbol] = dummy; + + return new Proxy(dummy, lazyHandler) as any; +} diff --git a/src/metro/utils.ts b/src/metro/utils.ts index 0a18db2..4151eed 100644 --- a/src/metro/utils.ts +++ b/src/metro/utils.ts @@ -1,3 +1,5 @@ +import { proxyLazy } from "@lib/utils/lazy"; + import { byDisplayName, byFilePath, byName, byProps, byStoreName, byTypeName } from "./filters"; import { findAllExports, findExports } from "./finders"; import { FilterCheckDef, FilterDefinition, ModuleExports } from "./types"; @@ -41,16 +43,25 @@ export function createSimpleFilter( } export const findByProps = (...props: string[]) => findExports(byProps(...props)); +export const findByPropsLazy = (...props: string[]) => proxyLazy(() => findByProps(...props)); export const findByPropsAll = (...props: string[]) => findAllExports(byProps(...props)); export const findByName = (name: string, expDefault = true) => findExports(expDefault ? byName(name) : byName.byDefault(name)); +export const findByNameLazy = (name: string, expDefault = true) => proxyLazy(() => findByName(name, expDefault)); export const findByNameAll = (name: string, expDefault = true) => findAllExports(expDefault ? byName(name) : byName.byDefault(name)); export const findByDisplayName = (name: string, expDefault = true) => findExports(expDefault ? byDisplayName(name) : byDisplayName.byDefault(name)); +export const findByDisplayNameLazy = (name: string, expDefault = true) => proxyLazy(() => findByDisplayName(name, expDefault)); export const findByDisplayNameAll = (name: string, expDefault = true) => findAllExports(expDefault ? byDisplayName(name) : byDisplayName.byDefault(name)); export const findByTypeName = (name: string, expDefault = true) => findExports(expDefault ? byTypeName(name) : byTypeName.byDefault(name)); +export const findByTypeNameLazy = (name: string, expDefault = true) => proxyLazy(() => findByTypeName(name, expDefault)); export const findByTypeNameAll = (name: string, expDefault = true) => findAllExports(expDefault ? byTypeName(name) : byTypeName.byDefault(name)); export const findByStoreName = (name: string) => findExports(byStoreName(name)); +export const findByStoreNameLazy = (name: string) => proxyLazy(() => findByStoreName(name)); + export const findByFilePath = (path: string) => findExports(byFilePath(path)); +export const findByFilePathLazy = (path: string) => proxyLazy(() => findByFilePath(path)); + +