UNPKG

@lesnoypudge/utils-react

Version:

lesnoypudge's utils-react

174 lines (173 loc) 6.2 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { autoBind, sleep, inRange, invariant } from "@lesnoypudge/utils"; import { asyncRetry } from "./utils/asyncRetry.js"; import { modifiedReactLazy } from "./utils/modifiedReactLazy.js"; class LazyLoad { constructor() { /** * Original React.lazy will cache rejected state and * refuse to try to load again. * This version modifies how internal cache works by not * remembering rejected state. * Source of React.lazy: https://github.com/facebook/react/blob/main/packages/react/src/ReactLazy.js * Abandoned (2019) pull request with similar solution: https://github.com/facebook/react/pull/15296 */ __publicField(this, "modifiedReactLazy", modifiedReactLazy); /** * Predefined wrapper for preloaded components. */ __publicField(this, "basePreloadedComponent", this.createBasePreloadedComponent()); /** * Predefined wrapper for async components. */ __publicField(this, "baseAsyncComponent", this.createBaseAsyncLoadedComponent()); autoBind(this); } /** * Creates a group of components that is loaded together * when one of them is called. * Loading is resolved when all components is loaded. * Throws error if called component is failed to load. */ createPreloadGroup(options) { const promiseFactoryList = []; const resolvedModules = []; const promisesInWork = []; const trigger = async () => { const promisesToAwait = promiseFactoryList.map(async (promiseFactory, index) => { const alreadyLoaded = resolvedModules[index]; if (alreadyLoaded) return alreadyLoaded; const alreadyResolving = promisesInWork[index]; if (alreadyResolving) return alreadyResolving; const modulePromise = asyncRetry(promiseFactory, options).then((v) => v ?? null); promisesInWork[index] = modulePromise; return await modulePromise; }); const results = await Promise.all(promisesToAwait); results.forEach((result, index) => { resolvedModules[index] = result; promisesInWork[index] = null; }); }; const withPreloadGroup = (factory) => { const index = promiseFactoryList.length; resolvedModules[index] = null; promiseFactoryList[index] = factory; const moduleOrPromiseModuleFactory = () => { const module = resolvedModules[index]; if (module) return module; return new Promise((res, rej) => { const asyncBody = async () => { await trigger(); const loadedComponent = resolvedModules[index]; invariant(loadedComponent, "Failed to lazy load."); return loadedComponent; }; asyncBody().then(res).catch(rej); }); }; return moduleOrPromiseModuleFactory; }; return { withPreloadGroup, trigger }; } /** * Creates a group of components that starts loading when * one of them is called. * Loading is resolved as soon as called component is loaded. * Other components is loaded in background. */ createAsyncLoadGroup(options) { const promiseFactoryList = []; const resolvedModules = []; const promises = []; const withAsyncLoadGroup = (factory) => { const currentIndex = promiseFactoryList.length; resolvedModules[currentIndex] = null; promiseFactoryList[currentIndex] = factory; const moduleOrPromiseModuleFactory = () => { const module = resolvedModules[currentIndex]; if (module) return module; promiseFactoryList.forEach((promiseModuleFactory, index) => { if (resolvedModules[index]) return; if (promises[index]) return; const promise = asyncRetry(promiseModuleFactory, options).then((maybeComponent) => { if (!maybeComponent) return null; resolvedModules[index] = maybeComponent; return maybeComponent ?? null; }).finally(() => { promises[index] = null; }); promises[index] = promise; }); return new Promise((res, rej) => { const asyncBody = async () => { const module2 = await promises[currentIndex]; invariant(module2); return module2; }; asyncBody().then(res).catch(rej); }); }; return moduleOrPromiseModuleFactory; }; return { withAsyncLoadGroup }; } /** * Wraps a component loading function to add additional * delay before loading in dev mode. */ withDelay(factory, options) { return async () => { const result = await factory(); if (options == null ? void 0 : options.enable) { await sleep(options.delay ?? inRange(300, 500)); } return result; }; } /** * Wrapper for basic lazy component. */ baseComponent(factory, options) { return this.modifiedReactLazy(this.withDelay(factory, options)); } /** * Creates predefined wrapper for preloaded components. */ createBasePreloadedComponent(options) { const { withPreloadGroup, trigger } = this.createPreloadGroup(options == null ? void 0 : options.retry); const load = (factory) => { return this.modifiedReactLazy(withPreloadGroup(this.withDelay(factory, options == null ? void 0 : options.delay))); }; return { trigger, load }; } /** * Creates predefined wrapper for async components. */ createBaseAsyncLoadedComponent(options) { const { withAsyncLoadGroup } = this.createAsyncLoadGroup(options == null ? void 0 : options.retry); return (factory) => { return this.modifiedReactLazy(withAsyncLoadGroup(this.withDelay(factory, options == null ? void 0 : options.delay))); }; } } const lazyLoad = new LazyLoad(); export { lazyLoad }; //# sourceMappingURL=lazyLoad.js.map