metro-file-map 0.83.4 → 0.83.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/package.json +3 -2
  2. package/src/Watcher.d.ts +74 -0
  3. package/src/Watcher.js +68 -48
  4. package/src/Watcher.js.flow +84 -51
  5. package/src/cache/DiskCacheManager.d.ts +49 -0
  6. package/src/cache/DiskCacheManager.js +1 -5
  7. package/src/constants.d.ts +22 -0
  8. package/src/crawlers/node/hasNativeFindSupport.d.ts +19 -0
  9. package/src/crawlers/node/index.d.ts +21 -0
  10. package/src/crawlers/node/index.js +6 -10
  11. package/src/crawlers/node/index.js.flow +8 -6
  12. package/src/crawlers/watchman/index.d.ts +23 -0
  13. package/src/crawlers/watchman/index.js +2 -9
  14. package/src/crawlers/watchman/index.js.flow +2 -6
  15. package/src/flow-types.d.ts +460 -0
  16. package/src/flow-types.js.flow +89 -29
  17. package/src/index.d.ts +182 -0
  18. package/src/index.js +148 -132
  19. package/src/index.js.flow +200 -155
  20. package/src/lib/FileProcessor.d.ts +60 -0
  21. package/src/lib/FileProcessor.js +1 -5
  22. package/src/lib/FileSystemChangeAggregator.d.ts +40 -0
  23. package/src/lib/FileSystemChangeAggregator.js +89 -0
  24. package/src/lib/FileSystemChangeAggregator.js.flow +143 -0
  25. package/src/lib/RootPathUtils.d.ts +30 -0
  26. package/src/lib/RootPathUtils.js +2 -9
  27. package/src/lib/TreeFS.d.ts +174 -0
  28. package/src/lib/TreeFS.js +68 -21
  29. package/src/lib/TreeFS.js.flow +89 -16
  30. package/src/lib/checkWatchmanCapabilities.d.ts +20 -0
  31. package/src/lib/normalizePathSeparatorsToPosix.d.ts +20 -0
  32. package/src/lib/normalizePathSeparatorsToPosix.js +1 -4
  33. package/src/lib/normalizePathSeparatorsToSystem.d.ts +20 -0
  34. package/src/lib/normalizePathSeparatorsToSystem.js +1 -4
  35. package/src/lib/rootRelativeCacheKeys.d.ts +24 -0
  36. package/src/lib/rootRelativeCacheKeys.js +1 -5
  37. package/src/lib/sorting.d.ts +23 -0
  38. package/src/plugins/DependencyPlugin.d.ts +52 -0
  39. package/src/plugins/DependencyPlugin.js +1 -3
  40. package/src/plugins/DependencyPlugin.js.flow +1 -16
  41. package/src/plugins/HastePlugin.d.ts +69 -0
  42. package/src/plugins/HastePlugin.js +12 -16
  43. package/src/plugins/HastePlugin.js.flow +12 -12
  44. package/src/plugins/MockPlugin.d.ts +48 -0
  45. package/src/plugins/MockPlugin.js +18 -25
  46. package/src/plugins/MockPlugin.js.flow +18 -22
  47. package/src/plugins/dependencies/dependencyExtractor.d.ts +1 -1
  48. package/src/plugins/haste/DuplicateHasteCandidatesError.d.ts +31 -0
  49. package/src/plugins/haste/DuplicateHasteCandidatesError.js +1 -5
  50. package/src/plugins/haste/HasteConflictsError.d.ts +23 -0
  51. package/src/plugins/haste/HasteConflictsError.js +1 -5
  52. package/src/plugins/haste/computeConflicts.d.ts +34 -0
  53. package/src/plugins/haste/computeConflicts.js +1 -5
  54. package/src/plugins/haste/getPlatformExtension.d.ts +21 -0
  55. package/src/plugins/mocks/getMockName.d.ts +20 -0
  56. package/src/plugins/mocks/getMockName.js +1 -4
  57. package/src/watchers/AbstractWatcher.d.ts +41 -0
  58. package/src/watchers/AbstractWatcher.js +2 -9
  59. package/src/watchers/FallbackWatcher.d.ts +28 -0
  60. package/src/watchers/FallbackWatcher.js +21 -12
  61. package/src/watchers/FallbackWatcher.js.flow +28 -5
  62. package/src/watchers/NativeWatcher.d.ts +55 -0
  63. package/src/watchers/NativeWatcher.js +28 -9
  64. package/src/watchers/NativeWatcher.js.flow +33 -6
  65. package/src/watchers/RecrawlWarning.d.ts +32 -0
  66. package/src/watchers/WatchmanWatcher.d.ts +34 -0
  67. package/src/watchers/WatchmanWatcher.js +2 -9
  68. package/src/watchers/common.d.ts +70 -0
  69. package/src/watchers/common.js +7 -6
  70. package/src/watchers/common.js.flow +1 -0
