utils.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. "use strict";
  2. /** @typedef {import("webpack").Compilation["inputFileSystem"] } InputFileSystem */
  3. /** @typedef {import("fs").Stats } Stats */
  4. /**
  5. * @param {InputFileSystem} inputFileSystem
  6. * @param {string} path
  7. * @return {Promise<undefined | Stats>}
  8. */
  9. function stat(inputFileSystem, path) {
  10. return new Promise((resolve, reject) => {
  11. inputFileSystem.stat(path,
  12. /**
  13. * @param {null | undefined | NodeJS.ErrnoException} err
  14. * @param {undefined | Stats} stats
  15. */
  16. // @ts-ignore
  17. (err, stats) => {
  18. if (err) {
  19. reject(err);
  20. return;
  21. }
  22. resolve(stats);
  23. });
  24. });
  25. }
  26. /**
  27. * @param {InputFileSystem} inputFileSystem
  28. * @param {string} path
  29. * @return {Promise<string | Buffer>}
  30. */
  31. function readFile(inputFileSystem, path) {
  32. return new Promise((resolve, reject) => {
  33. inputFileSystem.readFile(path,
  34. /**
  35. * @param {null | undefined | NodeJS.ErrnoException} err
  36. * @param {undefined | string | Buffer} data
  37. */
  38. (err, data) => {
  39. if (err) {
  40. reject(err);
  41. return;
  42. }
  43. resolve( /** @type {string | Buffer} */data);
  44. });
  45. });
  46. }
  47. const notSettled = Symbol(`not-settled`);
  48. /**
  49. * @template T
  50. * @typedef {() => Promise<T>} Task
  51. */
  52. /**
  53. * Run tasks with limited concurrency.
  54. * @template T
  55. * @param {number} limit - Limit of tasks that run at once.
  56. * @param {Task<T>[]} tasks - List of tasks to run.
  57. * @returns {Promise<T[]>} A promise that fulfills to an array of the results
  58. */
  59. function throttleAll(limit, tasks) {
  60. if (!Number.isInteger(limit) || limit < 1) {
  61. throw new TypeError(`Expected \`limit\` to be a finite number > 0, got \`${limit}\` (${typeof limit})`);
  62. }
  63. if (!Array.isArray(tasks) || !tasks.every(task => typeof task === `function`)) {
  64. throw new TypeError(`Expected \`tasks\` to be a list of functions returning a promise`);
  65. }
  66. return new Promise((resolve, reject) => {
  67. const result = Array(tasks.length).fill(notSettled);
  68. const entries = tasks.entries();
  69. const next = () => {
  70. const {
  71. done,
  72. value
  73. } = entries.next();
  74. if (done) {
  75. const isLast = !result.includes(notSettled);
  76. if (isLast) {
  77. resolve( /** @type{T[]} **/result);
  78. }
  79. return;
  80. }
  81. const [index, task] = value;
  82. /**
  83. * @param {T} x
  84. */
  85. const onFulfilled = x => {
  86. result[index] = x;
  87. next();
  88. };
  89. task().then(onFulfilled, reject);
  90. };
  91. Array(limit).fill(0).forEach(next);
  92. });
  93. }
  94. /**
  95. * @template T
  96. * @param fn {(function(): any) | undefined}
  97. * @returns {function(): T}
  98. */
  99. function memoize(fn) {
  100. let cache = false;
  101. /** @type {T} */
  102. let result;
  103. return () => {
  104. if (cache) {
  105. return result;
  106. }
  107. result = /** @type {function(): any} */fn();
  108. cache = true;
  109. // Allow to clean up memory for fn
  110. // and all dependent resources
  111. // eslint-disable-next-line no-undefined, no-param-reassign
  112. fn = undefined;
  113. return result;
  114. };
  115. }
  116. module.exports = {
  117. stat,
  118. readFile,
  119. throttleAll,
  120. memoize
  121. };