lazyModule.js 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. /**
  7. * Declares a module that loads on first use, in the shape a dynamic `import()`
  8. * has: the accessor resolves to the module and the caller awaits it. The thunk
  9. * is a `require` until `lib/` moves to ecma modules, which changes that keyword
  10. * and nothing else — `Promise.resolve` already passes an `import()` promise
  11. * through. It is never called outside the promise, so nothing here needs a
  12. * synchronous load.
  13. *
  14. * `loaded` reads the module back for a consumer with nowhere to await — a
  15. * synchronous hook, a dependency template, a generator. It throws until an
  16. * awaited call has resolved, so every such consumer must sit behind one
  17. * (`NormalModuleFactory`'s `prepareModuleType`, `Compilation`'s pending runtime
  18. * modules). A module with no such boundary is required at the top of its file
  19. * instead of being declared here.
  20. * @template T
  21. * @param {() => T | Promise<T>} load loads the module
  22. * @returns {LazyModuleAccessor<T>} resolves the module, loading it once
  23. */
  24. const lazyModule = (load) => {
  25. /** @type {Promise<T> | undefined} */
  26. let promise;
  27. /** @type {T | undefined} */
  28. let value;
  29. const get = () => {
  30. if (promise === undefined) {
  31. promise = Promise.resolve(load()).then((module) => (value = module));
  32. // release the loader and everything it holds
  33. /** @type {(() => T | Promise<T>) | undefined} */
  34. (load) = undefined;
  35. }
  36. return promise;
  37. };
  38. get.loaded = () => {
  39. if (value === undefined) {
  40. throw new Error(
  41. "Lazy module was read before an awaited call resolved it; preload it at the nearest async boundary"
  42. );
  43. }
  44. return value;
  45. };
  46. return get;
  47. };
  48. /**
  49. * @template T
  50. * @typedef {(() => Promise<T>) & { loaded: () => T }} LazyModuleAccessor
  51. */
  52. module.exports = lazyModule;