package/src/index.d.ts ADDED
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @noformat
8
+ * @oncall react_native
9
+ * @generated SignedSource<<806d228988308075b7a911c3dfb513d3>>
10
+ *
11
+ * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
12
+ * Original file: packages/metro-file-map/src/index.js
13
+ * To regenerate, run:
14
+ * js1 build metro-ts-defs (internal) OR
15
+ * yarn run build-ts-defs (OSS)
16
+ */
17
+
18
+ import type {
19
+ BuildParameters,
20
+ BuildResult,
21
+ CacheData,
22
+ CacheManagerFactory,
23
+ ChangeEventMetadata,
24
+ Console,
25
+ FileData,
26
+ FileSystem,
27
+ HasteMapData,
28
+ HasteMapItem,
29
+ HType,
30
+ InputFileMapPlugin,
31
+ PerfLoggerFactory,
32
+ } from './flow-types';
33
+
34
+ import EventEmitter from 'events';
35
+
36
+ export type {
37
+ BuildParameters,
38
+ BuildResult,
39
+ CacheData,
40
+ ChangeEventMetadata,
41
+ FileData,
42
+ FileMap,
43
+ FileSystem,
44
+ HasteMapData,
45
+ HasteMapItem,
46
+ InputFileMapPlugin,
47
+ };
48
+ export type InputOptions = Readonly<{
49
+ computeSha1?: null | undefined | boolean;
50
+ enableSymlinks?: null | undefined | boolean;
51
+ extensions: ReadonlyArray<string>;
52
+ forceNodeFilesystemAPI?: null | undefined | boolean;
53
+ ignorePattern?: null | undefined | RegExp;
54
+ plugins?: ReadonlyArray<InputFileMapPlugin>;
55
+ retainAllFiles: boolean;
56
+ rootDir: string;
57
+ roots: ReadonlyArray<string>;
58
+ cacheManagerFactory?: null | undefined | CacheManagerFactory;
59
+ console?: Console;
60
+ healthCheck: HealthCheckOptions;
61
+ maxFilesPerWorker?: null | undefined | number;
62
+ maxWorkers: number;
63
+ perfLoggerFactory?: null | undefined | PerfLoggerFactory;
64
+ resetCache?: null | undefined | boolean;
65
+ useWatchman?: null | undefined | boolean;
66
+ watch?: null | undefined | boolean;
67
+ watchmanDeferStates?: ReadonlyArray<string>;
68
+ }>;
69
+ type HealthCheckOptions = Readonly<{
70
+ enabled: boolean;
71
+ interval: number;
72
+ timeout: number;
73
+ filePrefix: string;
74
+ }>;
75
+ export {DiskCacheManager} from './cache/DiskCacheManager';
76
+ export {default as DependencyPlugin} from './plugins/DependencyPlugin';
77
+ export type {DependencyPluginOptions} from './plugins/DependencyPlugin';
78
+ export {DuplicateHasteCandidatesError} from './plugins/haste/DuplicateHasteCandidatesError';
79
+ export {HasteConflictsError} from './plugins/haste/HasteConflictsError';
80
+ export {default as HastePlugin} from './plugins/HastePlugin';
81
+ export type {HasteMap} from './flow-types';
82
+ export type {HealthCheckResult} from './Watcher';
83
+ export type {
84
+ CacheManager,
85
+ CacheManagerFactory,
86
+ CacheManagerFactoryOptions,
87
+ CacheManagerWriteOptions,
88
+ ChangeEvent,
89
+ DependencyExtractor,
90
+ WatcherStatus,
91
+ } from './flow-types';
92
+ /**
93
+ * FileMap includes a JavaScript implementation of Facebook's haste module system.
94
+ *
95
+ * This implementation is inspired by https://github.com/facebook/node-haste
96
+ * and was built with for high-performance in large code repositories with
97
+ * hundreds of thousands of files. This implementation is scalable and provides
98
+ * predictable performance.
99
+ *
100
+ * Because the file map creation and synchronization is critical to startup
101
+ * performance and most tasks are blocked by I/O this class makes heavy use of
102
+ * synchronous operations. It uses worker processes for parallelizing file
103
+ * access and metadata extraction.
104
+ *
105
+ * The data structures created by `metro-file-map` can be used directly from the
106
+ * cache without further processing. The metadata objects in the `files` and
107
+ * `map` objects contain cross-references: a metadata object from one can look
108
+ * up the corresponding metadata object in the other map. Note that in most
109
+ * projects, the number of files will be greater than the number of haste
110
+ * modules one module can refer to many files based on platform extensions.
111
+ *
112
+ * type CacheData = {
113
+ * clocks: WatchmanClocks,
114
+ * files: {[filepath: string]: FileMetadata},
115
+ * map: {[id: string]: HasteMapItem},
116
+ * mocks: {[id: string]: string},
117
+ * }
118
+ *
119
+ * // Watchman clocks are used for query synchronization and file system deltas.
120
+ * type WatchmanClocks = {[filepath: string]: string};
121
+ *
122
+ * type FileMetadata = {
123
+ * id: ?string, // used to look up module metadata objects in `map`.
124
+ * mtime: number, // check for outdated files.
125
+ * size: number, // size of the file in bytes.
126
+ * visited: boolean, // whether the file has been parsed or not.
127
+ * dependencies: Array<string>, // all relative dependencies of this file.
128
+ * sha1: ?string, // SHA-1 of the file, if requested via options.
129
+ * symlink: ?(1 | 0 | string), // Truthy if symlink, string is target
130
+ * };
131
+ *
132
+ * // Modules can be targeted to a specific platform based on the file name.
133
+ * // Example: platform.ios.js and Platform.android.js will both map to the same
134
+ * // `Platform` module. The platform should be specified during resolution.
135
+ * type HasteMapItem = {[platform: string]: ModuleMetadata};
136
+ *
137
+ * //
138
+ * type ModuleMetadata = {
139
+ * path: string, // the path to look up the file object in `files`.
140
+ * type: string, // the module type (either `package` or `module`).
141
+ * };
142
+ *
143
+ * Note that the data structures described above are conceptual only. The actual
144
+ * implementation uses arrays and constant keys for metadata storage. Instead of
145
+ * `{id: 'flatMap', mtime: 3421, size: 42, visited: true, dependencies: []}` the real
146
+ * representation is similar to `['flatMap', 3421, 42, 1, []]` to save storage space
147
+ * and reduce parse and write time of a big JSON blob.
148
+ *
149
+ * The FileMap is created as follows:
150
+ * 1. read data from the cache or create an empty structure.
151
+ *
152
+ * 2. crawl the file system.
153
+ * * empty cache: crawl the entire file system.
154
+ * * cache available:
155
+ * * if watchman is available: get file system delta changes.
156
+ * * if watchman is unavailable: crawl the entire file system.
157
+ * * build metadata objects for every file. This builds the `files` part of
158
+ * the `FileMap`.
159
+ *
160
+ * 3. visit and extract metadata from changed files, including sha1,
161
+ * depedendencies, and any plugins.
162
+ * * this is done in parallel over worker processes to improve performance.
163
+ * * the worst case is to visit all files.
164
+ * * the best case is no file system access and retrieving all data from
165
+ * the cache.
166
+ * * the average case is a small number of changed files.
167
+ *
168
+ * 4. serialize the new `FileMap` in a cache file.
169
+ *
170
+ */
171
+ declare class FileMap extends EventEmitter {
172
+ static create(options: InputOptions): FileMap;
173
+ constructor(options: InputOptions);
174
+ build(): Promise<BuildResult>;
175
+ /**
176
+ * 1. read data from the cache or create an empty structure.
177
+ */
178
+ read(): Promise<null | undefined | CacheData>;
179
+ end(): Promise<void>;
180
+ static H: HType;
181
+ }
182
+ export default FileMap;
package/src/index.js CHANGED
@@ -40,6 +40,7 @@ var _checkWatchmanCapabilities = _interopRequireDefault(
40
40
  require("./lib/checkWatchmanCapabilities"),
41
41
  );
