metro-file-map 0.83.3 → 0.84.0
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.
- package/package.json +4 -3
- package/src/Watcher.js +59 -52
- package/src/Watcher.js.flow +39 -39
- package/src/cache/DiskCacheManager.js.flow +3 -3
- package/src/constants.js +9 -8
- package/src/constants.js.flow +6 -18
- package/src/crawlers/node/index.js +27 -25
- package/src/crawlers/node/index.js.flow +6 -8
- package/src/crawlers/watchman/index.js +26 -22
- package/src/crawlers/watchman/index.js.flow +3 -4
- package/src/crawlers/watchman/planQuery.d.ts +24 -0
- package/src/crawlers/watchman/planQuery.js.flow +4 -4
- package/src/flow-types.js.flow +125 -87
- package/src/index.js +267 -235
- package/src/index.js.flow +269 -275
- package/src/lib/FileProcessor.js +76 -54
- package/src/lib/FileProcessor.js.flow +93 -72
- package/src/lib/RootPathUtils.js +25 -21
- package/src/lib/RootPathUtils.js.flow +4 -4
- package/src/lib/TreeFS.js +72 -77
- package/src/lib/TreeFS.js.flow +104 -124
- package/src/lib/checkWatchmanCapabilities.js.flow +4 -4
- package/src/lib/dependencyExtractor.d.ts +14 -0
- package/src/lib/normalizePathSeparatorsToPosix.js +25 -21
- package/src/lib/normalizePathSeparatorsToPosix.js.flow +3 -3
- package/src/lib/normalizePathSeparatorsToSystem.js +25 -21
- package/src/lib/normalizePathSeparatorsToSystem.js.flow +3 -3
- package/src/lib/rootRelativeCacheKeys.js +0 -20
- package/src/lib/rootRelativeCacheKeys.js.flow +1 -23
- package/src/plugins/DependencyPlugin.js +75 -0
- package/src/plugins/DependencyPlugin.js.flow +144 -0
- package/src/plugins/HastePlugin.js +83 -38
- package/src/plugins/HastePlugin.js.flow +105 -51
- package/src/plugins/MockPlugin.js +7 -4
- package/src/plugins/MockPlugin.js.flow +24 -15
- package/src/plugins/dependencies/dependencyExtractor.d.ts +14 -0
- package/src/{lib → plugins/dependencies}/dependencyExtractor.js.flow +3 -6
- package/src/plugins/dependencies/worker.d.ts +24 -0
- package/src/plugins/dependencies/worker.js +24 -0
- package/src/plugins/dependencies/worker.js.flow +53 -0
- package/src/plugins/haste/HasteConflictsError.js.flow +2 -2
- package/src/plugins/haste/computeConflicts.js +2 -1
- package/src/plugins/haste/computeConflicts.js.flow +11 -12
- package/src/plugins/haste/getPlatformExtension.js.flow +2 -2
- package/src/plugins/haste/worker.d.ts +24 -0
- package/src/plugins/haste/worker.js +35 -0
- package/src/plugins/haste/worker.js.flow +64 -0
- package/src/plugins/mocks/getMockName.js +27 -23
- package/src/plugins/mocks/getMockName.js.flow +2 -4
- package/src/watchers/AbstractWatcher.js +27 -22
- package/src/watchers/AbstractWatcher.js.flow +6 -5
- package/src/watchers/FallbackWatcher.js +88 -84
- package/src/watchers/FallbackWatcher.js.flow +65 -65
- package/src/watchers/NativeWatcher.js +25 -21
- package/src/watchers/NativeWatcher.js.flow +3 -3
- package/src/watchers/RecrawlWarning.js.flow +1 -1
- package/src/watchers/WatchmanWatcher.js +61 -53
- package/src/watchers/WatchmanWatcher.js.flow +39 -38
- package/src/watchers/common.js.flow +5 -5
- package/src/worker.d.ts +36 -0
- package/src/worker.js +16 -58
- package/src/worker.js.flow +19 -69
- package/src/workerExclusionList.d.ts +12 -0
- package/src/workerExclusionList.js.flow +1 -1
- package/src/Watcher.d.ts +0 -24
- package/src/cache/DiskCacheManager.d.ts +0 -38
- package/src/flow-types.d.ts +0 -353
- package/src/index.d.ts +0 -97
- package/src/lib/DuplicateHasteCandidatesError.d.ts +0 -24
- /package/src/{lib → plugins/dependencies}/dependencyExtractor.js +0 -0
package/src/index.js.flow
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
EventsQueue,
|
|
26
26
|
FileData,
|
|
27
27
|
FileMapPlugin,
|
|
28
|
+
FileMapPluginWorker,
|
|
28
29
|
FileMetadata,
|
|
29
30
|
FileSystem,
|
|
30
31
|
HasteMapData,
|
|
@@ -47,8 +48,6 @@ import normalizePathSeparatorsToPosix from './lib/normalizePathSeparatorsToPosix
|
|
|
47
48
|
import normalizePathSeparatorsToSystem from './lib/normalizePathSeparatorsToSystem';
|
|
48
49
|
import {RootPathUtils} from './lib/RootPathUtils';
|
|
49
50
|
import TreeFS from './lib/TreeFS';
|
|
50
|
-
import HastePlugin from './plugins/HastePlugin';
|
|
51
|
-
import MockPlugin from './plugins/MockPlugin';
|
|
52
51
|
import {Watcher} from './Watcher';
|
|
53
52
|
import EventEmitter from 'events';
|
|
54
53
|
import {promises as fsPromises} from 'fs';
|
|
@@ -72,26 +71,16 @@ export type {
|
|
|
72
71
|
HasteMapItem,
|
|
73
72
|
};
|
|
74
73
|
|
|
75
|
-
export type InputOptions =
|
|
76
|
-
computeDependencies?: ?boolean,
|
|
74
|
+
export type InputOptions = Readonly<{
|
|
77
75
|
computeSha1?: ?boolean,
|
|
78
|
-
enableHastePackages?: boolean,
|
|
79
76
|
enableSymlinks?: ?boolean,
|
|
80
|
-
|
|
81
|
-
extensions: $ReadOnlyArray<string>,
|
|
77
|
+
extensions: ReadonlyArray<string>,
|
|
82
78
|
forceNodeFilesystemAPI?: ?boolean,
|
|
83
79
|
ignorePattern?: ?RegExp,
|
|
84
|
-
|
|
85
|
-
platforms: $ReadOnlyArray<string>,
|
|
86
|
-
plugins?: $ReadOnlyArray<FileMapPlugin<>>,
|
|
80
|
+
plugins?: ReadonlyArray<AnyFileMapPlugin>,
|
|
87
81
|
retainAllFiles: boolean,
|
|
88
82
|
rootDir: string,
|
|
89
|
-
roots:
|
|
90
|
-
skipPackageJson?: ?boolean,
|
|
91
|
-
|
|
92
|
-
// Module paths that should export a 'getCacheKey' method
|
|
93
|
-
dependencyExtractor?: ?string,
|
|
94
|
-
hasteImplModulePath?: ?string,
|
|
83
|
+
roots: ReadonlyArray<string>,
|
|
95
84
|
|
|
96
85
|
cacheManagerFactory?: ?CacheManagerFactory,
|
|
97
86
|
console?: Console,
|
|
@@ -100,30 +89,38 @@ export type InputOptions = $ReadOnly<{
|
|
|
100
89
|
maxWorkers: number,
|
|
101
90
|
perfLoggerFactory?: ?PerfLoggerFactory,
|
|
102
91
|
resetCache?: ?boolean,
|
|
103
|
-
throwOnModuleCollision?: ?boolean,
|
|
104
92
|
useWatchman?: ?boolean,
|
|
105
93
|
watch?: ?boolean,
|
|
106
|
-
watchmanDeferStates?:
|
|
94
|
+
watchmanDeferStates?: ReadonlyArray<string>,
|
|
107
95
|
}>;
|
|
108
96
|
|
|
109
|
-
type HealthCheckOptions =
|
|
97
|
+
type HealthCheckOptions = Readonly<{
|
|
110
98
|
enabled: boolean,
|
|
111
99
|
interval: number,
|
|
112
100
|
timeout: number,
|
|
113
101
|
filePrefix: string,
|
|
114
102
|
}>;
|
|
115
103
|
|
|
116
|
-
type InternalOptions =
|
|
104
|
+
type InternalOptions = Readonly<{
|
|
117
105
|
...BuildParameters,
|
|
118
106
|
healthCheck: HealthCheckOptions,
|
|
119
107
|
perfLoggerFactory: ?PerfLoggerFactory,
|
|
120
108
|
resetCache: ?boolean,
|
|
121
109
|
useWatchman: boolean,
|
|
122
110
|
watch: boolean,
|
|
123
|
-
watchmanDeferStates:
|
|
111
|
+
watchmanDeferStates: ReadonlyArray<string>,
|
|
112
|
+
}>;
|
|
113
|
+
|
|
114
|
+
// $FlowFixMe[unclear-type] Plugin types cannot be known statically
|
|
115
|
+
type AnyFileMapPlugin = FileMapPlugin<any, any>;
|
|
116
|
+
type IndexedPlugin = Readonly<{
|
|
117
|
+
plugin: AnyFileMapPlugin,
|
|
118
|
+
dataIdx: ?number,
|
|
124
119
|
}>;
|
|
125
120
|
|
|
126
121
|
export {DiskCacheManager} from './cache/DiskCacheManager';
|
|
122
|
+
export {default as DependencyPlugin} from './plugins/DependencyPlugin';
|
|
123
|
+
export type {DependencyPluginOptions} from './plugins/DependencyPlugin';
|
|
127
124
|
export {DuplicateHasteCandidatesError} from './plugins/haste/DuplicateHasteCandidatesError';
|
|
128
125
|
export {HasteConflictsError} from './plugins/haste/HasteConflictsError';
|
|
129
126
|
export {default as HastePlugin} from './plugins/HastePlugin';
|
|
@@ -143,12 +140,11 @@ export type {
|
|
|
143
140
|
// This should be bumped whenever a code change to `metro-file-map` itself
|
|
144
141
|
// would cause a change to the cache data structure and/or content (for a given
|
|
145
142
|
// filesystem state and build parameters).
|
|
146
|
-
const CACHE_BREAKER = '
|
|
143
|
+
const CACHE_BREAKER = '11';
|
|
147
144
|
|
|
148
145
|
const CHANGE_INTERVAL = 30;
|
|
149
146
|
|
|
150
147
|
const NODE_MODULES = path.sep + 'node_modules' + path.sep;
|
|
151
|
-
const PACKAGE_JSON = path.sep + 'package.json';
|
|
152
148
|
const VCS_DIRECTORIES = /[/\\]\.(git|hg)[/\\]/.source;
|
|
153
149
|
const WATCHMAN_REQUIRED_CAPABILITIES = [
|
|
154
150
|
'field-content.sha1hex',
|
|
@@ -165,7 +161,7 @@ const WATCHMAN_REQUIRED_CAPABILITIES = [
|
|
|
165
161
|
* hundreds of thousands of files. This implementation is scalable and provides
|
|
166
162
|
* predictable performance.
|
|
167
163
|
*
|
|
168
|
-
* Because the
|
|
164
|
+
* Because the file map creation and synchronization is critical to startup
|
|
169
165
|
* performance and most tasks are blocked by I/O this class makes heavy use of
|
|
170
166
|
* synchronous operations. It uses worker processes for parallelizing file
|
|
171
167
|
* access and metadata extraction.
|
|
@@ -214,7 +210,7 @@ const WATCHMAN_REQUIRED_CAPABILITIES = [
|
|
|
214
210
|
* representation is similar to `['flatMap', 3421, 42, 1, []]` to save storage space
|
|
215
211
|
* and reduce parse and write time of a big JSON blob.
|
|
216
212
|
*
|
|
217
|
-
* The
|
|
213
|
+
* The FileMap is created as follows:
|
|
218
214
|
* 1. read data from the cache or create an empty structure.
|
|
219
215
|
*
|
|
220
216
|
* 2. crawl the file system.
|
|
@@ -223,36 +219,34 @@ const WATCHMAN_REQUIRED_CAPABILITIES = [
|
|
|
223
219
|
* * if watchman is available: get file system delta changes.
|
|
224
220
|
* * if watchman is unavailable: crawl the entire file system.
|
|
225
221
|
* * build metadata objects for every file. This builds the `files` part of
|
|
226
|
-
* the `
|
|
222
|
+
* the `FileMap`.
|
|
227
223
|
*
|
|
228
|
-
* 3.
|
|
224
|
+
* 3. visit and extract metadata from changed files, including sha1,
|
|
225
|
+
* depedendencies, and any plugins.
|
|
229
226
|
* * this is done in parallel over worker processes to improve performance.
|
|
230
|
-
* * the worst case is to
|
|
227
|
+
* * the worst case is to visit all files.
|
|
231
228
|
* * the best case is no file system access and retrieving all data from
|
|
232
229
|
* the cache.
|
|
233
230
|
* * the average case is a small number of changed files.
|
|
234
231
|
*
|
|
235
|
-
* 4. serialize the new `
|
|
232
|
+
* 4. serialize the new `FileMap` in a cache file.
|
|
236
233
|
*
|
|
237
234
|
*/
|
|
238
235
|
export default class FileMap extends EventEmitter {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
#hastePlugin: HastePlugin;
|
|
254
|
-
#mockPlugin: ?MockPlugin = null;
|
|
255
|
-
#plugins: $ReadOnlyArray<FileMapPlugin<>>;
|
|
236
|
+
#buildPromise: ?Promise<BuildResult>;
|
|
237
|
+
+#cacheManager: CacheManager;
|
|
238
|
+
#canUseWatchmanPromise: Promise<boolean>;
|
|
239
|
+
#changeID: number;
|
|
240
|
+
#changeInterval: ?IntervalID;
|
|
241
|
+
+#console: Console;
|
|
242
|
+
+#crawlerAbortController: AbortController;
|
|
243
|
+
+#fileProcessor: FileProcessor;
|
|
244
|
+
#healthCheckInterval: ?IntervalID;
|
|
245
|
+
+#options: InternalOptions;
|
|
246
|
+
+#pathUtils: RootPathUtils;
|
|
247
|
+
+#plugins: ReadonlyArray<IndexedPlugin>;
|
|
248
|
+
+#startupPerfLogger: ?PerfLogger;
|
|
249
|
+
#watcher: ?Watcher;
|
|
256
250
|
|
|
257
251
|
static create(options: InputOptions): FileMap {
|
|
258
252
|
return new FileMap(options);
|
|
@@ -262,9 +256,9 @@ export default class FileMap extends EventEmitter {
|
|
|
262
256
|
super();
|
|
263
257
|
|
|
264
258
|
if (options.perfLoggerFactory) {
|
|
265
|
-
this
|
|
259
|
+
this.#startupPerfLogger =
|
|
266
260
|
options.perfLoggerFactory?.('START_UP').subSpan('fileMap') ?? null;
|
|
267
|
-
this
|
|
261
|
+
this.#startupPerfLogger?.point('constructor_start');
|
|
268
262
|
}
|
|
269
263
|
|
|
270
264
|
// Add VCS_DIRECTORIES to provided ignorePattern
|
|
@@ -285,56 +279,39 @@ export default class FileMap extends EventEmitter {
|
|
|
285
279
|
ignorePattern = new RegExp(VCS_DIRECTORIES);
|
|
286
280
|
}
|
|
287
281
|
|
|
288
|
-
this
|
|
289
|
-
const throwOnModuleCollision = Boolean(options.throwOnModuleCollision);
|
|
290
|
-
|
|
291
|
-
const enableHastePackages = options.enableHastePackages ?? true;
|
|
292
|
-
|
|
293
|
-
this.#hastePlugin = new HastePlugin({
|
|
294
|
-
console: this._console,
|
|
295
|
-
enableHastePackages,
|
|
296
|
-
perfLogger: this._startupPerfLogger,
|
|
297
|
-
platforms: new Set(options.platforms),
|
|
298
|
-
rootDir: options.rootDir,
|
|
299
|
-
failValidationOnConflicts: throwOnModuleCollision,
|
|
300
|
-
});
|
|
282
|
+
this.#console = options.console || global.console;
|
|
301
283
|
|
|
302
|
-
|
|
284
|
+
let dataSlot: number = H.PLUGINDATA;
|
|
303
285
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
286
|
+
const indexedPlugins: Array<IndexedPlugin> = [];
|
|
287
|
+
const pluginWorkers: Array<FileMapPluginWorker> = [];
|
|
288
|
+
const plugins = options.plugins ?? [];
|
|
289
|
+
for (const plugin of plugins) {
|
|
290
|
+
const maybeWorker = plugin.getWorker();
|
|
291
|
+
indexedPlugins.push({
|
|
292
|
+
plugin,
|
|
293
|
+
dataIdx: maybeWorker != null ? dataSlot++ : null,
|
|
310
294
|
});
|
|
311
|
-
|
|
295
|
+
if (maybeWorker != null) {
|
|
296
|
+
pluginWorkers.push(maybeWorker);
|
|
297
|
+
}
|
|
312
298
|
}
|
|
313
|
-
|
|
314
|
-
this.#plugins = plugins;
|
|
299
|
+
this.#plugins = indexedPlugins;
|
|
315
300
|
|
|
316
301
|
const buildParameters: BuildParameters = {
|
|
317
|
-
|
|
318
|
-
options.computeDependencies == null
|
|
319
|
-
? true
|
|
320
|
-
: options.computeDependencies,
|
|
302
|
+
cacheBreaker: CACHE_BREAKER,
|
|
321
303
|
computeSha1: options.computeSha1 || false,
|
|
322
|
-
dependencyExtractor: options.dependencyExtractor ?? null,
|
|
323
|
-
enableHastePackages,
|
|
324
304
|
enableSymlinks: options.enableSymlinks || false,
|
|
325
305
|
extensions: options.extensions,
|
|
326
306
|
forceNodeFilesystemAPI: !!options.forceNodeFilesystemAPI,
|
|
327
|
-
hasteImplModulePath: options.hasteImplModulePath,
|
|
328
307
|
ignorePattern,
|
|
329
|
-
plugins
|
|
308
|
+
plugins,
|
|
330
309
|
retainAllFiles: options.retainAllFiles,
|
|
331
310
|
rootDir: options.rootDir,
|
|
332
311
|
roots: Array.from(new Set(options.roots)),
|
|
333
|
-
skipPackageJson: !!options.skipPackageJson,
|
|
334
|
-
cacheBreaker: CACHE_BREAKER,
|
|
335
312
|
};
|
|
336
313
|
|
|
337
|
-
this
|
|
314
|
+
this.#options = {
|
|
338
315
|
...buildParameters,
|
|
339
316
|
healthCheck: options.healthCheck,
|
|
340
317
|
perfLoggerFactory: options.perfLoggerFactory,
|
|
@@ -347,33 +324,31 @@ export default class FileMap extends EventEmitter {
|
|
|
347
324
|
const cacheFactoryOptions: CacheManagerFactoryOptions = {
|
|
348
325
|
buildParameters,
|
|
349
326
|
};
|
|
350
|
-
this
|
|
327
|
+
this.#cacheManager = options.cacheManagerFactory
|
|
351
328
|
? options.cacheManagerFactory.call(null, cacheFactoryOptions)
|
|
352
329
|
: new DiskCacheManager(cacheFactoryOptions, {});
|
|
353
330
|
|
|
354
|
-
this
|
|
355
|
-
dependencyExtractor: buildParameters.dependencyExtractor,
|
|
356
|
-
enableHastePackages: buildParameters.enableHastePackages,
|
|
357
|
-
enableWorkerThreads: options.enableWorkerThreads ?? false,
|
|
358
|
-
hasteImplModulePath: buildParameters.hasteImplModulePath,
|
|
331
|
+
this.#fileProcessor = new FileProcessor({
|
|
359
332
|
maxFilesPerWorker: options.maxFilesPerWorker,
|
|
360
333
|
maxWorkers: options.maxWorkers,
|
|
361
|
-
perfLogger: this
|
|
334
|
+
perfLogger: this.#startupPerfLogger,
|
|
335
|
+
pluginWorkers,
|
|
336
|
+
rootDir: options.rootDir,
|
|
362
337
|
});
|
|
363
338
|
|
|
364
|
-
this
|
|
365
|
-
this
|
|
366
|
-
this
|
|
367
|
-
this
|
|
368
|
-
this
|
|
339
|
+
this.#buildPromise = null;
|
|
340
|
+
this.#pathUtils = new RootPathUtils(options.rootDir);
|
|
341
|
+
this.#startupPerfLogger?.point('constructor_end');
|
|
342
|
+
this.#crawlerAbortController = new AbortController();
|
|
343
|
+
this.#changeID = 0;
|
|
369
344
|
}
|
|
370
345
|
|
|
371
346
|
build(): Promise<BuildResult> {
|
|
372
|
-
this
|
|
373
|
-
if (!this
|
|
374
|
-
this
|
|
347
|
+
this.#startupPerfLogger?.point('build_start');
|
|
348
|
+
if (!this.#buildPromise) {
|
|
349
|
+
this.#buildPromise = (async () => {
|
|
375
350
|
let initialData: ?CacheData;
|
|
376
|
-
if (this.
|
|
351
|
+
if (this.#options.resetCache !== true) {
|
|
377
352
|
initialData = await this.read();
|
|
378
353
|
}
|
|
379
354
|
if (!initialData) {
|
|
@@ -382,23 +357,22 @@ export default class FileMap extends EventEmitter {
|
|
|
382
357
|
debug('Cache loaded (%d clock(s))', initialData.clocks.size);
|
|
383
358
|
}
|
|
384
359
|
|
|
385
|
-
const rootDir = this.
|
|
386
|
-
this
|
|
360
|
+
const rootDir = this.#options.rootDir;
|
|
361
|
+
this.#startupPerfLogger?.point('constructFileSystem_start');
|
|
387
362
|
const processFile: ProcessFileFunction = (
|
|
388
|
-
|
|
363
|
+
normalPath,
|
|
389
364
|
metadata,
|
|
390
365
|
opts,
|
|
391
366
|
) => {
|
|
392
|
-
const result = this.
|
|
393
|
-
|
|
367
|
+
const result = this.#fileProcessor.processRegularFile(
|
|
368
|
+
normalPath,
|
|
394
369
|
metadata,
|
|
395
370
|
{
|
|
396
371
|
computeSha1: opts.computeSha1,
|
|
397
|
-
computeDependencies: false,
|
|
398
372
|
maybeReturnContent: true,
|
|
399
373
|
},
|
|
400
374
|
);
|
|
401
|
-
debug('Lazily processed file: %s',
|
|
375
|
+
debug('Lazily processed file: %s', normalPath);
|
|
402
376
|
// Emit an event to inform caches that there is new data to save.
|
|
403
377
|
this.emit('metadata');
|
|
404
378
|
return result?.content;
|
|
@@ -406,16 +380,16 @@ export default class FileMap extends EventEmitter {
|
|
|
406
380
|
const fileSystem =
|
|
407
381
|
initialData != null
|
|
408
382
|
? TreeFS.fromDeserializedSnapshot({
|
|
409
|
-
rootDir,
|
|
410
383
|
// Typed `mixed` because we've read this from an external
|
|
411
384
|
// source. It'd be too expensive to validate at runtime, so
|
|
412
385
|
// trust our cache manager that this is correct.
|
|
413
386
|
// $FlowFixMe[incompatible-type]
|
|
414
387
|
fileSystemData: initialData.fileSystemData,
|
|
415
388
|
processFile,
|
|
389
|
+
rootDir,
|
|
416
390
|
})
|
|
417
|
-
: new TreeFS({
|
|
418
|
-
this
|
|
391
|
+
: new TreeFS({processFile, rootDir});
|
|
392
|
+
this.#startupPerfLogger?.point('constructFileSystem_end');
|
|
419
393
|
|
|
420
394
|
const plugins = this.#plugins;
|
|
421
395
|
|
|
@@ -423,28 +397,53 @@ export default class FileMap extends EventEmitter {
|
|
|
423
397
|
// crawling to build a diff of current state vs cached. `fileSystem`
|
|
424
398
|
// is not mutated during either operation.
|
|
425
399
|
const [fileDelta] = await Promise.all([
|
|
426
|
-
this
|
|
427
|
-
fileSystem,
|
|
400
|
+
this.#buildFileDelta({
|
|
428
401
|
clocks: initialData?.clocks ?? new Map(),
|
|
402
|
+
fileSystem,
|
|
429
403
|
}),
|
|
430
404
|
Promise.all(
|
|
431
|
-
plugins.map(plugin =>
|
|
405
|
+
plugins.map(({plugin, dataIdx}) =>
|
|
432
406
|
plugin.initialize({
|
|
433
|
-
files:
|
|
407
|
+
files: {
|
|
408
|
+
lookup: mixedPath => {
|
|
409
|
+
const result = fileSystem.lookup(mixedPath);
|
|
410
|
+
if (!result.exists) {
|
|
411
|
+
return {exists: false};
|
|
412
|
+
}
|
|
413
|
+
if (result.type === 'd') {
|
|
414
|
+
return {exists: true, type: 'd'};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
exists: true,
|
|
418
|
+
type: 'f',
|
|
419
|
+
pluginData:
|
|
420
|
+
dataIdx != null ? result.metadata[dataIdx] : null,
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
fileIterator: opts =>
|
|
424
|
+
mapIterator(
|
|
425
|
+
fileSystem.metadataIterator(opts),
|
|
426
|
+
({baseName, canonicalPath, metadata}) => ({
|
|
427
|
+
baseName,
|
|
428
|
+
canonicalPath,
|
|
429
|
+
pluginData: dataIdx != null ? metadata[dataIdx] : null,
|
|
430
|
+
}),
|
|
431
|
+
),
|
|
432
|
+
},
|
|
434
433
|
pluginState: initialData?.plugins.get(plugin.name),
|
|
435
434
|
}),
|
|
436
435
|
),
|
|
437
436
|
),
|
|
438
437
|
]);
|
|
439
438
|
|
|
440
|
-
// Update `fileSystem
|
|
441
|
-
await this
|
|
439
|
+
// Update `fileSystem` and plugins based on the file delta.
|
|
440
|
+
await this.#applyFileDelta(fileSystem, plugins, fileDelta);
|
|
442
441
|
|
|
443
|
-
// Validate
|
|
444
|
-
plugins.forEach(plugin => plugin.assertValid());
|
|
442
|
+
// Validate plugins before persisting them.
|
|
443
|
+
plugins.forEach(({plugin}) => plugin.assertValid());
|
|
445
444
|
|
|
446
445
|
const watchmanClocks = new Map(fileDelta.clocks ?? []);
|
|
447
|
-
await this
|
|
446
|
+
await this.#takeSnapshotAndPersist(
|
|
448
447
|
fileSystem,
|
|
449
448
|
watchmanClocks,
|
|
450
449
|
plugins,
|
|
@@ -457,16 +456,12 @@ export default class FileMap extends EventEmitter {
|
|
|
457
456
|
fileDelta.removedFiles.size,
|
|
458
457
|
);
|
|
459
458
|
|
|
460
|
-
await this
|
|
461
|
-
return {
|
|
462
|
-
fileSystem,
|
|
463
|
-
hasteMap: this.#hastePlugin,
|
|
464
|
-
mockMap: this.#mockPlugin,
|
|
465
|
-
};
|
|
459
|
+
await this.#watch(fileSystem, watchmanClocks, plugins);
|
|
460
|
+
return {fileSystem};
|
|
466
461
|
})();
|
|
467
462
|
}
|
|
468
|
-
return this.
|
|
469
|
-
this
|
|
463
|
+
return this.#buildPromise.then(result => {
|
|
464
|
+
this.#startupPerfLogger?.point('build_end');
|
|
470
465
|
return result;
|
|
471
466
|
});
|
|
472
467
|
}
|
|
@@ -476,33 +471,33 @@ export default class FileMap extends EventEmitter {
|
|
|
476
471
|
*/
|
|
477
472
|
async read(): Promise<?CacheData> {
|
|
478
473
|
let data: ?CacheData;
|
|
479
|
-
this
|
|
474
|
+
this.#startupPerfLogger?.point('read_start');
|
|
480
475
|
try {
|
|
481
|
-
data = await this.
|
|
476
|
+
data = await this.#cacheManager.read();
|
|
482
477
|
} catch (e) {
|
|
483
|
-
this.
|
|
478
|
+
this.#console.warn(
|
|
484
479
|
'Error while reading cache, falling back to a full crawl:\n',
|
|
485
480
|
e,
|
|
486
481
|
);
|
|
487
|
-
this
|
|
482
|
+
this.#startupPerfLogger?.annotate({
|
|
488
483
|
string: {cacheReadError: e.toString()},
|
|
489
484
|
});
|
|
490
485
|
}
|
|
491
|
-
this
|
|
486
|
+
this.#startupPerfLogger?.point('read_end');
|
|
492
487
|
return data;
|
|
493
488
|
}
|
|
494
489
|
|
|
495
490
|
/**
|
|
496
491
|
* 2. crawl the file system.
|
|
497
492
|
*/
|
|
498
|
-
async
|
|
493
|
+
async #buildFileDelta(
|
|
499
494
|
previousState: CrawlerOptions['previousState'],
|
|
500
495
|
): Promise<{
|
|
501
496
|
removedFiles: Set<CanonicalPath>,
|
|
502
497
|
changedFiles: FileData,
|
|
503
498
|
clocks?: WatchmanClocks,
|
|
504
499
|
}> {
|
|
505
|
-
this
|
|
500
|
+
this.#startupPerfLogger?.point('buildFileDelta_start');
|
|
506
501
|
|
|
507
502
|
const {
|
|
508
503
|
computeSha1,
|
|
@@ -515,16 +510,16 @@ export default class FileMap extends EventEmitter {
|
|
|
515
510
|
rootDir,
|
|
516
511
|
watch,
|
|
517
512
|
watchmanDeferStates,
|
|
518
|
-
} = this
|
|
513
|
+
} = this.#options;
|
|
519
514
|
|
|
520
|
-
this
|
|
521
|
-
abortSignal: this.
|
|
515
|
+
this.#watcher = new Watcher({
|
|
516
|
+
abortSignal: this.#crawlerAbortController.signal,
|
|
522
517
|
computeSha1,
|
|
523
|
-
console: this
|
|
518
|
+
console: this.#console,
|
|
524
519
|
enableSymlinks,
|
|
525
520
|
extensions,
|
|
526
521
|
forceNodeFilesystemAPI,
|
|
527
|
-
healthCheckFilePrefix: this.
|
|
522
|
+
healthCheckFilePrefix: this.#options.healthCheck.filePrefix,
|
|
528
523
|
// TODO: Refactor out the two different ignore strategies here.
|
|
529
524
|
ignoreForCrawl: filePath => {
|
|
530
525
|
const ignoreMatched = ignorePattern.test(filePath);
|
|
@@ -533,53 +528,55 @@ export default class FileMap extends EventEmitter {
|
|
|
533
528
|
);
|
|
534
529
|
},
|
|
535
530
|
ignorePatternForWatch: ignorePattern,
|
|
536
|
-
perfLogger: this
|
|
531
|
+
perfLogger: this.#startupPerfLogger,
|
|
537
532
|
previousState,
|
|
538
|
-
roots,
|
|
539
533
|
rootDir,
|
|
540
|
-
|
|
534
|
+
roots,
|
|
535
|
+
useWatchman: await this.#shouldUseWatchman(),
|
|
541
536
|
watch,
|
|
542
537
|
watchmanDeferStates,
|
|
543
538
|
});
|
|
544
|
-
const watcher = this
|
|
539
|
+
const watcher = this.#watcher;
|
|
545
540
|
|
|
546
541
|
watcher.on('status', status => this.emit('status', status));
|
|
547
542
|
|
|
548
543
|
return watcher.crawl().then(result => {
|
|
549
|
-
this
|
|
544
|
+
this.#startupPerfLogger?.point('buildFileDelta_end');
|
|
550
545
|
return result;
|
|
551
546
|
});
|
|
552
547
|
}
|
|
553
548
|
|
|
554
|
-
|
|
549
|
+
#maybeReadLink(normalPath: Path, fileMetadata: FileMetadata): ?Promise<void> {
|
|
555
550
|
// If we only need to read a link, it's more efficient to do it in-band
|
|
556
551
|
// (with async file IO) than to have the overhead of worker IO.
|
|
557
552
|
if (fileMetadata[H.SYMLINK] === 1) {
|
|
558
|
-
return fsPromises
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
553
|
+
return fsPromises
|
|
554
|
+
.readlink(this.#pathUtils.normalToAbsolute(normalPath))
|
|
555
|
+
.then(symlinkTarget => {
|
|
556
|
+
fileMetadata[H.VISITED] = 1;
|
|
557
|
+
fileMetadata[H.SYMLINK] = symlinkTarget;
|
|
558
|
+
});
|
|
562
559
|
}
|
|
563
560
|
return null;
|
|
564
561
|
}
|
|
565
562
|
|
|
566
|
-
async
|
|
563
|
+
async #applyFileDelta(
|
|
567
564
|
fileSystem: MutableFileSystem,
|
|
568
|
-
plugins:
|
|
569
|
-
delta:
|
|
565
|
+
plugins: ReadonlyArray<IndexedPlugin>,
|
|
566
|
+
delta: Readonly<{
|
|
570
567
|
changedFiles: FileData,
|
|
571
|
-
removedFiles:
|
|
568
|
+
removedFiles: ReadonlySet<CanonicalPath>,
|
|
572
569
|
clocks?: WatchmanClocks,
|
|
573
570
|
}>,
|
|
574
571
|
): Promise<void> {
|
|
575
|
-
this
|
|
572
|
+
this.#startupPerfLogger?.point('applyFileDelta_start');
|
|
576
573
|
const {changedFiles, removedFiles} = delta;
|
|
577
|
-
this
|
|
574
|
+
this.#startupPerfLogger?.point('applyFileDelta_preprocess_start');
|
|
578
575
|
const missingFiles: Set<string> = new Set();
|
|
579
576
|
|
|
580
|
-
// Remove files first so that we don't mistake moved
|
|
577
|
+
// Remove files first so that we don't mistake moved modules
|
|
581
578
|
// modules as duplicates.
|
|
582
|
-
this
|
|
579
|
+
this.#startupPerfLogger?.point('applyFileDelta_remove_start');
|
|
583
580
|
const removed: Array<[string, FileMetadata]> = [];
|
|
584
581
|
for (const relativeFilePath of removedFiles) {
|
|
585
582
|
const metadata = fileSystem.remove(relativeFilePath);
|
|
@@ -587,77 +584,52 @@ export default class FileMap extends EventEmitter {
|
|
|
587
584
|
removed.push([relativeFilePath, metadata]);
|
|
588
585
|
}
|
|
589
586
|
}
|
|
590
|
-
this
|
|
587
|
+
this.#startupPerfLogger?.point('applyFileDelta_remove_end');
|
|
591
588
|
|
|
592
589
|
const readLinkPromises = [];
|
|
593
590
|
const readLinkErrors: Array<{
|
|
594
|
-
|
|
591
|
+
normalFilePath: string,
|
|
595
592
|
error: Error & {code?: string},
|
|
596
593
|
}> = [];
|
|
597
594
|
const filesToProcess: Array<[string, FileMetadata]> = [];
|
|
598
595
|
|
|
599
|
-
for (const [
|
|
596
|
+
for (const [normalFilePath, fileData] of changedFiles) {
|
|
600
597
|
// A crawler may preserve the H.VISITED flag to indicate that the file
|
|
601
598
|
// contents are unchaged and it doesn't need visiting again.
|
|
602
599
|
if (fileData[H.VISITED] === 1) {
|
|
603
600
|
continue;
|
|
604
601
|
}
|
|
605
602
|
|
|
606
|
-
if (
|
|
607
|
-
this._options.skipPackageJson &&
|
|
608
|
-
relativeFilePath.endsWith(PACKAGE_JSON)
|
|
609
|
-
) {
|
|
610
|
-
continue;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if (
|
|
614
|
-
fileData[H.SYMLINK] === 0 &&
|
|
615
|
-
!this._options.computeDependencies &&
|
|
616
|
-
!this._options.computeSha1 &&
|
|
617
|
-
this._options.hasteImplModulePath == null &&
|
|
618
|
-
!(
|
|
619
|
-
this._options.enableHastePackages &&
|
|
620
|
-
relativeFilePath.endsWith(PACKAGE_JSON)
|
|
621
|
-
)
|
|
622
|
-
) {
|
|
623
|
-
// Nothing to process
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// SHA-1, if requested, should already be present thanks to the crawler.
|
|
628
|
-
const absolutePath = this._pathUtils.normalToAbsolute(relativeFilePath);
|
|
629
|
-
|
|
630
603
|
if (fileData[H.SYMLINK] === 0) {
|
|
631
|
-
filesToProcess.push([
|
|
604
|
+
filesToProcess.push([normalFilePath, fileData]);
|
|
632
605
|
} else {
|
|
633
|
-
const maybeReadLink = this
|
|
606
|
+
const maybeReadLink = this.#maybeReadLink(normalFilePath, fileData);
|
|
634
607
|
if (maybeReadLink) {
|
|
635
608
|
readLinkPromises.push(
|
|
636
609
|
maybeReadLink.catch(error =>
|
|
637
|
-
readLinkErrors.push({
|
|
610
|
+
readLinkErrors.push({normalFilePath, error}),
|
|
638
611
|
),
|
|
639
612
|
);
|
|
640
613
|
}
|
|
641
614
|
}
|
|
642
615
|
}
|
|
643
|
-
this
|
|
616
|
+
this.#startupPerfLogger?.point('applyFileDelta_preprocess_end');
|
|
644
617
|
|
|
645
618
|
debug(
|
|
646
|
-
'
|
|
619
|
+
'Found %d added/modified files and %d symlinks.',
|
|
647
620
|
filesToProcess.length,
|
|
648
621
|
readLinkPromises.length,
|
|
649
622
|
);
|
|
650
623
|
|
|
651
|
-
this
|
|
624
|
+
this.#startupPerfLogger?.point('applyFileDelta_process_start');
|
|
652
625
|
const [batchResult] = await Promise.all([
|
|
653
|
-
this.
|
|
654
|
-
computeSha1: this.
|
|
655
|
-
computeDependencies: this._options.computeDependencies,
|
|
626
|
+
this.#fileProcessor.processBatch(filesToProcess, {
|
|
627
|
+
computeSha1: this.#options.computeSha1,
|
|
656
628
|
maybeReturnContent: false,
|
|
657
629
|
}),
|
|
658
630
|
Promise.all(readLinkPromises),
|
|
659
631
|
]);
|
|
660
|
-
this
|
|
632
|
+
this.#startupPerfLogger?.point('applyFileDelta_process_end');
|
|
661
633
|
|
|
662
634
|
// It's possible that a file could be deleted between being seen by the
|
|
663
635
|
// crawler and our attempt to process it. For our purposes, this is
|
|
@@ -668,14 +640,14 @@ export default class FileMap extends EventEmitter {
|
|
|
668
640
|
// Treat the file accordingly - don't add it to `FileSystem`, and remove
|
|
669
641
|
// it if it already exists. We're not emitting events at this point in
|
|
670
642
|
// startup, so there's nothing more to do.
|
|
671
|
-
this
|
|
672
|
-
for (const {
|
|
643
|
+
this.#startupPerfLogger?.point('applyFileDelta_missing_start');
|
|
644
|
+
for (const {normalFilePath, error} of batchResult.errors.concat(
|
|
673
645
|
readLinkErrors,
|
|
674
646
|
)) {
|
|
675
647
|
/* $FlowFixMe[incompatible-type] Error exposed after improved typing of
|
|
676
648
|
* Array.{includes,indexOf,lastIndexOf} */
|
|
677
649
|
if (['ENOENT', 'EACCESS'].includes(error.code)) {
|
|
678
|
-
missingFiles.add(
|
|
650
|
+
missingFiles.add(normalFilePath);
|
|
679
651
|
} else {
|
|
680
652
|
// Anything else is fatal.
|
|
681
653
|
throw error;
|
|
@@ -688,42 +660,49 @@ export default class FileMap extends EventEmitter {
|
|
|
688
660
|
removed.push([relativeFilePath, metadata]);
|
|
689
661
|
}
|
|
690
662
|
}
|
|
691
|
-
this
|
|
663
|
+
this.#startupPerfLogger?.point('applyFileDelta_missing_end');
|
|
692
664
|
|
|
693
|
-
this
|
|
665
|
+
this.#startupPerfLogger?.point('applyFileDelta_add_start');
|
|
694
666
|
fileSystem.bulkAddOrModify(changedFiles);
|
|
695
|
-
this
|
|
667
|
+
this.#startupPerfLogger?.point('applyFileDelta_add_end');
|
|
668
|
+
|
|
669
|
+
this.#startupPerfLogger?.point('applyFileDelta_updatePlugins_start');
|
|
696
670
|
|
|
697
|
-
this._startupPerfLogger?.point('applyFileDelta_updatePlugins_start');
|
|
698
671
|
await Promise.all([
|
|
699
|
-
plugins.map(plugin =>
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
672
|
+
plugins.map(({plugin, dataIdx}) => {
|
|
673
|
+
const mapFn: (
|
|
674
|
+
[CanonicalPath, FileMetadata],
|
|
675
|
+
) => [CanonicalPath, unknown] =
|
|
676
|
+
dataIdx != null
|
|
677
|
+
? ([relativePath, fileData]) => [relativePath, fileData[dataIdx]]
|
|
678
|
+
: ([relativePath, fileData]) => [relativePath, null];
|
|
679
|
+
return plugin.bulkUpdate({
|
|
680
|
+
addedOrModified: mapIterator(changedFiles.entries(), mapFn),
|
|
681
|
+
removed: mapIterator(removed.values(), mapFn),
|
|
682
|
+
});
|
|
683
|
+
}),
|
|
705
684
|
]);
|
|
706
|
-
this
|
|
707
|
-
this
|
|
685
|
+
this.#startupPerfLogger?.point('applyFileDelta_updatePlugins_end');
|
|
686
|
+
this.#startupPerfLogger?.point('applyFileDelta_end');
|
|
708
687
|
}
|
|
709
688
|
|
|
710
689
|
/**
|
|
711
690
|
* 4. Serialize a snapshot of our raw data via the configured cache manager
|
|
712
691
|
*/
|
|
713
|
-
async
|
|
692
|
+
async #takeSnapshotAndPersist(
|
|
714
693
|
fileSystem: FileSystem,
|
|
715
694
|
clocks: WatchmanClocks,
|
|
716
|
-
plugins:
|
|
695
|
+
plugins: ReadonlyArray<IndexedPlugin>,
|
|
717
696
|
changed: FileData,
|
|
718
697
|
removed: Set<CanonicalPath>,
|
|
719
698
|
) {
|
|
720
|
-
this
|
|
721
|
-
await this.
|
|
699
|
+
this.#startupPerfLogger?.point('persist_start');
|
|
700
|
+
await this.#cacheManager.write(
|
|
722
701
|
() => ({
|
|
723
|
-
fileSystemData: fileSystem.getSerializableSnapshot(),
|
|
724
702
|
clocks: new Map(clocks),
|
|
703
|
+
fileSystemData: fileSystem.getSerializableSnapshot(),
|
|
725
704
|
plugins: new Map(
|
|
726
|
-
plugins.map(plugin => [
|
|
705
|
+
plugins.map(({plugin}) => [
|
|
727
706
|
plugin.name,
|
|
728
707
|
plugin.getSerializableSnapshot(),
|
|
729
708
|
]),
|
|
@@ -745,29 +724,29 @@ export default class FileMap extends EventEmitter {
|
|
|
745
724
|
},
|
|
746
725
|
},
|
|
747
726
|
onWriteError: error => {
|
|
748
|
-
this.
|
|
727
|
+
this.#console.warn('[metro-file-map] Cache write error\n:', error);
|
|
749
728
|
},
|
|
750
729
|
},
|
|
751
730
|
);
|
|
752
|
-
this
|
|
731
|
+
this.#startupPerfLogger?.point('persist_end');
|
|
753
732
|
}
|
|
754
733
|
|
|
755
734
|
/**
|
|
756
735
|
* Watch mode
|
|
757
736
|
*/
|
|
758
|
-
async
|
|
737
|
+
async #watch(
|
|
759
738
|
fileSystem: MutableFileSystem,
|
|
760
739
|
clocks: WatchmanClocks,
|
|
761
|
-
plugins:
|
|
740
|
+
plugins: ReadonlyArray<IndexedPlugin>,
|
|
762
741
|
): Promise<void> {
|
|
763
|
-
this
|
|
764
|
-
if (!this.
|
|
765
|
-
this
|
|
742
|
+
this.#startupPerfLogger?.point('watch_start');
|
|
743
|
+
if (!this.#options.watch) {
|
|
744
|
+
this.#startupPerfLogger?.point('watch_end');
|
|
766
745
|
return;
|
|
767
746
|
}
|
|
768
747
|
|
|
769
748
|
const hasWatchedExtension = (filePath: string) =>
|
|
770
|
-
this.
|
|
749
|
+
this.#options.extensions.some(ext => filePath.endsWith(ext));
|
|
771
750
|
|
|
772
751
|
let changeQueue: Promise<null | void> = Promise.resolve();
|
|
773
752
|
let nextEmit: ?{
|
|
@@ -783,8 +762,8 @@ export default class FileMap extends EventEmitter {
|
|
|
783
762
|
}
|
|
784
763
|
const {eventsQueue, firstEventTimestamp, firstEnqueuedTimestamp} =
|
|
785
764
|
nextEmit;
|
|
786
|
-
const hmrPerfLogger = this.
|
|
787
|
-
key: this
|
|
765
|
+
const hmrPerfLogger = this.#options.perfLoggerFactory?.('HMR', {
|
|
766
|
+
key: this.#getNextChangeID(),
|
|
788
767
|
});
|
|
789
768
|
if (hmrPerfLogger != null) {
|
|
790
769
|
hmrPerfLogger.start({timestamp: firstEventTimestamp});
|
|
@@ -798,8 +777,8 @@ export default class FileMap extends EventEmitter {
|
|
|
798
777
|
hmrPerfLogger.point('fileChange_start');
|
|
799
778
|
}
|
|
800
779
|
const changeEvent: ChangeEvent = {
|
|
801
|
-
logger: hmrPerfLogger,
|
|
802
780
|
eventsQueue,
|
|
781
|
+
logger: hmrPerfLogger,
|
|
803
782
|
};
|
|
804
783
|
this.emit('change', changeEvent);
|
|
805
784
|
nextEmit = null;
|
|
@@ -814,7 +793,7 @@ export default class FileMap extends EventEmitter {
|
|
|
814
793
|
(change.metadata.type === 'f' &&
|
|
815
794
|
!hasWatchedExtension(change.relativePath)) ||
|
|
816
795
|
// Don't emit events relating to symlinks if enableSymlinks: false
|
|
817
|
-
(!this.
|
|
796
|
+
(!this.#options.enableSymlinks && change.metadata?.type === 'l'))
|
|
818
797
|
) {
|
|
819
798
|
return;
|
|
820
799
|
}
|
|
@@ -826,12 +805,12 @@ export default class FileMap extends EventEmitter {
|
|
|
826
805
|
|
|
827
806
|
// Ignore files (including symlinks) whose path matches ignorePattern
|
|
828
807
|
// (we don't ignore node_modules in watch mode)
|
|
829
|
-
if (this.
|
|
808
|
+
if (this.#options.ignorePattern.test(absoluteFilePath)) {
|
|
830
809
|
return;
|
|
831
810
|
}
|
|
832
811
|
|
|
833
812
|
const relativeFilePath =
|
|
834
|
-
this.
|
|
813
|
+
this.#pathUtils.absoluteToNormal(absoluteFilePath);
|
|
835
814
|
const linkStats = fileSystem.linkStats(relativeFilePath);
|
|
836
815
|
|
|
837
816
|
// The file has been accessed, not modified. If the modified time is
|
|
@@ -889,9 +868,9 @@ export default class FileMap extends EventEmitter {
|
|
|
889
868
|
if (nextEmit == null) {
|
|
890
869
|
nextEmit = {
|
|
891
870
|
eventsQueue: [event],
|
|
892
|
-
firstEventTimestamp: onChangeStartTime,
|
|
893
871
|
firstEnqueuedTimestamp:
|
|
894
872
|
performance.timeOrigin + performance.now(),
|
|
873
|
+
firstEventTimestamp: onChangeStartTime,
|
|
895
874
|
};
|
|
896
875
|
} else {
|
|
897
876
|
nextEmit.eventsQueue.push(event);
|
|
@@ -900,7 +879,7 @@ export default class FileMap extends EventEmitter {
|
|
|
900
879
|
};
|
|
901
880
|
|
|
902
881
|
// If the file was added or modified,
|
|
903
|
-
// parse it and update the
|
|
882
|
+
// parse it and update the file map.
|
|
904
883
|
if (change.event === 'touch') {
|
|
905
884
|
invariant(
|
|
906
885
|
change.metadata.size != null,
|
|
@@ -910,30 +889,33 @@ export default class FileMap extends EventEmitter {
|
|
|
910
889
|
change.metadata.modifiedTime,
|
|
911
890
|
change.metadata.size,
|
|
912
891
|
0,
|
|
913
|
-
'',
|
|
914
892
|
null,
|
|
915
893
|
change.metadata.type === 'l' ? 1 : 0,
|
|
916
|
-
|
|
894
|
+
null,
|
|
917
895
|
];
|
|
918
896
|
|
|
919
897
|
try {
|
|
920
898
|
if (change.metadata.type === 'l') {
|
|
921
|
-
await this
|
|
899
|
+
await this.#maybeReadLink(relativeFilePath, fileMetadata);
|
|
922
900
|
} else {
|
|
923
|
-
await this.
|
|
924
|
-
|
|
901
|
+
await this.#fileProcessor.processRegularFile(
|
|
902
|
+
relativeFilePath,
|
|
925
903
|
fileMetadata,
|
|
926
904
|
{
|
|
927
|
-
computeSha1: this.
|
|
928
|
-
computeDependencies: this._options.computeDependencies,
|
|
905
|
+
computeSha1: this.#options.computeSha1,
|
|
929
906
|
maybeReturnContent: false,
|
|
930
907
|
},
|
|
931
908
|
);
|
|
932
909
|
}
|
|
933
910
|
fileSystem.addOrModify(relativeFilePath, fileMetadata);
|
|
934
|
-
this
|
|
935
|
-
plugins.forEach(plugin =>
|
|
936
|
-
|
|
911
|
+
this.#updateClock(clocks, change.clock);
|
|
912
|
+
plugins.forEach(({plugin, dataIdx}) =>
|
|
913
|
+
dataIdx != null
|
|
914
|
+
? plugin.onNewOrModifiedFile(
|
|
915
|
+
relativeFilePath,
|
|
916
|
+
fileMetadata[dataIdx],
|
|
917
|
+
)
|
|
918
|
+
: plugin.onNewOrModifiedFile(relativeFilePath),
|
|
937
919
|
);
|
|
938
920
|
enqueueEvent(change.metadata);
|
|
939
921
|
} catch (e) {
|
|
@@ -956,9 +938,11 @@ export default class FileMap extends EventEmitter {
|
|
|
956
938
|
// We've already checked linkStats != null above, so the file
|
|
957
939
|
// exists in the file map and remove should always return metadata.
|
|
958
940
|
const metadata = nullthrows(fileSystem.remove(relativeFilePath));
|
|
959
|
-
this
|
|
960
|
-
plugins.forEach(plugin =>
|
|
961
|
-
|
|
941
|
+
this.#updateClock(clocks, change.clock);
|
|
942
|
+
plugins.forEach(({plugin, dataIdx}) =>
|
|
943
|
+
dataIdx != null
|
|
944
|
+
? plugin.onRemovedFile(relativeFilePath, metadata[dataIdx])
|
|
945
|
+
: plugin.onRemovedFile(relativeFilePath),
|
|
962
946
|
);
|
|
963
947
|
|
|
964
948
|
enqueueEvent({
|
|
@@ -974,68 +958,68 @@ export default class FileMap extends EventEmitter {
|
|
|
974
958
|
return null;
|
|
975
959
|
})
|
|
976
960
|
.catch((error: Error) => {
|
|
977
|
-
this.
|
|
961
|
+
this.#console.error(
|
|
978
962
|
`metro-file-map: watch error:\n ${error.stack}\n`,
|
|
979
963
|
);
|
|
980
964
|
});
|
|
981
965
|
};
|
|
982
966
|
|
|
983
|
-
this
|
|
967
|
+
this.#changeInterval = setInterval(emitChange, CHANGE_INTERVAL);
|
|
984
968
|
|
|
985
969
|
invariant(
|
|
986
|
-
this
|
|
987
|
-
'Expected
|
|
970
|
+
this.#watcher != null,
|
|
971
|
+
'Expected #watcher to have been initialised by build()',
|
|
988
972
|
);
|
|
989
|
-
await this.
|
|
973
|
+
await this.#watcher.watch(onChange);
|
|
990
974
|
|
|
991
|
-
if (this.
|
|
975
|
+
if (this.#options.healthCheck.enabled) {
|
|
992
976
|
const performHealthCheck = () => {
|
|
993
|
-
if (!this
|
|
977
|
+
if (!this.#watcher) {
|
|
994
978
|
return;
|
|
995
979
|
}
|
|
996
980
|
// $FlowFixMe[unused-promise]
|
|
997
|
-
this
|
|
998
|
-
.checkHealth(this.
|
|
981
|
+
this.#watcher
|
|
982
|
+
.checkHealth(this.#options.healthCheck.timeout)
|
|
999
983
|
.then(result => {
|
|
1000
984
|
this.emit('healthCheck', result);
|
|
1001
985
|
});
|
|
1002
986
|
};
|
|
1003
987
|
performHealthCheck();
|
|
1004
|
-
this
|
|
988
|
+
this.#healthCheckInterval = setInterval(
|
|
1005
989
|
performHealthCheck,
|
|
1006
|
-
this.
|
|
990
|
+
this.#options.healthCheck.interval,
|
|
1007
991
|
);
|
|
1008
992
|
}
|
|
1009
|
-
this
|
|
993
|
+
this.#startupPerfLogger?.point('watch_end');
|
|
1010
994
|
}
|
|
1011
995
|
|
|
1012
996
|
async end(): Promise<void> {
|
|
1013
|
-
if (this
|
|
1014
|
-
clearInterval(this
|
|
997
|
+
if (this.#changeInterval) {
|
|
998
|
+
clearInterval(this.#changeInterval);
|
|
1015
999
|
}
|
|
1016
|
-
if (this
|
|
1017
|
-
clearInterval(this
|
|
1000
|
+
if (this.#healthCheckInterval) {
|
|
1001
|
+
clearInterval(this.#healthCheckInterval);
|
|
1018
1002
|
}
|
|
1019
1003
|
|
|
1020
|
-
this.
|
|
1004
|
+
this.#crawlerAbortController.abort();
|
|
1021
1005
|
|
|
1022
1006
|
await Promise.all([
|
|
1023
|
-
this.
|
|
1024
|
-
this
|
|
1025
|
-
this.
|
|
1007
|
+
this.#fileProcessor.end(),
|
|
1008
|
+
this.#watcher?.close(),
|
|
1009
|
+
this.#cacheManager.end(),
|
|
1026
1010
|
]);
|
|
1027
1011
|
}
|
|
1028
1012
|
|
|
1029
|
-
async
|
|
1030
|
-
if (!this.
|
|
1013
|
+
async #shouldUseWatchman(): Promise<boolean> {
|
|
1014
|
+
if (!this.#options.useWatchman) {
|
|
1031
1015
|
return false;
|
|
1032
1016
|
}
|
|
1033
|
-
if (!this
|
|
1034
|
-
this
|
|
1017
|
+
if (!this.#canUseWatchmanPromise) {
|
|
1018
|
+
this.#canUseWatchmanPromise = checkWatchmanCapabilities(
|
|
1035
1019
|
WATCHMAN_REQUIRED_CAPABILITIES,
|
|
1036
1020
|
)
|
|
1037
1021
|
.then(({version}) => {
|
|
1038
|
-
this
|
|
1022
|
+
this.#startupPerfLogger?.annotate({
|
|
1039
1023
|
string: {
|
|
1040
1024
|
watchmanVersion: version,
|
|
1041
1025
|
},
|
|
@@ -1045,7 +1029,7 @@ export default class FileMap extends EventEmitter {
|
|
|
1045
1029
|
.catch(e => {
|
|
1046
1030
|
// TODO: Advise people to either install Watchman or set
|
|
1047
1031
|
// `useWatchman: false` here?
|
|
1048
|
-
this
|
|
1032
|
+
this.#startupPerfLogger?.annotate({
|
|
1049
1033
|
string: {
|
|
1050
1034
|
watchmanFailedCapabilityCheck: e?.message ?? '[missing]',
|
|
1051
1035
|
},
|
|
@@ -1053,24 +1037,34 @@ export default class FileMap extends EventEmitter {
|
|
|
1053
1037
|
return false;
|
|
1054
1038
|
});
|
|
1055
1039
|
}
|
|
1056
|
-
return this
|
|
1040
|
+
return this.#canUseWatchmanPromise;
|
|
1057
1041
|
}
|
|
1058
1042
|
|
|
1059
|
-
|
|
1060
|
-
if (this
|
|
1061
|
-
this
|
|
1043
|
+
#getNextChangeID(): number {
|
|
1044
|
+
if (this.#changeID >= Number.MAX_SAFE_INTEGER) {
|
|
1045
|
+
this.#changeID = 0;
|
|
1062
1046
|
}
|
|
1063
|
-
return ++this
|
|
1047
|
+
return ++this.#changeID;
|
|
1064
1048
|
}
|
|
1065
1049
|
|
|
1066
|
-
|
|
1050
|
+
#updateClock(clocks: WatchmanClocks, newClock?: ?ChangeEventClock): void {
|
|
1067
1051
|
if (newClock == null) {
|
|
1068
1052
|
return;
|
|
1069
1053
|
}
|
|
1070
1054
|
const [absoluteWatchRoot, clockSpec] = newClock;
|
|
1071
|
-
const relativeFsRoot = this.
|
|
1055
|
+
const relativeFsRoot = this.#pathUtils.absoluteToNormal(absoluteWatchRoot);
|
|
1072
1056
|
clocks.set(normalizePathSeparatorsToPosix(relativeFsRoot), clockSpec);
|
|
1073
1057
|
}
|
|
1074
1058
|
|
|
1075
1059
|
static H: HType = H;
|
|
1076
1060
|
}
|
|
1061
|
+
|
|
1062
|
+
// TODO: Replace with it.map() from Node 22+
|
|
1063
|
+
const mapIterator: <T, S>(Iterator<T>, (T) => S) => Iterable<S> = (it, fn) =>
|
|
1064
|
+
'map' in it
|
|
1065
|
+
? it.map(fn)
|
|
1066
|
+
: (function* mapped() {
|
|
1067
|
+
for (const item of it) {
|
|
1068
|
+
yield fn(item);
|
|
1069
|
+
}
|
|
1070
|
+
})();
|