metro 0.75.0 → 0.75.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metro",
3
- "version": "0.75.0",
3
+ "version": "0.75.1",
4
4
  "description": "🚇 The JavaScript bundler for React Native.",
5
5
  "main": "src/index.js",
6
6
  "bin": "src/cli.js",
@@ -35,23 +35,23 @@
35
35
  "invariant": "^2.2.4",
36
36
  "jest-worker": "^27.2.0",
37
37
  "lodash.throttle": "^4.1.1",
38
- "metro-babel-transformer": "0.75.0",
39
- "metro-cache": "0.75.0",
40
- "metro-cache-key": "0.75.0",
41
- "metro-config": "0.75.0",
42
- "metro-core": "0.75.0",
43
- "metro-file-map": "0.75.0",
44
- "metro-hermes-compiler": "0.75.0",
45
- "metro-inspector-proxy": "0.75.0",
46
- "metro-minify-terser": "0.75.0",
47
- "metro-minify-uglify": "0.75.0",
48
- "metro-react-native-babel-preset": "0.75.0",
49
- "metro-resolver": "0.75.0",
50
- "metro-runtime": "0.75.0",
51
- "metro-source-map": "0.75.0",
52
- "metro-symbolicate": "0.75.0",
53
- "metro-transform-plugins": "0.75.0",
54
- "metro-transform-worker": "0.75.0",
38
+ "metro-babel-transformer": "0.75.1",
39
+ "metro-cache": "0.75.1",
40
+ "metro-cache-key": "0.75.1",
41
+ "metro-config": "0.75.1",
42
+ "metro-core": "0.75.1",
43
+ "metro-file-map": "0.75.1",
44
+ "metro-hermes-compiler": "0.75.1",
45
+ "metro-inspector-proxy": "0.75.1",
46
+ "metro-minify-terser": "0.75.1",
47
+ "metro-minify-uglify": "0.75.1",
48
+ "metro-react-native-babel-preset": "0.75.1",
49
+ "metro-resolver": "0.75.1",
50
+ "metro-runtime": "0.75.1",
51
+ "metro-source-map": "0.75.1",
52
+ "metro-symbolicate": "0.75.1",
53
+ "metro-transform-plugins": "0.75.1",
54
+ "metro-transform-worker": "0.75.1",
55
55
  "mime-types": "^2.1.27",
56
56
  "node-fetch": "^2.2.0",
57
57
  "nullthrows": "^1.1.1",
@@ -70,10 +70,10 @@
70
70
  "dedent": "^0.7.0",
71
71
  "jest-snapshot": "^26.5.2",
72
72
  "jest-snapshot-serializer-raw": "^1.2.0",
73
- "metro-babel-register": "0.75.0",
74
- "metro-memory-fs": "0.75.0",
75
- "metro-react-native-babel-preset": "0.75.0",
76
- "metro-react-native-babel-transformer": "0.75.0",
73
+ "metro-babel-register": "0.75.1",
74
+ "metro-memory-fs": "0.75.1",
75
+ "metro-react-native-babel-preset": "0.75.1",
76
+ "metro-react-native-babel-transformer": "0.75.1",
77
77
  "mock-req": "^0.2.0",
78
78
  "mock-res": "^0.6.0",
79
79
  "stack-trace": "^0.0.10"
@@ -11,7 +11,11 @@
11
11
 
12
12
  "use strict";
13
13
 
14
+ var _path = _interopRequireDefault(require("path"));
14
15
  var _Graph = require("./Graph");
16
+ function _interopRequireDefault(obj) {
17
+ return obj && obj.__esModule ? obj : { default: obj };
18
+ }
15
19
  const debug = require("debug")("Metro:DeltaCalculator");
16
20
  const { EventEmitter } = require("events");
17
21
 