42
42
  var _FileProcessor = require("./lib/FileProcessor");
43
+ var _FileSystemChangeAggregator = require("./lib/FileSystemChangeAggregator");
43
44
  var _normalizePathSeparatorsToPosix = _interopRequireDefault(
44
45
  require("./lib/normalizePathSeparatorsToPosix"),
45
46
  );
@@ -52,7 +53,6 @@ var _Watcher = require("./Watcher");
52
53
  var _events = _interopRequireDefault(require("events"));
53
54
  var _fs = require("fs");
54
55
  var _invariant = _interopRequireDefault(require("invariant"));
55
- var _nullthrows = _interopRequireDefault(require("nullthrows"));
56
56
  var path = _interopRequireWildcard(require("path"));
57
57
  var _perf_hooks = require("perf_hooks");
58
58
  var _DependencyPlugin = _interopRequireDefault(
@@ -69,10 +69,7 @@ function _interopRequireWildcard(e, t) {
69
69
  if (!t && e && e.__esModule) return e;
70
70
  var o,
71
71
  i,
72
- f = {
73
- __proto__: null,
74
- default: e,
75
- };
72
+ f = { __proto__: null, default: e };
76
73
  if (null === e || ("object" != typeof e && "function" != typeof e))
77
74
  return f;
78
75
  if ((o = t ? n : r)) {
@@ -92,11 +89,7 @@ function _interopRequireWildcard(e, t) {
92
89
  })(e, t);
93
90
  }
94
91
  function _interopRequireDefault(e) {
95
- return e && e.__esModule
96
- ? e
97
- : {
98
- default: e,
99
- };
92
+ return e && e.__esModule ? e : { default: e };
100
93
  }
101
94
  const debug = require("debug")("Metro:FileMap");
102
95
  const CACHE_BREAKER = "11";
@@ -277,7 +270,7 @@ class FileMap extends _events.default {
277
270
  };
278
271
  },
