NodeWatchFileSystem.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. const Watchpack = require("watchpack");
  8. /** @typedef {import("../../declarations/WebpackOptions").WatchOptions} WatchOptions */
  9. /** @typedef {import("../FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */
  10. /** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
  11. /** @typedef {import("../util/fs").WatchFileSystem} WatchFileSystem */
  12. /** @typedef {import("../util/fs").WatchMethod} WatchMethod */
  13. /** @typedef {import("../util/fs").Watcher} Watcher */
  14. class NodeWatchFileSystem {
  15. /**
  16. * @param {InputFileSystem} inputFileSystem input filesystem
  17. */
  18. constructor(inputFileSystem) {
  19. this.inputFileSystem = inputFileSystem;
  20. this.watcherOptions = {
  21. aggregateTimeout: 0
  22. };
  23. this.watcher = new Watchpack(this.watcherOptions);
  24. }
  25. /**
  26. * @param {Iterable<string>} files watched files
  27. * @param {Iterable<string>} directories watched directories
  28. * @param {Iterable<string>} missing watched existence entries
  29. * @param {number} startTime timestamp of start time
  30. * @param {WatchOptions} options options object
  31. * @param {function(Error | null, Map<string, FileSystemInfoEntry>, Map<string, FileSystemInfoEntry>, Set<string>, Set<string>): void} callback aggregated callback
  32. * @param {function(string, number): void} callbackUndelayed callback when the first change was detected
  33. * @returns {Watcher} a watcher
  34. */
  35. watch(
  36. files,
  37. directories,
  38. missing,
  39. startTime,
  40. options,
  41. callback,
  42. callbackUndelayed
  43. ) {
  44. if (!files || typeof files[Symbol.iterator] !== "function") {
  45. throw new Error("Invalid arguments: 'files'");
  46. }
  47. if (!directories || typeof directories[Symbol.iterator] !== "function") {
  48. throw new Error("Invalid arguments: 'directories'");
  49. }
  50. if (!missing || typeof missing[Symbol.iterator] !== "function") {
  51. throw new Error("Invalid arguments: 'missing'");
  52. }
  53. if (typeof callback !== "function") {
  54. throw new Error("Invalid arguments: 'callback'");
  55. }
  56. if (typeof startTime !== "number" && startTime) {
  57. throw new Error("Invalid arguments: 'startTime'");
  58. }
  59. if (typeof options !== "object") {
  60. throw new Error("Invalid arguments: 'options'");
  61. }
  62. if (typeof callbackUndelayed !== "function" && callbackUndelayed) {
  63. throw new Error("Invalid arguments: 'callbackUndelayed'");
  64. }
  65. const oldWatcher = this.watcher;
  66. this.watcher = new Watchpack(options);
  67. if (callbackUndelayed) {
  68. this.watcher.once("change", callbackUndelayed);
  69. }
  70. const fetchTimeInfo = () => {
  71. const fileTimeInfoEntries = new Map();
  72. const contextTimeInfoEntries = new Map();
  73. if (this.watcher) {
  74. this.watcher.collectTimeInfoEntries(
  75. fileTimeInfoEntries,
  76. contextTimeInfoEntries
  77. );
  78. }
  79. return { fileTimeInfoEntries, contextTimeInfoEntries };
  80. };
  81. this.watcher.once(
  82. "aggregated",
  83. /**
  84. * @param {Set<string>} changes changes
  85. * @param {Set<string>} removals removals
  86. */
  87. (changes, removals) => {
  88. // pause emitting events (avoids clearing aggregated changes and removals on timeout)
  89. this.watcher.pause();
  90. const fs = this.inputFileSystem;
  91. if (fs && fs.purge) {
  92. for (const item of changes) {
  93. fs.purge(item);
  94. }
  95. for (const item of removals) {
  96. fs.purge(item);
  97. }
  98. }
  99. const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo();
  100. callback(
  101. null,
  102. fileTimeInfoEntries,
  103. contextTimeInfoEntries,
  104. changes,
  105. removals
  106. );
  107. }
  108. );
  109. this.watcher.watch({ files, directories, missing, startTime });
  110. if (oldWatcher) {
  111. oldWatcher.close();
  112. }
  113. return {
  114. close: () => {
  115. if (this.watcher) {
  116. this.watcher.close();
  117. this.watcher = null;
  118. }
  119. },
  120. pause: () => {
  121. if (this.watcher) {
  122. this.watcher.pause();
  123. }
  124. },
  125. getAggregatedRemovals: util.deprecate(
  126. () => {
  127. const items = this.watcher && this.watcher.aggregatedRemovals;
  128. const fs = this.inputFileSystem;
  129. if (items && fs && fs.purge) {
  130. for (const item of items) {
  131. fs.purge(item);
  132. }
  133. }
  134. return items;
  135. },
  136. "Watcher.getAggregatedRemovals is deprecated in favor of Watcher.getInfo since that's more performant.",
  137. "DEP_WEBPACK_WATCHER_GET_AGGREGATED_REMOVALS"
  138. ),
  139. getAggregatedChanges: util.deprecate(
  140. () => {
  141. const items = this.watcher && this.watcher.aggregatedChanges;
  142. const fs = this.inputFileSystem;
  143. if (items && fs && fs.purge) {
  144. for (const item of items) {
  145. fs.purge(item);
  146. }
  147. }
  148. return items;
  149. },
  150. "Watcher.getAggregatedChanges is deprecated in favor of Watcher.getInfo since that's more performant.",
  151. "DEP_WEBPACK_WATCHER_GET_AGGREGATED_CHANGES"
  152. ),
  153. getFileTimeInfoEntries: util.deprecate(
  154. () => {
  155. return fetchTimeInfo().fileTimeInfoEntries;
  156. },
  157. "Watcher.getFileTimeInfoEntries is deprecated in favor of Watcher.getInfo since that's more performant.",
  158. "DEP_WEBPACK_WATCHER_FILE_TIME_INFO_ENTRIES"
  159. ),
  160. getContextTimeInfoEntries: util.deprecate(
  161. () => {
  162. return fetchTimeInfo().contextTimeInfoEntries;
  163. },
  164. "Watcher.getContextTimeInfoEntries is deprecated in favor of Watcher.getInfo since that's more performant.",
  165. "DEP_WEBPACK_WATCHER_CONTEXT_TIME_INFO_ENTRIES"
  166. ),
  167. getInfo: () => {
  168. const removals = this.watcher && this.watcher.aggregatedRemovals;
  169. const changes = this.watcher && this.watcher.aggregatedChanges;
  170. const fs = this.inputFileSystem;
  171. if (fs && fs.purge) {
  172. if (removals) {
  173. for (const item of removals) {
  174. fs.purge(item);
  175. }
  176. }
  177. if (changes) {
  178. for (const item of changes) {
  179. fs.purge(item);
  180. }
  181. }
  182. }
  183. const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo();
  184. return {
  185. changes,
  186. removals,
  187. fileTimeInfoEntries,
  188. contextTimeInfoEntries
  189. };
  190. }
  191. };
  192. }
  193. }
  194. module.exports = NodeWatchFileSystem;