@@ -25,7 +29,7 @@ class DeltaCalculator extends EventEmitter {
25
29
  _deletedFiles = new Set();
26
30
  _modifiedFiles = new Set();
27
31
  _addedFiles = new Set();
28
- _hasSymlinkChanges = false;
32
+ _requiresReset = false;
29
33
  constructor(entryPoints, changeEventSource, options) {
30
34
  super();
31
35
  this._options = options;
@@ -78,13 +82,13 @@ class DeltaCalculator extends EventEmitter {
78
82
  this._deletedFiles = new Set();
79
83
  const addedFiles = this._addedFiles;
80
84
  this._addedFiles = new Set();
81
- const hasSymlinkChanges = this._hasSymlinkChanges;
82
- this._hasSymlinkChanges = false;
85
+ const requiresReset = this._requiresReset;
86
+ this._requiresReset = false;
83
87
 
84
- // Revisit all files if changes include symlinks - resolutions may be
88
+ // Revisit all files if changes require a graph reset - resolutions may be
85
89
  // invalidated but we don't yet know which. This should be optimized in the
86
90
  // future.
87
- if (hasSymlinkChanges) {
91
+ if (requiresReset) {
88
92
  const markModified = (file) => {
89
93
  if (!addedFiles.has(file) && !deletedFiles.has(file)) {
90
94
  modifiedFiles.add(file);
@@ -165,12 +169,15 @@ class DeltaCalculator extends EventEmitter {
165
169
  */
166
170
  _handleFileChange = ({ type, filePath, metadata }, logger) => {
167
171
  debug("Handling %s: %s (type: %s)", type, filePath, metadata.type);
168
- if (metadata.type === "l") {
169
- this._hasSymlinkChanges = true;
172
+ if (
173
+ metadata.type === "l" ||
174
+ (this._options.unstable_enablePackageExports &&
175
+ filePath.endsWith(_path.default.sep + "package.json"))
176
+ ) {
177
+ this._requiresReset = true;
170
178
  this.emit("change", {
171
179
  logger,
172
180
  });
173
- return;
174
181
  }
175
182
  let state;
176
183
  if (this._deletedFiles.has(filePath)) {
@@ -233,13 +240,13 @@ class DeltaCalculator extends EventEmitter {
233
240
  // If a file has been deleted, we want to invalidate any other file that
234
241
  // depends on it, so we can process it and correctly return an error.
235
242
  deletedFiles.forEach((filePath) => {
236
- for (const path of this._graph.getModifiedModulesForDeletedPath(
243
+ for (const modifiedModulePath of this._graph.getModifiedModulesForDeletedPath(
237
244
  filePath
238
245
  )) {
239
246
  // Only mark the inverse dependency as modified if it's not already
240
247
  // marked as deleted (in that case we can just ignore it).
241
- if (!deletedFiles.has(path)) {
242
- modifiedFiles.add(path);
248
+ if (!deletedFiles.has(modifiedModulePath)) {
249
+ modifiedFiles.add(modifiedModulePath);
243
250
  }
244
251
  }
245
252
  });
@@ -11,6 +11,7 @@
11
11
 
12
12
  'use strict';
13
13
 
14
+ import path from 'path';
14
15
  import {Graph} from './Graph';
15
16
  import type {DeltaResult, Options} from './types.flow';
16
17
  import type {RootPerfLogger} from 'metro-config';
@@ -33,7 +34,7 @@ class DeltaCalculator<T> extends EventEmitter {
33
34
  _deletedFiles: Set<string> = new Set();
34
35
  _modifiedFiles: Set<string> = new Set();
35
36
  _addedFiles: Set<string> = new Set();
36
- _hasSymlinkChanges = false;
37
+ _requiresReset = false;
37
38
 
38
39
  _graph: Graph<T>;
39
40
 
@@ -104,13 +105,13 @@ class DeltaCalculator<T> extends EventEmitter {
104
105
  this._deletedFiles = new Set();
105
106
  const addedFiles = this._addedFiles;
106
107
  this._addedFiles = new Set();
107
- const hasSymlinkChanges = this._hasSymlinkChanges;
108
- this._hasSymlinkChanges = false;
108
+ const requiresReset = this._requiresReset;
109
+ this._requiresReset = false;
109
110
 
110
- // Revisit all files if changes include symlinks - resolutions may be
111
+ // Revisit all files if changes require a graph reset - resolutions may be
111
112
  // invalidated but we don't yet know which. This should be optimized in the
112
113
  // future.
113
- if (hasSymlinkChanges) {
114
+ if (requiresReset) {
114
115
  const markModified = (file: string) => {
115
116
  if (!addedFiles.has(file) && !deletedFiles.has(file)) {
116
117
  modifiedFiles.add(file);
@@ -207,10 +208,13 @@ class DeltaCalculator<T> extends EventEmitter {
207
208
  logger: ?RootPerfLogger,
208
209
  ): mixed => {
209
210
  debug('Handling %s: %s (type: %s)', type, filePath, metadata.type);
210
- if (metadata.type === 'l') {
211
- this._hasSymlinkChanges = true;
211
+ if (
212
+ metadata.type === 'l' ||
213
+ (this._options.unstable_enablePackageExports &&
214
+ filePath.endsWith(path.sep + 'package.json'))
215
+ ) {
216
+ this._requiresReset = true;
212
217
  this.emit('change', {logger});
213
- return;
214
218
  }
215
219
  let state: void | 'deleted' | 'modified' | 'added';
216
220
  if (this._deletedFiles.has(filePath)) {
@@ -281,13 +285,13 @@ class DeltaCalculator<T> extends EventEmitter {
281
285
  // If a file has been deleted, we want to invalidate any other file that
282
286
  // depends on it, so we can process it and correctly return an error.
283
287
  deletedFiles.forEach((filePath: string) => {
284
- for (const path of this._graph.getModifiedModulesForDeletedPath(
288
+ for (const modifiedModulePath of this._graph.getModifiedModulesForDeletedPath(
285
289
  filePath,
286
290
  )) {
287
291
  // Only mark the inverse dependency as modified if it's not already
288
292
  // marked as deleted (in that case we can just ignore it).
289
- if (!deletedFiles.has(path)) {
290
- modifiedFiles.add(path);
293
+ if (!deletedFiles.has(modifiedModulePath)) {
294
+ modifiedFiles.add(modifiedModulePath);
291
295
  }
292
296
  }
293
297
  });
@@ -138,6 +138,7 @@ export type Options<T = MixedOutput> = {
138
138
  +onProgress: ?(numProcessed: number, total: number) => mixed,
139
139
  +experimentalImportBundleSupport: boolean,
140
140
  +unstable_allowRequireContext: boolean,
141
+ +unstable_enablePackageExports: boolean,
141
142
  +shallow: boolean,
142
143
  };
143
144
 
@@ -81,6 +81,8 @@ class IncrementalBundler {
81
81
  this._config.server.experimentalImportBundleSupport,
82
82
  unstable_allowRequireContext:
83
83
  this._config.transformer.unstable_allowRequireContext,
84
+ unstable_enablePackageExports:
85
+ this._config.resolver.unstable_enablePackageExports,
84
86
  shallow: otherOptions.shallow,
85
87
  });
86
88
  this._config.serializer.experimentalSerializerHook(graph, {
@@ -123,6 +125,8 @@ class IncrementalBundler {
123
125
  this._config.server.experimentalImportBundleSupport,
124
126
  unstable_allowRequireContext:
125
127
  this._config.transformer.unstable_allowRequireContext,
128
+ unstable_enablePackageExports:
129
+ this._config.resolver.unstable_enablePackageExports,
126
130
  shallow: otherOptions.shallow,
127
131
  }
128
132
  );
@@ -130,6 +130,8 @@ class IncrementalBundler {
130
130
  this._config.server.experimentalImportBundleSupport,
131
131
  unstable_allowRequireContext:
132
132
  this._config.transformer.unstable_allowRequireContext,
133
+ unstable_enablePackageExports:
134
+ this._config.resolver.unstable_enablePackageExports,
133
135
  shallow: otherOptions.shallow,
134
136
  });
135
137
 
@@ -176,6 +178,8 @@ class IncrementalBundler {
176
178
  this._config.server.experimentalImportBundleSupport,
177
179
  unstable_allowRequireContext:
178
180
  this._config.transformer.unstable_allowRequireContext,
181
+ unstable_enablePackageExports:
182
+ this._config.resolver.unstable_enablePackageExports,
179
183
  shallow: otherOptions.shallow,
180
184
  },
181
185
  );
package/src/Server.js CHANGED
@@ -668,6 +668,11 @@ class Server {
668
668
  });
669
669
  const revPromise = this._bundler.getRevisionByGraphId(graphId);
670
670
  bundlePerfLogger.point("resolvingAndTransformingDependencies_start");
671
+ bundlePerfLogger.annotate({
672
+ bool: {
673
+ initial_build: revPromise == null,
674
+ },
675
+ });
671
676
  const { delta, revision } = await (revPromise != null
672
677
  ? this._bundler.updateGraph(await revPromise, false)
673
678
  : this._bundler.initializeGraph(
@@ -832,6 +832,11 @@ class Server {
832
832
  const revPromise = this._bundler.getRevisionByGraphId(graphId);
833
833
 
834
834
  bundlePerfLogger.point('resolvingAndTransformingDependencies_start');
835
+ bundlePerfLogger.annotate({
836
+ bool: {
837
+ initial_build: revPromise == null,
838
+ },
839
+ });
835
840
  const {delta, revision} = await (revPromise != null
836
841
  ? this._bundler.updateGraph(await revPromise, false)
837
842
  : this._bundler.initializeGraph(
@@ -13,6 +13,7 @@ var _utils = require("./utils");
13
13
 
14
14
  function main() {
15
15
  return (0, _utils.copyContextToObject)(
16
+ // $FlowFixMe[underconstrained-implicit-instantiation]
16
17
  require.context("./subdir", undefined, undefined, "sync")
17
18
  );
18
19
  }
@@ -15,6 +15,7 @@ declare var require: RequireWithContext;
15
15
 
16
16
  function main(): mixed {
17
17
  return copyContextToObject(
18
+ // $FlowFixMe[underconstrained-implicit-instantiation]
18
19
  require.context('./subdir', undefined, undefined, 'sync'),
19
20
  );
20
21
  }
@@ -203,6 +203,9 @@ class TerminalReporter {
203
203
  case "global_cache_disabled":
204
204
  this._logCacheDisabled(event.reason);
205
205
  break;
206
+ case "resolver_warning":
207
+ this._logWarning(event.message);
208
+ break;
206
209
  case "transform_cache_reset":
207
210
  reporting.logWarning(this.terminal, "the transform cache was reset.");
208
211
  break;
@@ -360,6 +363,9 @@ class TerminalReporter {
360
363
  e
361
364
  );
362
365
  }
366
+ _logWarning(message) {
367
+ reporting.logWarning(this.terminal, message);
368
+ }
363
369
  _logWatcherHealthCheckResult(result) {
364
370
  var _result$pauseReason, _this$_prevHealthChec;
365
371
  // Don't be spammy; only report changes in status.
@@ -260,6 +260,9 @@ class TerminalReporter {
260
260
  case 'global_cache_disabled':
261
261
  this._logCacheDisabled(event.reason);
262
262
  break;
263
+ case 'resolver_warning':
264
+ this._logWarning(event.message);
265
+ break;
263
266
  case 'transform_cache_reset':
264
267
  reporting.logWarning(this.terminal, 'the transform cache was reset.');
265
268
  break;
@@ -435,6 +438,10 @@ class TerminalReporter {
435
438
  );
436
439
  }
437
440
 
441
+ _logWarning(message: string): void {
442
+ reporting.logWarning(this.terminal, message);
443
+ }
444
+
438
445
  _logWatcherHealthCheckResult(result: HealthCheckResult) {
439
446
  // Don't be spammy; only report changes in status.
440
447
  if (
@@ -59,6 +59,8 @@ async function getPrependedScripts(
59
59
  onProgress: null,
60
60
  experimentalImportBundleSupport:
61
61
  config.server.experimentalImportBundleSupport,
62
+ unstable_enablePackageExports:
63
+ config.resolver.unstable_enablePackageExports,
62
64
  shallow: false,
63
65
  }
64
66
  );
@@ -69,6 +69,8 @@ async function getPrependedScripts(
69
69
  onProgress: null,
70
70
  experimentalImportBundleSupport:
71
71
  config.server.experimentalImportBundleSupport,
72
+ unstable_enablePackageExports:
73
+ config.resolver.unstable_enablePackageExports,
72
74
  shallow: false,
73
75
  },
74
76
  );
@@ -127,6 +127,10 @@ export type ReportableEvent =
127
127
  mode: 'BRIDGE' | 'NOBRIDGE',
128
128
  ...
129
129
  }
130
+ | {
131
+ type: 'resolver_warning',
132
+ message: string,
133
+ }
130
134
  | {
131
135
  type: 'transformer_load_started',
132
136
  }
@@ -67,6 +67,8 @@ async function calcTransformerOptions(
67
67
  config.server.experimentalImportBundleSupport,
68
68
  unstable_allowRequireContext:
69
69
  config.transformer.unstable_allowRequireContext,
70
+ unstable_enablePackageExports:
71
+ config.resolver.unstable_enablePackageExports,
70
72
  shallow: false,
71
73
  });
72
74
  return Array.from(dependencies.keys());
@@ -90,6 +90,8 @@ async function calcTransformerOptions(
90
90
  config.server.experimentalImportBundleSupport,
91
91
  unstable_allowRequireContext:
92
92
  config.transformer.unstable_allowRequireContext,
93
+ unstable_enablePackageExports:
94
+ config.resolver.unstable_enablePackageExports,
93
95
  shallow: false,
94
96
  });
95
97
 
@@ -74,6 +74,7 @@ class ModuleResolver {
74
74
  unstable_conditionNames,
75
75
  unstable_conditionsByPlatform,
76
76
  unstable_enablePackageExports,
77
+ unstable_getRealPath,
77
78
  } = this._options;
78
79
  try {
79
80
  var _resolverOptions$cust;
@@ -93,6 +94,8 @@ class ModuleResolver {
93
94
  unstable_conditionNames,
94
95
  unstable_conditionsByPlatform,
95
96
  unstable_enablePackageExports,
97
+ unstable_getRealPath,
98
+ unstable_logWarning: this._logWarning,
96
99
  customResolverOptions:
97
100
  (_resolverOptions$cust = resolverOptions.customResolverOptions) !==
98
101
  null && _resolverOptions$cust !== void 0
@@ -202,6 +205,12 @@ class ModuleResolver {
202
205
  throw new Error("invalid type");
203
206
  }
204
207
  }
208
+ _logWarning = (message) => {
209
+ this._options.reporter.update({
210
+ type: "resolver_warning",
211
+ message,
212
+ });
213
+ };
205
214
  _removeRoot(candidates) {
206
215
  if (candidates.filePathPrefix) {
207
216
  candidates.filePathPrefix = path.relative(
@@ -15,6 +15,7 @@ import type {
15
15
  CustomResolver,
16
16
  DoesFileExist,
17
17
  FileCandidates,
18
+ GetRealPath,
18
19
  IsAssetFile,
19
20
  Resolution,
20
21
  ResolveAsset,
@@ -30,6 +31,7 @@ const createDefaultContext = require('metro-resolver/src/createDefaultContext');
30
31
  const path = require('path');
31
32
  const util = require('util');
32
33
  import type {BundlerResolution} from '../../DeltaBundler/types.flow';
34
+ import type {Reporter} from '../../lib/reporting';
33
35
 
34
36
  export type DirExistsFn = (filePath: string) => boolean;
35
37
 
@@ -66,6 +68,7 @@ type Options<TPackage> = $ReadOnly<{
66
68
  nodeModulesPaths: $ReadOnlyArray<string>,
67
69
  preferNativePlatform: boolean,
68
70
  projectRoot: string,
71
+ reporter: Reporter,
69
72
  resolveAsset: ResolveAsset,
70
73
  resolveRequest: ?CustomResolver,
71
74
  sourceExts: $ReadOnlyArray<string>,
@@ -74,6 +77,7 @@ type Options<TPackage> = $ReadOnly<{
74
77
  [platform: string]: $ReadOnlyArray<string>,
75
78
  }>,
76
79
  unstable_enablePackageExports: boolean,
80
+ unstable_getRealPath: ?GetRealPath,
77
81
  }>;
78
82
 
79
83
  class ModuleResolver<TPackage: Packageish> {
@@ -136,6 +140,7 @@ class ModuleResolver<TPackage: Packageish> {
136
140
  unstable_conditionNames,
137
141
  unstable_conditionsByPlatform,
138
142
  unstable_enablePackageExports,
143
+ unstable_getRealPath,
139
144
  } = this._options;
140
145
 
141
146
  try {
@@ -155,6 +160,8 @@ class ModuleResolver<TPackage: Packageish> {
155
160
  unstable_conditionNames,
156
161
  unstable_conditionsByPlatform,
157
162
  unstable_enablePackageExports,
163
+ unstable_getRealPath,
164
+ unstable_logWarning: this._logWarning,
158
165
  customResolverOptions: resolverOptions.customResolverOptions ?? {},
159
166
  originModulePath: fromModule.path,
160
167
  resolveHasteModule: (name: string) =>
@@ -268,6 +275,13 @@ class ModuleResolver<TPackage: Packageish> {
268
275
  }
269
276
  }
270
277
 
278
+ _logWarning = (message: string): void => {
279
+ this._options.reporter.update({
280
+ type: 'resolver_warning',
281
+ message,
282
+ });
283
+ };
284
+
271
285
  _removeRoot(candidates: FileCandidates): FileCandidates {
272
286
  if (candidates.filePathPrefix) {
273
287
  candidates.filePathPrefix = path.relative(
@@ -150,14 +150,24 @@ class DependencyGraph extends EventEmitter {
150
150
  nodeModulesPaths: this._config.resolver.nodeModulesPaths,
151
151
  preferNativePlatform: true,
152
152
  projectRoot: this._config.projectRoot,
153
+ reporter: this._config.reporter,
153
154
  resolveAsset: (dirPath, assetName, extension) => {
154
155
  const basePath = dirPath + path.sep + assetName;
155
- const assets = [
156
+ let assets = [
156
157
  basePath + extension,
157
158
  ...this._config.resolver.assetResolutions.map(
158
159
  (resolution) => basePath + "@" + resolution + "x" + extension
159
160
  ),
160
- ].filter((candidate) => this._fileSystem.exists(candidate));
161
+ ];
162
+ if (this._config.resolver.unstable_enableSymlinks) {
163
+ assets = assets
164
+ .map((candidate) => this._fileSystem.getRealPath(candidate))
165
+ .filter(Boolean);
166
+ } else {
167
+ assets = assets.filter((candidate) =>
168
+ this._fileSystem.exists(candidate)
169
+ );
170
+ }
161
171
  return assets.length ? assets : null;
162
172
  },
163
173
  resolveRequest: this._config.resolver.resolveRequest,
@@ -167,6 +177,9 @@ class DependencyGraph extends EventEmitter {
167
177
  this._config.resolver.unstable_conditionsByPlatform,
168
178
  unstable_enablePackageExports:
169
179
  this._config.resolver.unstable_enablePackageExports,
180
+ unstable_getRealPath: this._config.resolver.unstable_enableSymlinks
181
+ ? (path) => this._fileSystem.getRealPath(path)
182
+ : null,
170
183
  });
171
184
  }
172
185
  _createModuleCache() {
@@ -186,14 +199,18 @@ class DependencyGraph extends EventEmitter {
186
199
  const containerName =
187
200
  splitIndex !== -1 ? filename.slice(0, splitIndex + 4) : filename;
188
201
 
189
- // TODO Calling realpath allows us to get a hash for a given path even when
202
+ // Prior to unstable_enableSymlinks:
203
+ // Calling realpath allows us to get a hash for a given path even when
190
204
  // it's a symlink to a file, which prevents Metro from crashing in such a
191
205
  // case. However, it doesn't allow Metro to track changes to the target file
192
206
  // of the symlink. We should fix this by implementing a symlink map into
193
207
  // Metro (or maybe by implementing those "extra transformation sources" we've
194
208
  // been talking about for stuff like CSS or WASM).
195
-
196
- const resolvedPath = fs.realpathSync(containerName);
209
+ //
210
+ // This is unnecessary with a symlink-aware fileSystem implementation.
211
+ const resolvedPath = this._config.resolver.unstable_enableSymlinks
212
+ ? containerName
213
+ : fs.realpathSync(containerName);
197
214
  const sha1 = this._fileSystem.getSha1(resolvedPath);
198
215
  if (!sha1) {
199
216
  throw new ReferenceError(`SHA-1 for file ${filename} (${resolvedPath}) is not computed.
@@ -197,14 +197,26 @@ class DependencyGraph extends EventEmitter {
197
197
  nodeModulesPaths: this._config.resolver.nodeModulesPaths,
198
198
  preferNativePlatform: true,
199
199
  projectRoot: this._config.projectRoot,
200
+ reporter: this._config.reporter,
200
201
  resolveAsset: (dirPath: string, assetName: string, extension: string) => {
201
202
  const basePath = dirPath + path.sep + assetName;
202
- const assets = [
203
+ let assets = [
203
204
  basePath + extension,
204
205
  ...this._config.resolver.assetResolutions.map(
205
206
  resolution => basePath + '@' + resolution + 'x' + extension,
206
207
  ),
207
- ].filter(candidate => this._fileSystem.exists(candidate));
208
+ ];
209
+
210
+ if (this._config.resolver.unstable_enableSymlinks) {
211
+ assets = assets
212
+ .map(candidate => this._fileSystem.getRealPath(candidate))
213
+ .filter(Boolean);
214
+ } else {
215
+ assets = assets.filter(candidate =>
216
+ this._fileSystem.exists(candidate),
217
+ );
218
+ }
219
+
208
220
  return assets.length ? assets : null;
209
221
  },
210
222
  resolveRequest: this._config.resolver.resolveRequest,
@@ -214,6 +226,9 @@ class DependencyGraph extends EventEmitter {
214
226
  this._config.resolver.unstable_conditionsByPlatform,
215
227
  unstable_enablePackageExports:
216
228
  this._config.resolver.unstable_enablePackageExports,
229
+ unstable_getRealPath: this._config.resolver.unstable_enableSymlinks
230
+ ? path => this._fileSystem.getRealPath(path)
231
+ : null,
217
232
  });
218
233
  }
219
234
 
@@ -236,14 +251,19 @@ class DependencyGraph extends EventEmitter {
236
251
  const containerName =
237
252
  splitIndex !== -1 ? filename.slice(0, splitIndex + 4) : filename;
238
253
 
239
- // TODO Calling realpath allows us to get a hash for a given path even when
254
+ // Prior to unstable_enableSymlinks:
255
+ // Calling realpath allows us to get a hash for a given path even when
240
256
  // it's a symlink to a file, which prevents Metro from crashing in such a
241
257
  // case. However, it doesn't allow Metro to track changes to the target file
242
258
  // of the symlink. We should fix this by implementing a symlink map into
243
259
  // Metro (or maybe by implementing those "extra transformation sources" we've
244
260
  // been talking about for stuff like CSS or WASM).
261
+ //
262
+ // This is unnecessary with a symlink-aware fileSystem implementation.
263
+ const resolvedPath = this._config.resolver.unstable_enableSymlinks
264
+ ? containerName
265
+ : fs.realpathSync(containerName);
245
266
 
246
- const resolvedPath = fs.realpathSync(containerName);
247
267
  const sha1 = this._fileSystem.getSha1(resolvedPath);
248
268
 
249
269
  if (!sha1) {