279
272
  fileIterator: (opts) =>
280
- mapIterator(
273
+ mapIterable(
281
274
  fileSystem.metadataIterator(opts),
282
275
  ({ baseName, canonicalPath, metadata }) => ({
283
276
  baseName,
@@ -291,21 +284,21 @@ class FileMap extends _events.default {
291
284
  ),
292
285
  ),
293
286
  ]);
294
- await this.#applyFileDelta(fileSystem, plugins, fileDelta);
287
+ const actualChanges = await this.#applyFileDelta(
288
+ fileSystem,
289
+ plugins,
290
+ fileDelta,
291
+ );
292
+ const changeSize = actualChanges.getSize();
295
293
  plugins.forEach(({ plugin }) => plugin.assertValid());
296
294
  const watchmanClocks = new Map(fileDelta.clocks ?? []);
297
295
  await this.#takeSnapshotAndPersist(
298
296
  fileSystem,
299
297
  watchmanClocks,
300
298
  plugins,
301
- fileDelta.changedFiles,
302
- fileDelta.removedFiles,
303
- );
304
- debug(
305
- "Finished mapping files (%d changes, %d removed).",
306
- fileDelta.changedFiles.size,
307
- fileDelta.removedFiles.size,
299
+ changeSize > 0,
308
300
  );
