@lesnoypudge/utils-react
Version:
lesnoypudge's utils-react
174 lines (173 loc) • 6.2 kB
JavaScript
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