diff --git a/packages/metro-config/src/__tests__/__snapshots__/loadConfig-test.js.snap b/packages/metro-config/src/__tests__/__snapshots__/loadConfig-test.js.snap index 5468e71d7c..a51dcc6626 100644 --- a/packages/metro-config/src/__tests__/__snapshots__/loadConfig-test.js.snap +++ b/packages/metro-config/src/__tests__/__snapshots__/loadConfig-test.js.snap @@ -177,6 +177,7 @@ Object { "debounceMs": 5000, "enabled": true, }, + "unstable_lazySha1": false, "unstable_workerThreads": false, "watchman": Object { "deferStates": Array [ @@ -364,6 +365,7 @@ Object { "debounceMs": 5000, "enabled": true, }, + "unstable_lazySha1": false, "unstable_workerThreads": false, "watchman": Object { "deferStates": Array [ @@ -551,6 +553,7 @@ Object { "debounceMs": 5000, "enabled": true, }, + "unstable_lazySha1": false, "unstable_workerThreads": false, "watchman": Object { "deferStates": Array [ @@ -738,6 +741,7 @@ Object { "debounceMs": 5000, "enabled": true, }, + "unstable_lazySha1": false, "unstable_workerThreads": false, "watchman": Object { "deferStates": Array [ diff --git a/packages/metro-config/src/configTypes.flow.js b/packages/metro-config/src/configTypes.flow.js index 3d4fca03cf..13991ba932 100644 --- a/packages/metro-config/src/configTypes.flow.js +++ b/packages/metro-config/src/configTypes.flow.js @@ -205,6 +205,7 @@ type WatcherConfigT = { enabled: boolean, debounceMs?: number, }>, + unstable_lazySha1: boolean, unstable_workerThreads: boolean, watchman: $ReadOnly<{ deferStates: $ReadOnlyArray, diff --git a/packages/metro-config/src/defaults/index.js b/packages/metro-config/src/defaults/index.js index 0a24c21f90..aa3ec7334a 100644 --- a/packages/metro-config/src/defaults/index.js +++ b/packages/metro-config/src/defaults/index.js @@ -147,6 +147,7 @@ const getDefaultValues = (projectRoot: ?string): ConfigT => ({ interval: 30000, timeout: 5000, }, + unstable_lazySha1: false, unstable_workerThreads: false, unstable_autoSaveCache: { enabled: true, diff --git a/packages/metro/src/Bundler.js b/packages/metro/src/Bundler.js index e9acb0861b..27dd6b5120 100644 --- a/packages/metro/src/Bundler.js +++ b/packages/metro/src/Bundler.js @@ -36,8 +36,16 @@ class Bundler { .ready() .then(() => { config.reporter.update({type: 'transformer_load_started'}); - this._transformer = new Transformer(config, (...args) => - this._depGraph.getSha1(...args), + this._transformer = new Transformer( + config, + config.watcher.unstable_lazySha1 + ? // This object-form API is expected to replace passing a function + // once lazy SHA1 is stable. This will be a breaking change. + { + unstable_getOrComputeSha1: filePath => + this._depGraph.unstable_getOrComputeSha1(filePath), + } + : (...args) => this._depGraph.getSha1(...args), ); config.reporter.update({type: 'transformer_load_done'}); }) diff --git a/packages/metro/src/DeltaBundler/Transformer.js b/packages/metro/src/DeltaBundler/Transformer.js index de41e9030e..28dd9b1361 100644 --- a/packages/metro/src/DeltaBundler/Transformer.js +++ b/packages/metro/src/DeltaBundler/Transformer.js @@ -24,19 +24,30 @@ const fs = require('fs'); const {Cache, stableHash} = require('metro-cache'); const path = require('path'); +type LazySha1Fn = string => Promise<{content?: Buffer, sha1: string}>; +type EagerSha1Fn = string => string; + class Transformer { _config: ConfigT; _cache: Cache>; _baseHash: string; - _getSha1: string => string; + _getSha1: EagerSha1Fn | LazySha1Fn; _workerFarm: WorkerFarm; - constructor(config: ConfigT, getSha1Fn: string => string) { + constructor( + config: ConfigT, + getSha1FnOrOpts: + | $ReadOnly<{unstable_getOrComputeSha1: LazySha1Fn}> + | EagerSha1Fn, + ) { this._config = config; this._config.watchFolders.forEach(verifyRootExists); this._cache = new Cache(config.cacheStores); - this._getSha1 = getSha1Fn; + this._getSha1 = + typeof getSha1FnOrOpts === 'function' + ? getSha1FnOrOpts + : getSha1FnOrOpts.unstable_getOrComputeSha1; // Remove the transformer config params that we don't want to pass to the // transformer. We should change the config object and move them away so we @@ -129,11 +140,21 @@ class Transformer { ]); let sha1: string; + let content: ?Buffer; if (fileBuffer) { // Shortcut for virtual modules which provide the contents with the filename. sha1 = crypto.createHash('sha1').update(fileBuffer).digest('hex'); + content = fileBuffer; } else { - sha1 = this._getSha1(filePath); + const result = await this._getSha1(filePath); + if (typeof result === 'string') { + sha1 = result; + } else { + sha1 = result.sha1; + if (result.content) { + content = result.content; + } + } } let fullKey = Buffer.concat([partialKey, Buffer.from(sha1, 'hex')]); @@ -158,7 +179,7 @@ class Transformer { : await this._workerFarm.transform( localPath, transformerOptions, - fileBuffer, + content, ); // Only re-compute the full key if the SHA-1 changed. This is because diff --git a/packages/metro/src/node-haste/DependencyGraph.js b/packages/metro/src/node-haste/DependencyGraph.js index de3a448f66..ae87188fec 100644 --- a/packages/metro/src/node-haste/DependencyGraph.js +++ b/packages/metro/src/node-haste/DependencyGraph.js @@ -55,6 +55,14 @@ function getOrCreateMap( return subMap; } +const missingSha1Error = (mixedPath: string) => + new Error(`Failed to get the SHA-1 for: ${mixedPath}. + Potential causes: + 1) The file is not watched. Ensure it is under the configured \`projectRoot\` or \`watchFolders\`. + 2) Check \`blockList\` in your metro.config.js and make sure it isn't excluding the file path. + 3) The file may have been deleted since it was resolved - try refreshing your app. + 4) Otherwise, this is a bug in Metro or the configured resolver - please report it.`); + class DependencyGraph extends EventEmitter { _config: ConfigT; _haste: MetroFileMap; @@ -258,21 +266,25 @@ class DependencyGraph extends EventEmitter { getSha1(filename: string): string { const sha1 = this._fileSystem.getSha1(filename); - if (!sha1) { - throw new ReferenceError( - `Failed to get the SHA-1 for ${filename}. - Potential causes: - 1) The file is not watched. Ensure it is under the configured \`projectRoot\` or \`watchFolders\`. - 2) Check \`blockList\` in your metro.config.js and make sure it isn't excluding the file path. - 3) The file may have been deleted since it was resolved - try refreshing your app. - 4) Otherwise, this is a bug in Metro or the configured resolver - please report it.`, - ); + throw missingSha1Error(filename); } - return sha1; } + /** + * Used when watcher.unstable_lazySha1 is true + */ + async unstable_getOrComputeSha1( + mixedPath: string, + ): Promise<{content?: Buffer, sha1: string}> { + const result = await this._fileSystem.getOrComputeSha1(mixedPath); + if (!result || !result.sha1) { + throw missingSha1Error(mixedPath); + } + return result; + } + getWatcher(): EventEmitter { return this._haste; } diff --git a/packages/metro/src/node-haste/DependencyGraph/createFileMap.js b/packages/metro/src/node-haste/DependencyGraph/createFileMap.js index 905c0a8f96..d3d8fd3c03 100644 --- a/packages/metro/src/node-haste/DependencyGraph/createFileMap.js +++ b/packages/metro/src/node-haste/DependencyGraph/createFileMap.js @@ -85,7 +85,7 @@ function createFileMap( })), perfLoggerFactory: config.unstable_perfLoggerFactory, computeDependencies, - computeSha1: true, + computeSha1: !config.watcher.unstable_lazySha1, dependencyExtractor: config.resolver.dependencyExtractor, enableHastePackages: config?.resolver.enableGlobalPackages, enableSymlinks: true,