301
+ debug("Finished mapping files (%d changes).", changeSize);
309
302
  await this.#watch(fileSystem, watchmanClocks, plugins);
310
303
  return {
311
304
  fileSystem,
@@ -375,10 +368,9 @@ class FileMap extends _events.default {
375
368
  });
376
369
  const watcher = this.#watcher;
377
370
  watcher.on("status", (status) => this.emit("status", status));
378
- return watcher.crawl().then((result) => {
379
- this.#startupPerfLogger?.point("buildFileDelta_end");
380
- return result;
381
- });
371
+ const result = await watcher.crawl();
372
+ this.#startupPerfLogger?.point("buildFileDelta_end");
373
+ return result;
382
374
  }
383
375
  #maybeReadLink(normalPath, fileMetadata) {
384
376
  if (fileMetadata[_constants.default.SYMLINK] === 1) {
@@ -395,14 +387,11 @@ class FileMap extends _events.default {
395
387
  this.#startupPerfLogger?.point("applyFileDelta_start");
396
388
  const { changedFiles, removedFiles } = delta;
397
389
  this.#startupPerfLogger?.point("applyFileDelta_preprocess_start");
398
- const missingFiles = new Set();
399
390
  this.#startupPerfLogger?.point("applyFileDelta_remove_start");
400
- const removed = [];
391
+ const changeAggregator =
392
+ new _FileSystemChangeAggregator.FileSystemChangeAggregator();
401
393
  for (const relativeFilePath of removedFiles) {
402
- const metadata = fileSystem.remove(relativeFilePath);
403
- if (metadata) {
404
- removed.push([relativeFilePath, metadata]);
405
- }
394
+ fileSystem.remove(relativeFilePath, changeAggregator);
406
395
  }
407
396
  this.#startupPerfLogger?.point("applyFileDelta_remove_end");
408
397
  const readLinkPromises = [];
@@ -418,12 +407,12 @@ class FileMap extends _events.default {
418
407
  const maybeReadLink = this.#maybeReadLink(normalFilePath, fileData);
419
408
  if (maybeReadLink) {
420
409
  readLinkPromises.push(
421
- maybeReadLink.catch((error) =>
410
+ maybeReadLink.catch((error) => {
422
411
  readLinkErrors.push({
423
412
  normalFilePath,
424
413
  error,
425
- }),
426
- ),
414
+ });
415
+ }),
427
416
  );
428
417
  }
429
418
  }
@@ -448,39 +437,34 @@ class FileMap extends _events.default {
448
437
  readLinkErrors,
449
438
  )) {
450
439
  if (["ENOENT", "EACCESS"].includes(error.code)) {
451
- missingFiles.add(normalFilePath);
440
+ delta.changedFiles.delete(normalFilePath);
441
+ fileSystem.remove(normalFilePath, changeAggregator);
452
442
  } else {
453
443
  throw error;
454
444
  }
455
445
  }
456
- for (const relativeFilePath of missingFiles) {
457
- changedFiles.delete(relativeFilePath);
458
- const metadata = fileSystem.remove(relativeFilePath);
459
- if (metadata) {
460
- removed.push([relativeFilePath, metadata]);
461
- }
462
- }
463
446
  this.#startupPerfLogger?.point("applyFileDelta_missing_end");
464
447
  this.#startupPerfLogger?.point("applyFileDelta_add_start");
465
- fileSystem.bulkAddOrModify(changedFiles);
448
+ fileSystem.bulkAddOrModify(changedFiles, changeAggregator);
466
449
  this.#startupPerfLogger?.point("applyFileDelta_add_end");
467
450
  this.#startupPerfLogger?.point("applyFileDelta_updatePlugins_start");
468
- await Promise.all([
469
- plugins.map(({ plugin, dataIdx }) => {
470
- const mapFn =
471
- dataIdx != null
472
- ? ([relativePath, fileData]) => [relativePath, fileData[dataIdx]]
473
- : ([relativePath, fileData]) => [relativePath, null];
474
- return plugin.bulkUpdate({
475
- addedOrModified: mapIterator(changedFiles.entries(), mapFn),
476
- removed: mapIterator(removed.values(), mapFn),
477
- });
478
- }),
479
- ]);
451
+ this.#plugins.forEach(({ plugin, dataIdx }) => {
452
+ plugin.onChanged(
453
+ changeAggregator.getMappedView(
454
+ dataIdx != null ? (metadata) => metadata[dataIdx] : () => null,
455
+ ),
456
+ );
457
+ });
480
458
  this.#startupPerfLogger?.point("applyFileDelta_updatePlugins_end");
481
459
  this.#startupPerfLogger?.point("applyFileDelta_end");
460
+ return changeAggregator;
482
461
  }
483
- async #takeSnapshotAndPersist(fileSystem, clocks, plugins, changed, removed) {
462
+ async #takeSnapshotAndPersist(
463
+ fileSystem,
464
+ clocks,
465
+ plugins,
466
+ changedSinceCacheRead,
467
+ ) {
484
468
  this.#startupPerfLogger?.point("persist_start");
485
469
  await this.#cacheManager.write(
486
470
  () => ({
@@ -494,7 +478,7 @@ class FileMap extends _events.default {
494
478
  ),
495
479
  }),
496
480
  {
497
- changedSinceCacheRead: changed.size + removed.size > 0,
481
+ changedSinceCacheRead,
498
482
  eventSource: {
499
483
  onChange: (cb) => {
500
484
  this.on("change", cb);
@@ -520,14 +504,46 @@ class FileMap extends _events.default {
520
504
  }
521
505
  const hasWatchedExtension = (filePath) =>
522
506
  this.#options.extensions.some((ext) => filePath.endsWith(ext));
523
- let changeQueue = Promise.resolve();
524
507
  let nextEmit = null;
525
508
  const emitChange = () => {
526
- if (nextEmit == null || nextEmit.eventsQueue.length === 0) {
509
+ if (nextEmit == null) {
510
+ return;
511
+ }
512
+ const { events, firstEventTimestamp, firstEnqueuedTimestamp } = nextEmit;
513
+ const changeAggregator =
514
+ new _FileSystemChangeAggregator.FileSystemChangeAggregator();
515
+ for (const event of events) {
516
+ const { relativeFilePath, clock } = event;
517
+ if (event.type === "delete") {
518
+ fileSystem.remove(relativeFilePath, changeAggregator);
519
+ } else {
520
+ fileSystem.addOrModify(
521
+ relativeFilePath,
522
+ event.metadata,
523
+ changeAggregator,
524
+ );
525
+ }
526
+ this.#updateClock(clocks, clock);
527
+ }
528
+ const changeSize = changeAggregator.getSize();
529
+ if (changeSize === 0) {
530
+ nextEmit = null;
527
531
  return;
528
532
  }
529
- const { eventsQueue, firstEventTimestamp, firstEnqueuedTimestamp } =
530
- nextEmit;
533
+ const _netChange = changeAggregator.getView();
534
+ this.#plugins.forEach(({ plugin, dataIdx }) => {
535
+ plugin.onChanged(
536
+ changeAggregator.getMappedView(
537
+ dataIdx != null ? (metadata) => metadata[dataIdx] : () => null,
538
+ ),
539
+ );
540
+ });
541
+ const toPublicMetadata = (metadata) => ({
542
+ isSymlink: metadata[_constants.default.SYMLINK] !== 0,
543
+ modifiedTime: metadata[_constants.default.MTIME] ?? null,
544
+ });
545
+ const changesWithMetadata =
546
+ changeAggregator.getMappedView(toPublicMetadata);
531
547
  const hmrPerfLogger = this.#options.perfLoggerFactory?.("HMR", {
532
548
  key: this.#getNextChangeID(),
533
549
  });
@@ -541,20 +557,23 @@ class FileMap extends _events.default {
541
557
  hmrPerfLogger.point("waitingForChangeInterval_end");
542
558
  hmrPerfLogger.annotate({
543
559
  int: {
544
- eventsQueueLength: eventsQueue.length,
560
+ changeSize,
545
561
  },
546
562
  });
547
563
  hmrPerfLogger.point("fileChange_start");
548
564
  }
549
565
  const changeEvent = {
550
- eventsQueue,
566
+ changes: changesWithMetadata,
551
567
  logger: hmrPerfLogger,
568
+ rootDir: this.#options.rootDir,
552
569
  };
553
570
  this.emit("change", changeEvent);
554
571
  nextEmit = null;
555
572
  };
573
+ let changeQueue = Promise.resolve();
556
574
  const onChange = (change) => {
557
575
  if (
576
+ change.event !== "recrawl" &&
558
577
  change.metadata &&
559
578
  (change.metadata.type === "d" ||
560
579
  (change.metadata.type === "f" &&
@@ -572,62 +591,36 @@ class FileMap extends _events.default {
572
591
  }
573
592
  const relativeFilePath =
574
593
  this.#pathUtils.absoluteToNormal(absoluteFilePath);
575
- const linkStats = fileSystem.linkStats(relativeFilePath);
576
- if (
577
- change.event === "touch" &&
578
- linkStats != null &&
579
- change.metadata.modifiedTime != null &&
580
- linkStats.modifiedTime === change.metadata.modifiedTime
581
- ) {
582
- return;
583
- }
584
- const eventTypeToEmit =
585
- change.event === "touch"
586
- ? linkStats == null
587
- ? "add"
588
- : "change"
589
- : "delete";
590
594
  const onChangeStartTime =
591
595
  _perf_hooks.performance.timeOrigin + _perf_hooks.performance.now();
596
+ const enqueueEvent = (event) => {
597
+ nextEmit ??= {
598
+ events: [],
599
+ firstEnqueuedTimestamp:
600
+ _perf_hooks.performance.timeOrigin + _perf_hooks.performance.now(),
601
+ firstEventTimestamp: onChangeStartTime,
602
+ };
603
+ nextEmit.events.push(event);
604
+ };
592
605
  changeQueue = changeQueue
593
606
  .then(async () => {
594
607
  if (
595
608
  nextEmit != null &&
596
- nextEmit.eventsQueue.find(
609
+ nextEmit.events.find(
597
610
  (event) =>
598
- event.type === eventTypeToEmit &&
599
- event.filePath === absoluteFilePath &&
611
+ event.type === change.event &&
612
+ event.relativeFilePath === relativeFilePath &&
600
613
  ((!event.metadata && !change.metadata) ||
601
614
  (event.metadata &&
602
615
  change.metadata &&
603
- event.metadata.modifiedTime != null &&
616
+ event.metadata[_constants.default.MTIME] != null &&
604
617
  change.metadata.modifiedTime != null &&
605
- event.metadata.modifiedTime ===
618
+ event.metadata[_constants.default.MTIME] ===
606
619
  change.metadata.modifiedTime)),
607
620
  )
608
621
  ) {
609
622
  return null;
610
623
  }
611
- const linkStats = fileSystem.linkStats(relativeFilePath);
612
- const enqueueEvent = (metadata) => {
613
- const event = {
614
- filePath: absoluteFilePath,
615
- metadata,
616
- type: eventTypeToEmit,
617
- };
618
- if (nextEmit == null) {
619
- nextEmit = {
620
- eventsQueue: [event],
621
- firstEnqueuedTimestamp:
622
- _perf_hooks.performance.timeOrigin +
623
- _perf_hooks.performance.now(),
624
- firstEventTimestamp: onChangeStartTime,
625
- };
626
- } else {
627
- nextEmit.eventsQueue.push(event);
628
- }
629
- return null;
630
- };
631
624
  if (change.event === "touch") {
632
625
  (0, _invariant.default)(
633
626
  change.metadata.size != null,
@@ -654,40 +647,65 @@ class FileMap extends _events.default {
654
647
  },
655
648
  );
656
649
  }
657
- fileSystem.addOrModify(relativeFilePath, fileMetadata);
658
- this.#updateClock(clocks, change.clock);
659
- plugins.forEach(({ plugin, dataIdx }) =>
660
- dataIdx != null
661
- ? plugin.onNewOrModifiedFile(
662
- relativeFilePath,
663
- fileMetadata[dataIdx],
664
- )
665
- : plugin.onNewOrModifiedFile(relativeFilePath),
666
- );
667
- enqueueEvent(change.metadata);
650
+ enqueueEvent({
651
+ clock: change.clock,
652
+ relativeFilePath,
653
+ metadata: fileMetadata,
654
+ type: change.event,
655
+ });
668
656
  } catch (e) {
669
657
  if (!["ENOENT", "EACCESS"].includes(e.code)) {
670
658
  throw e;
671
659
  }
672
660
  }
673
661
  } else if (change.event === "delete") {
674
- if (linkStats == null) {
662
+ enqueueEvent({
663
+ clock: change.clock,
664
+ relativeFilePath,
665
+ type: "delete",
666
+ });
667
+ } else if (change.event === "recrawl") {
668
+ emitChange();
669
+ const absoluteDirPath = path.join(
670
+ change.root,
671
+ (0, _normalizePathSeparatorsToSystem.default)(
672
+ change.relativePath,
673
+ ),
674
+ );
675
+ const subpath = this.#pathUtils.absoluteToNormal(absoluteDirPath);
676
+ const watcher = this.#watcher;
677
+ (0, _invariant.default)(
678
+ watcher != null,
679
+ "Watcher must be initialized",
680
+ );
681
+ const crawlResult = await watcher.recrawl(subpath, fileSystem);
682
+ if (
683
+ crawlResult.changedFiles.size === 0 &&
684
+ crawlResult.removedFiles.size === 0
685
+ ) {
675
686
  return null;
676
687
  }
677
- const metadata = (0, _nullthrows.default)(
678
- fileSystem.remove(relativeFilePath),
688
+ const recrawlChangeAggregator = await this.#applyFileDelta(
689
+ fileSystem,
690
+ this.#plugins,
691
+ crawlResult,
679
692
  );
680
693
  this.#updateClock(clocks, change.clock);
681
- plugins.forEach(({ plugin, dataIdx }) =>
682
- dataIdx != null
683
- ? plugin.onRemovedFile(relativeFilePath, metadata[dataIdx])
684
- : plugin.onRemovedFile(relativeFilePath),
685
- );
686
- enqueueEvent({
687
- modifiedTime: null,
688
- size: null,
689
- type: linkStats.fileType,
694
+ if (recrawlChangeAggregator.getSize() === 0) {
695
+ return null;
696
+ }
697
+ const toPublicMetadata = (metadata) => ({
698
+ isSymlink: metadata[_constants.default.SYMLINK] !== 0,
699
+ modifiedTime: metadata[_constants.default.MTIME] ?? null,
690
700
  });
701
+ const changesWithMetadata =
702
+ recrawlChangeAggregator.getMappedView(toPublicMetadata);
703
+ const changeEvent = {
704
+ changes: changesWithMetadata,
705
+ logger: null,
706
+ rootDir: this.#options.rootDir,
707
+ };
708
+ this.emit("change", changeEvent);
691
709
  } else {
692
710
  throw new Error(
693
711
  `metro-file-map: Unrecognized event type from watcher: ${change.event}`,
@@ -787,11 +805,9 @@ class FileMap extends _events.default {
787
805
  static H = _constants.default;
788
806
  }
789
807
  exports.default = FileMap;
790
- const mapIterator = (it, fn) =>
791
- "map" in it
792
- ? it.map(fn)
793
- : (function* mapped() {
794
- for (const item of it) {
795
- yield fn(item);
796
- }
797
- })();
808
+ const mapIterable = (it, fn) =>
809
+ (function* mapped() {
810
+ for (const item of it) {
811
+ yield fn(item);
812
+ }
813
+ })();