metro 0.63.0 → 0.64.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.
Files changed (90) hide show
  1. package/package.json +22 -22
  2. package/src/Assets.js +8 -2
  3. package/src/Bundler/util.js +17 -10
  4. package/src/Bundler/util.js.flow +13 -12
  5. package/src/Bundler.js +4 -0
  6. package/src/DeltaBundler/DeltaCalculator.js +6 -1
  7. package/src/DeltaBundler/Serializers/baseBytecodeBundle.js +10 -0
  8. package/src/DeltaBundler/Serializers/baseJSBundle.js +5 -0
  9. package/src/DeltaBundler/Serializers/getAllFiles.js +6 -0
  10. package/src/DeltaBundler/Serializers/getAssets.js +4 -0
  11. package/src/DeltaBundler/Serializers/getRamBundleInfo.js +6 -0
  12. package/src/DeltaBundler/Serializers/helpers/bytecode.js +5 -0
  13. package/src/DeltaBundler/Serializers/helpers/getSourceMapInfo.js +5 -0
  14. package/src/DeltaBundler/Serializers/helpers/processBytecodeModules.js +2 -0
  15. package/src/DeltaBundler/Serializers/helpers/processModules.js +2 -0
  16. package/src/DeltaBundler/Serializers/hmrJSBundle.js +8 -2
  17. package/src/DeltaBundler/Serializers/sourceMapGenerator.js +4 -0
  18. package/src/DeltaBundler/Serializers/sourceMapObject.js +4 -0
  19. package/src/DeltaBundler/Transformer.js +16 -0
  20. package/src/DeltaBundler/Worker.js +4 -0
  21. package/src/DeltaBundler/WorkerFarm.js +9 -0
  22. package/src/DeltaBundler/computeDelta.js +5 -0
  23. package/src/DeltaBundler/getTransformCacheKey.js +1 -1
  24. package/src/DeltaBundler/mergeDeltas.js +5 -0
  25. package/src/DeltaBundler/traverseDependencies.js +14 -0
  26. package/src/DeltaBundler.js +6 -1
  27. package/src/HmrServer.js +18 -0
  28. package/src/IncrementalBundler.js +16 -0
  29. package/src/ModuleGraph/node-haste/Package.js +5 -0
  30. package/src/ModuleGraph/node-haste/node-haste.flow.js.flow +0 -2
  31. package/src/ModuleGraph/node-haste/node-haste.js +3 -0
  32. package/src/ModuleGraph/node-haste/node-haste.js.flow +4 -2
  33. package/src/ModuleGraph/output/indexed-ram-bundle.js +10 -0
  34. package/src/ModuleGraph/output/multiple-files-ram-bundle.js +10 -0
  35. package/src/ModuleGraph/output/reverse-dependency-map-references.js +33 -9
  36. package/src/ModuleGraph/output/reverse-dependency-map-references.js.flow +28 -13
  37. package/src/ModuleGraph/output/util.js +59 -25
  38. package/src/ModuleGraph/output/util.js.flow +59 -23
  39. package/src/ModuleGraph/test-helpers.js +74 -2
  40. package/src/ModuleGraph/types.flow.js.flow +13 -8
  41. package/src/ModuleGraph/worker/JsFileWrapping.js +90 -12
  42. package/src/ModuleGraph/worker/JsFileWrapping.js.flow +34 -20
  43. package/src/ModuleGraph/worker/collectDependencies.js +329 -205
  44. package/src/ModuleGraph/worker/collectDependencies.js.flow +397 -209
  45. package/src/ModuleGraph/worker/generate.js.flow +2 -2
  46. package/src/ModuleGraph/worker/generateImportNames.js +2 -1
  47. package/src/ModuleGraph/worker/generateImportNames.js.flow +3 -4
  48. package/src/ModuleGraph/worker/mergeSourceMaps.js +5 -0
  49. package/src/Server/symbolicate.js +14 -0
  50. package/src/Server.js +16 -3
  51. package/src/Server.js.flow +1 -0
  52. package/src/cli-utils.js +4 -0
  53. package/src/cli.js +0 -0
  54. package/src/commands/build.js +10 -0
  55. package/src/commands/build.js.flow +2 -0
  56. package/src/commands/dependencies.js +4 -0
  57. package/src/commands/serve.js +5 -0
  58. package/src/commands/serve.js.flow +2 -0
  59. package/src/index.js +13 -1
  60. package/src/index.js.flow +2 -0
  61. package/src/integration_tests/basic_bundle/import-export/index.js +19 -2
  62. package/src/integration_tests/execBundle.js +2 -2
  63. package/src/integration_tests/metro.config.js +4 -0
  64. package/src/lib/BatchProcessor.js +1 -1
  65. package/src/lib/TerminalReporter.js +10 -0
  66. package/src/lib/attachWebsocketServer.js +5 -1
  67. package/src/lib/bundleToBytecode.js +7 -3
  68. package/src/lib/bundleToString.js +6 -1
  69. package/src/lib/debounceAsyncQueue.js +4 -0
  70. package/src/lib/getAppendScripts.js +2 -0
  71. package/src/lib/getPreludeCode.js +2 -0
  72. package/src/lib/getPrependedScripts.js +11 -0
  73. package/src/lib/logToConsole.js +1 -2
  74. package/src/lib/parseOptionsFromUrl.js +5 -0
  75. package/src/lib/reporting.js +1 -1
  76. package/src/lib/splitBundleOptions.js +1 -1
  77. package/src/lib/transformHelpers.js +19 -3
  78. package/src/lib/transformHelpers.js.flow +4 -4
  79. package/src/node-haste/DependencyGraph/ModuleResolution.js +10 -2
  80. package/src/node-haste/DependencyGraph/ModuleResolution.js.flow +6 -5
  81. package/src/node-haste/DependencyGraph.js +11 -2
  82. package/src/node-haste/DependencyGraph.js.flow +5 -0
  83. package/src/node-haste/Package.js +2 -0
  84. package/src/shared/output/RamBundle/as-assets.js +1 -2
  85. package/src/shared/output/RamBundle/as-indexed-file.js +2 -0
  86. package/src/shared/output/RamBundle/util.js +2 -2
  87. package/src/shared/output/RamBundle.js +9 -0
  88. package/src/shared/output/bundle.js +9 -0
  89. package/src/ModuleGraph/worker/optimizeDependencies.js +0 -122
  90. package/src/ModuleGraph/worker/optimizeDependencies.js.flow +0 -128
@@ -10,6 +10,7 @@
10
10
 
11
11
  'use strict';
12
12
 
13
+ const invariant = require('invariant');
13
14
  const nullthrows = require('nullthrows');
14
15
 
15
16
  const generate = require('@babel/generator').default;
@@ -17,101 +18,114 @@ const template = require('@babel/template').default;
17
18
  const traverse = require('@babel/traverse').default;
18
19
  const types = require('@babel/types');
19
20
 
20
- import type {Ast} from '@babel/core';
21
+ const {isImport} = types;
22
+
23
+ import type {NodePath} from '@babel/traverse';
24
+ import type {CallExpression, Identifier, StringLiteral} from '@babel/types';
21
25
  import type {
22
26
  AllowOptionalDependencies,
23
27
  AsyncDependencyType,
24
28
  } from 'metro/src/DeltaBundler/types.flow.js';
25
29
 
26
- opaque type Identifier = any;
27
- opaque type Path = any;
28
-
29
- type DepOptions = {|
30
- +prefetchOnly: boolean,
31
- +jsResource?: boolean,
32
- +isOptional?: boolean,
33
- |};
30
+ type ImportDependencyOptions = $ReadOnly<{
31
+ asyncType: AsyncDependencyType,
32
+ jsResource?: boolean,
33
+ splitCondition?: NodePath<>,
34
+ }>;
34
35
 
35
- type InternalDependency<D> = {|
36
- +data: D,
37
- +name: string,
38
- |};
36
+ export type Dependency<TSplitCondition> = $ReadOnly<{
37
+ data: DependencyData<TSplitCondition>,
38
+ name: string,
39
+ }>;
39
40
 
40
- type InternalDependencyData = {|
41
+ type DependencyData<TSplitCondition> = $ReadOnly<{
42
+ // If null, then the dependency is synchronous.
43
+ // (ex. `require('foo')`)
41
44
  asyncType: AsyncDependencyType | null,
42
45
  isOptional?: boolean,
46
+ // If left unspecified, then the dependency is unconditionally split.
47
+ splitCondition?: TSplitCondition,
43
48
  locs: Array<BabelSourceLocation>,
44
- |};
49
+ }>;
45
50
 
46
- type InternalDependencyInfo = {|
47
- data: InternalDependencyData,
51
+ export type MutableInternalDependency<TSplitCondition> = {
52
+ ...DependencyData<TSplitCondition>,
48
53
  index: number,
49
- |};
54
+ name: string,
55
+ };
56
+
57
+ export type InternalDependency<TSplitCondition> = $ReadOnly<
58
+ MutableInternalDependency<TSplitCondition>,
59
+ >;
50
60
 
51
- type State = {|
52
- asyncRequireModulePathStringLiteral: ?Identifier,
53
- dependency: number,
61
+ export type State<TSplitCondition> = {
62
+ asyncRequireModulePathStringLiteral: ?StringLiteral,
54
63
  dependencyCalls: Set<string>,
55
- dependencyData: Map<string, InternalDependencyData>,
56
- dependencyIndexes: Map<string, number>,
64
+ dependencyRegistry: ModuleDependencyRegistry<TSplitCondition>,
65
+ dependencyTransformer: DependencyTransformer<TSplitCondition>,
57
66
  dynamicRequires: DynamicRequiresBehavior,
58
67
  dependencyMapIdentifier: ?Identifier,
59
68
  keepRequireNames: boolean,
60
- disableRequiresTransform: boolean,
61
69
  allowOptionalDependencies: AllowOptionalDependencies,
62
- |};
63
-
64
- export type Options = {|
65
- +asyncRequireModulePath: string,
66
- +dependencyMapName?: string,
67
- +dynamicRequires: DynamicRequiresBehavior,
68
- +inlineableCalls: $ReadOnlyArray<string>,
69
- +keepRequireNames: boolean,
70
- +disableRequiresTransform?: boolean,
71
- +allowOptionalDependencies: AllowOptionalDependencies,
72
- |};
73
-
74
- export type CollectedDependencies = {|
75
- +ast: Ast,
76
- +dependencyMapName: string,
77
- +dependencies: $ReadOnlyArray<Dependency>,
78
- |};
70
+ };
79
71
 
80
- export type DependencyData = $ReadOnly<InternalDependencyData>;
72
+ export type Options<TSplitCondition = void> = $ReadOnly<{
73
+ asyncRequireModulePath: string,
74
+ dependencyMapName?: string,
75
+ dynamicRequires: DynamicRequiresBehavior,
76
+ inlineableCalls: $ReadOnlyArray<string>,
77
+ keepRequireNames: boolean,
78
+ allowOptionalDependencies: AllowOptionalDependencies,
79
+ dependencyRegistry?: ModuleDependencyRegistry<TSplitCondition>,
80
+ dependencyTransformer?: DependencyTransformer<TSplitCondition>,
81
+ }>;
82
+
83
+ export type CollectedDependencies<+TSplitCondition> = $ReadOnly<{
84
+ ast: BabelNodeFile,
85
+ dependencyMapName: string,
86
+ dependencies: $ReadOnlyArray<Dependency<TSplitCondition>>,
87
+ }>;
88
+
89
+ // Registry for the dependency of a module.
90
+ // Defines when dependencies should be collapsed.
91
+ // E.g. should a module that's once required optinally and once not
92
+ // be tretaed as the smae or different dependencies.
93
+ export interface ModuleDependencyRegistry<+TSplitCondition> {
94
+ registerDependency(
95
+ qualifier: ImportQualifier,
96
+ ): InternalDependency<TSplitCondition>;
97
+ getDependencies(): Array<InternalDependency<TSplitCondition>>;
98
+ }
81
99
 
82
- export type Dependency = InternalDependency<DependencyData>;
100
+ export interface DependencyTransformer<-TSplitCondition> {
101
+ transformSyncRequire(
102
+ path: NodePath<CallExpression>,
103
+ dependency: InternalDependency<TSplitCondition>,
104
+ state: State<TSplitCondition>,
105
+ ): void;
106
+ transformImportCall(
107
+ path: NodePath<>,
108
+ dependency: InternalDependency<TSplitCondition>,
109
+ state: State<TSplitCondition>,
110
+ ): void;
111
+ transformJSResource(
112
+ path: NodePath<>,
113
+ dependency: InternalDependency<TSplitCondition>,
114
+ state: State<TSplitCondition>,
115
+ ): void;
116
+ transformPrefetch(
117
+ path: NodePath<>,
118
+ dependency: InternalDependency<TSplitCondition>,
119
+ state: State<TSplitCondition>,
120
+ ): void;
121
+ transformIllegalDynamicRequire(
122
+ path: NodePath<>,
123
+ state: State<TSplitCondition>,
124
+ ): void;
125
+ }
83
126
 
84
127
  export type DynamicRequiresBehavior = 'throwAtRuntime' | 'reject';
85
128
 
86
- /**
87
- * Produces a Babel template that will throw at runtime when the require call
88
- * is reached. This makes dynamic require errors catchable by libraries that
89
- * want to use them.
90
- */
91
- const dynamicRequireErrorTemplate = template(`
92
- (function(line) {
93
- throw new Error(
94
- 'Dynamic require defined at line ' + line + '; not supported by Metro',
95
- );
96
- })(LINE)
97
- `);
98
-
99
- /**
100
- * Produces a Babel template that transforms an "import(...)" call into a
101
- * "require(...)" call to the asyncRequire specified.
102
- */
103
- const makeAsyncRequireTemplate = template(`
104
- require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, MODULE_NAME)
105
- `);
106
-
107
- const makeAsyncPrefetchTemplate = template(`
108
- require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, MODULE_NAME)
109
- `);
110
-
111
- const makeJSResourceTemplate = template(`
112
- require(ASYNC_REQUIRE_MODULE_PATH).resource(MODULE_ID, MODULE_NAME)
113
- `);
114
-
115
129
  /**
116
130
  * Transform all the calls to `require()` and `import()` in a file into ID-
117
131
  * independent code, and return the list of dependencies. For example, a call
@@ -121,62 +135,78 @@ const makeJSResourceTemplate = template(`
121
135
  *
122
136
  * The second argument is only provided for debugging purposes.
123
137
  */
124
- function collectDependencies(
125
- ast: Ast,
126
- options: Options,
127
- ): CollectedDependencies {
138
+ function collectDependencies<TSplitCondition = void>(
139
+ ast: BabelNodeFile,
140
+ options: Options<TSplitCondition>,
141
+ ): CollectedDependencies<TSplitCondition> {
128
142
  const visited = new WeakSet();
129
143
 
130
- const state: State = {
144
+ const state: State<TSplitCondition> = {
131
145
  asyncRequireModulePathStringLiteral: null,
132
- dependency: 0,
133
146
  dependencyCalls: new Set(),
134
- dependencyData: new Map(),
135
- dependencyIndexes: new Map(),
147
+ dependencyRegistry:
148
+ options.dependencyRegistry ?? new DefaultModuleDependencyRegistry(),
149
+ dependencyTransformer:
150
+ options.dependencyTransformer ?? DefaultDependencyTransformer,
136
151
  dependencyMapIdentifier: null,
137
152
  dynamicRequires: options.dynamicRequires,
138
153
  keepRequireNames: options.keepRequireNames,
139
- disableRequiresTransform: !!options.disableRequiresTransform,
140
154
  allowOptionalDependencies: options.allowOptionalDependencies,
141
155
  };
142
156
 
143
157
  const visitor = {
144
- CallExpression(path: Path, state: State) {
158
+ CallExpression(path, state): void {
145
159
  if (visited.has(path.node)) {
146
160
  return;
147
161
  }
148
162
 
149
- const callee = path.get('callee');
150
- const name = callee.node.name;
163
+ const callee = path.node.callee;
164
+ const name = callee.type === 'Identifier' ? callee.name : null;
151
165
 
152
- if (callee.isImport()) {
166
+ if (isImport(callee)) {
153
167
  processImportCall(path, state, {
154
- prefetchOnly: false,
168
+ asyncType: 'async',
155
169
  });
156
170
  return;
157
171
  }
158
172
 
159
173
  if (name === '__prefetchImport' && !path.scope.getBinding(name)) {
160
174
  processImportCall(path, state, {
161
- prefetchOnly: true,
175
+ asyncType: 'prefetch',
176
+ });
177
+ return;
178
+ }
179
+
180
+ if (name === '__jsResource' && !path.scope.getBinding(name)) {
181
+ processImportCall(path, state, {
182
+ asyncType: 'async',
183
+ jsResource: true,
162
184
  });
163
185
  return;
164
186
  }
165
187
 
166
188
  if (
167
- (name === '__jsResource' ||
168
- name === '__conditionallySplitJSResource') &&
189
+ name === '__conditionallySplitJSResource' &&
169
190
  !path.scope.getBinding(name)
170
191
  ) {
192
+ const args = path.get('arguments');
193
+ invariant(Array.isArray(args), 'Expected arguments to be an array');
194
+
171
195
  processImportCall(path, state, {
172
- prefetchOnly: false,
196
+ asyncType: 'async',
173
197
  jsResource: true,
198
+ splitCondition: args[1],
174
199
  });
175
200
  return;
176
201
  }
177
202
 
178
- if (state.dependencyCalls.has(name) && !path.scope.getBinding(name)) {
179
- visited.add(processRequireCall(path, state).node);
203
+ if (
204
+ name != null &&
205
+ state.dependencyCalls.has(name) &&
206
+ !path.scope.getBinding(name)
207
+ ) {
208
+ processRequireCall(path, state);
209
+ visited.add(path.node);
180
210
  }
181
211
  },
182
212
 
@@ -184,7 +214,7 @@ function collectDependencies(
184
214
  ExportNamedDeclaration: collectImports,
185
215
  ExportAllDeclaration: collectImports,
186
216
 
187
- Program(path: Path, state: State) {
217
+ Program(path, state) {
188
218
  state.asyncRequireModulePathStringLiteral = types.stringLiteral(
189
219
  options.asyncRequireModulePath,
190
220
  );
@@ -205,11 +235,15 @@ function collectDependencies(
205
235
 
206
236
  traverse(ast, visitor, null, state);
207
237
 
238
+ const collectedDependencies = state.dependencyRegistry.getDependencies();
208
239
  // Compute the list of dependencies.
209
- const dependencies = new Array(state.dependency);
240
+ const dependencies = new Array(collectedDependencies.length);
210
241
 
211
- for (const [name, data] of state.dependencyData) {
212
- dependencies[nullthrows(state.dependencyIndexes.get(name))] = {name, data};
242
+ for (const {index, name, ...dependencyData} of collectedDependencies) {
243
+ dependencies[index] = {
244
+ name,
245
+ data: dependencyData,
246
+ };
213
247
  }
214
248
 
215
249
  return {
@@ -219,165 +253,128 @@ function collectDependencies(
219
253
  };
220
254
  }
221
255
 
222
- function collectImports(path: Path, state: State) {
256
+ function collectImports<TSplitCondition>(
257
+ path: NodePath<>,
258
+ state: State<TSplitCondition>,
259
+ ): void {
223
260
  if (path.node.source) {
224
- const dep = getDependency(
261
+ registerDependency(
225
262
  state,
226
- path.node.source.value,
227
263
  {
228
- prefetchOnly: false,
264
+ name: path.node.source.value,
265
+ asyncType: null,
266
+ optional: false,
229
267
  },
230
268
  path,
231
269
  );
232
-
233
- dep.data.asyncType = null;
234
270
  }
235
271
  }
236
272
 
237
- function processImportCall(path: Path, state: State, opts: DepOptions): Path {
273
+ function processImportCall<TSplitCondition>(
274
+ path: NodePath<CallExpression>,
275
+ state: State<TSplitCondition>,
276
+ options: ImportDependencyOptions,
277
+ ): void {
238
278
  const name = getModuleNameFromCallArgs(path);
239
279
 
240
280
  if (name == null) {
241
281
  throw new InvalidRequireCallError(path);
242
282
  }
243
283
 
244
- const options = {
245
- ...opts,
246
- isOptional: isOptionalDependency(name, path, state),
247
- };
248
- const dep = getDependency(state, name, options, path);
249
- if (!options.prefetchOnly && dep.data.asyncType === 'prefetch') {
250
- dep.data.asyncType = 'async';
251
- }
252
- if (state.disableRequiresTransform) {
253
- return path;
254
- }
255
-
256
- const ASYNC_REQUIRE_MODULE_PATH = state.asyncRequireModulePathStringLiteral;
257
- const MODULE_ID = types.memberExpression(
258
- state.dependencyMapIdentifier,
259
- types.numericLiteral(dep.index),
260
- true,
284
+ const dep = registerDependency(
285
+ state,
286
+ {
287
+ name,
288
+ asyncType: options.asyncType,
289
+ splitCondition: options.splitCondition,
290
+ optional: isOptionalDependency(name, path, state),
291
+ },
292
+ path,
261
293
  );
262
- const MODULE_NAME = types.stringLiteral(name);
294
+
295
+ const transformer = state.dependencyTransformer;
263
296
 
264
297
  if (options.jsResource) {
265
- path.replaceWith(
266
- makeJSResourceTemplate({
267
- ASYNC_REQUIRE_MODULE_PATH,
268
- MODULE_ID,
269
- MODULE_NAME,
270
- }),
271
- );
272
- } else if (!options.prefetchOnly) {
273
- path.replaceWith(
274
- makeAsyncRequireTemplate({
275
- ASYNC_REQUIRE_MODULE_PATH,
276
- MODULE_ID,
277
- MODULE_NAME,
278
- }),
279
- );
298
+ transformer.transformJSResource(path, dep, state);
299
+ } else if (options.asyncType === 'async') {
300
+ transformer.transformImportCall(path, dep, state);
280
301
  } else {
281
- path.replaceWith(
282
- makeAsyncPrefetchTemplate({
283
- ASYNC_REQUIRE_MODULE_PATH,
284
- MODULE_ID,
285
- MODULE_NAME,
286
- }),
287
- );
302
+ transformer.transformPrefetch(path, dep, state);
288
303
  }
289
-
290
- return path;
291
304
  }
292
305
 
293
- function processRequireCall(path: Path, state: State): Path {
306
+ function processRequireCall<TSplitCondition>(
307
+ path: NodePath<CallExpression>,
308
+ state: State<TSplitCondition>,
309
+ ): void {
294
310
  const name = getModuleNameFromCallArgs(path);
295
311
 
312
+ const transformer = state.dependencyTransformer;
313
+
296
314
  if (name == null) {
297
315
  if (state.dynamicRequires === 'reject') {
298
316
  throw new InvalidRequireCallError(path);
299
317
  }
300
318
 
301
- path.replaceWith(
302
- dynamicRequireErrorTemplate({
303
- LINE: '' + path.node.loc.start.line,
304
- }),
305
- );
306
- return path;
319
+ transformer.transformIllegalDynamicRequire(path, state);
320
+ return;
307
321
  }
308
322
 
309
- const dep = getDependency(
323
+ const dep = registerDependency(
310
324
  state,
311
- name,
312
- {prefetchOnly: false, isOptional: isOptionalDependency(name, path, state)},
325
+ {
326
+ name,
327
+ asyncType: null,
328
+ optional: isOptionalDependency(name, path, state),
329
+ },
313
330
  path,
314
331
  );
315
- dep.data.asyncType = null;
316
-
317
- if (state.disableRequiresTransform) {
318
- return path;
319
- }
320
-
321
- const moduleIDExpression = types.memberExpression(
322
- state.dependencyMapIdentifier,
323
- types.numericLiteral(dep.index),
324
- true,
325
- );
326
-
327
- path.node.arguments = state.keepRequireNames
328
- ? [moduleIDExpression, types.stringLiteral(name)]
329
- : [moduleIDExpression];
330
332
 
331
- return path;
333
+ transformer.transformSyncRequire(path, dep, state);
332
334
  }
333
335
 
334
- function getNearestLocFromPath(path: Path): ?BabelSourceLocation {
335
- while (path && !path.node.loc) {
336
- path = path.parentPath;
336
+ function getNearestLocFromPath(path: NodePath<>): ?BabelSourceLocation {
337
+ let current = path;
338
+ while (current && !current.node.loc) {
339
+ current = current.parentPath;
337
340
  }
338
- return path?.node.loc;
341
+ return current?.node.loc;
339
342
  }
340
343
 
341
- function getDependency(
342
- state: State,
344
+ export type ImportQualifier = $ReadOnly<{
343
345
  name: string,
344
- options: DepOptions,
345
- path: Path,
346
- ): InternalDependencyInfo {
347
- const loc = getNearestLocFromPath(path);
348
- let index = state.dependencyIndexes.get(name);
349
- let data: ?InternalDependencyData = state.dependencyData.get(name);
350
-
351
- if (!data) {
352
- index = state.dependency++;
353
- data = {asyncType: 'async', locs: []};
354
-
355
- if (options.prefetchOnly) {
356
- data.asyncType = 'prefetch';
357
- }
358
-
359
- if (options.isOptional) {
360
- data.isOptional = true;
361
- }
346
+ asyncType: AsyncDependencyType | null,
347
+ splitCondition?: NodePath<>,
348
+ optional: boolean,
349
+ }>;
362
350
 
363
- state.dependencyIndexes.set(name, index);
364
- state.dependencyData.set(name, data);
365
- }
351
+ function registerDependency<TSplitCondition>(
352
+ state: State<TSplitCondition>,
353
+ qualifier: ImportQualifier,
354
+ path: NodePath<>,
355
+ ): InternalDependency<TSplitCondition> {
356
+ const dependency = state.dependencyRegistry.registerDependency(qualifier);
366
357
 
358
+ const loc = getNearestLocFromPath(path);
367
359
  if (loc != null) {
368
- data.locs.push(loc);
360
+ dependency.locs.push(loc);
369
361
  }
370
362
 
371
- return {index: nullthrows(index), data: nullthrows(data)};
363
+ return dependency;
372
364
  }
373
365
 
374
- const isOptionalDependency = (
366
+ function isOptionalDependency<TSplitCondition>(
375
367
  name: string,
376
- path: Path,
377
- state: State,
378
- ): boolean => {
368
+ path: NodePath<>,
369
+ state: State<TSplitCondition>,
370
+ ): boolean {
379
371
  const {allowOptionalDependencies} = state;
380
372
 
373
+ // The async require module is a 'built-in'. Resolving should never fail -> treat it as non-optional.
374
+ if (name === state.asyncRequireModulePathStringLiteral?.value) {
375
+ return false;
376
+ }
377
+
381
378
  const isExcluded = () =>
382
379
  Array.isArray(allowOptionalDependencies.exclude) &&
383
380
  allowOptionalDependencies.exclude.includes(name);
@@ -394,7 +391,11 @@ const isOptionalDependency = (
394
391
  if (p.node.type === 'BlockStatement') {
395
392
  // A single-level should have the tryStatement immediately followed BlockStatement
396
393
  // with the key 'block' to distinguish from the finally block, which has key = 'finalizer'
397
- return p.parentPath.node.type === 'TryStatement' && p.key === 'block';
394
+ return (
395
+ p.parentPath != null &&
396
+ p.parentPath.node.type === 'TryStatement' &&
397
+ p.key === 'block'
398
+ );
398
399
  }
399
400
  sCount += 1;
400
401
  }
@@ -402,16 +403,17 @@ const isOptionalDependency = (
402
403
  }
403
404
 
404
405
  return false;
405
- };
406
+ }
406
407
 
407
- function getModuleNameFromCallArgs(path: Path): ?string {
408
+ function getModuleNameFromCallArgs(path: NodePath<CallExpression>): ?string {
408
409
  const expectedCount =
409
410
  path.node.callee.name === '__conditionallySplitJSResource' ? 2 : 1;
410
- if (path.get('arguments').length !== expectedCount) {
411
+ const args = path.get('arguments');
412
+ if (!Array.isArray(args) || args.length !== expectedCount) {
411
413
  throw new InvalidRequireCallError(path);
412
414
  }
413
415
 
414
- const result = path.get('arguments.0').evaluate();
416
+ const result = args[0].evaluate();
415
417
 
416
418
  if (result.confident && typeof result.value === 'string') {
417
419
  return result.value;
@@ -433,4 +435,190 @@ class InvalidRequireCallError extends Error {
433
435
 
434
436
  collectDependencies.InvalidRequireCallError = InvalidRequireCallError;
435
437
 
438
+ /**
439
+ * Produces a Babel template that will throw at runtime when the require call
440
+ * is reached. This makes dynamic require errors catchable by libraries that
441
+ * want to use them.
442
+ */
443
+ const dynamicRequireErrorTemplate = template.statement(`
444
+ (function(line) {
445
+ throw new Error(
446
+ 'Dynamic require defined at line ' + line + '; not supported by Metro',
447
+ );
448
+ })(LINE)
449
+ `);
450
+
451
+ /**
452
+ * Produces a Babel template that transforms an "import(...)" call into a
453
+ * "require(...)" call to the asyncRequire specified.
454
+ */
455
+ const makeAsyncRequireTemplate = template.statement(`
456
+ require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, MODULE_NAME)
457
+ `);
458
+
459
+ const makeAsyncPrefetchTemplate = template.statement(`
460
+ require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, MODULE_NAME)
461
+ `);
462
+
463
+ const makeJSResourceTemplate = template.statement(`
464
+ require(ASYNC_REQUIRE_MODULE_PATH).resource(MODULE_ID, MODULE_NAME)
465
+ `);
466
+
467
+ const DefaultDependencyTransformer: DependencyTransformer<mixed> = {
468
+ transformSyncRequire(
469
+ path: NodePath<CallExpression>,
470
+ dependency: InternalDependency<mixed>,
471
+ state: State<mixed>,
472
+ ): void {
473
+ const moduleIDExpression = createModuleIDExpression(dependency, state);
474
+ path.node.arguments = state.keepRequireNames
475
+ ? [moduleIDExpression, types.stringLiteral(dependency.name)]
476
+ : [moduleIDExpression];
477
+ },
478
+
479
+ transformImportCall(
480
+ path: NodePath<>,
481
+ dependency: InternalDependency<mixed>,
482
+ state: State<mixed>,
483
+ ): void {
484
+ path.replaceWith(
485
+ makeAsyncRequireTemplate({
486
+ ASYNC_REQUIRE_MODULE_PATH: nullthrows(
487
+ state.asyncRequireModulePathStringLiteral,
488
+ ),
489
+ MODULE_ID: createModuleIDExpression(dependency, state),
490
+ MODULE_NAME: createModuleNameLiteral(dependency),
491
+ }),
492
+ );
493
+ },
494
+
495
+ transformJSResource(
496
+ path: NodePath<>,
497
+ dependency: InternalDependency<mixed>,
498
+ state: State<mixed>,
499
+ ): void {
500
+ path.replaceWith(
501
+ makeJSResourceTemplate({
502
+ ASYNC_REQUIRE_MODULE_PATH: nullthrows(
503
+ state.asyncRequireModulePathStringLiteral,
504
+ ),
505
+ MODULE_ID: createModuleIDExpression(dependency, state),
506
+ MODULE_NAME: createModuleNameLiteral(dependency),
507
+ }),
508
+ );
509
+ },
510
+
511
+ transformPrefetch(
512
+ path: NodePath<>,
513
+ dependency: InternalDependency<mixed>,
514
+ state: State<mixed>,
515
+ ): void {
516
+ path.replaceWith(
517
+ makeAsyncPrefetchTemplate({
518
+ ASYNC_REQUIRE_MODULE_PATH: nullthrows(
519
+ state.asyncRequireModulePathStringLiteral,
520
+ ),
521
+ MODULE_ID: createModuleIDExpression(dependency, state),
522
+ MODULE_NAME: createModuleNameLiteral(dependency),
523
+ }),
524
+ );
525
+ },
526
+
527
+ transformIllegalDynamicRequire(path: NodePath<>, state: State<mixed>): void {
528
+ path.replaceWith(
529
+ dynamicRequireErrorTemplate({
530
+ LINE: types.numericLiteral(path.node.loc?.start.line ?? 0),
531
+ }),
532
+ );
533
+ },
534
+ };
535
+
536
+ function createModuleIDExpression(
537
+ dependency: InternalDependency<mixed>,
538
+ state: State<mixed>,
539
+ ) {
540
+ return types.memberExpression(
541
+ nullthrows(state.dependencyMapIdentifier),
542
+ types.numericLiteral(dependency.index),
543
+ true,
544
+ );
545
+ }
546
+
547
+ function createModuleNameLiteral(dependency: InternalDependency<mixed>) {
548
+ return types.stringLiteral(dependency.name);
549
+ }
550
+
551
+ class DefaultModuleDependencyRegistry<TSplitCondition = void>
552
+ implements ModuleDependencyRegistry<TSplitCondition> {
553
+ _dependencies: Map<string, InternalDependency<TSplitCondition>> = new Map();
554
+
555
+ registerDependency(
556
+ qualifier: ImportQualifier,
557
+ ): InternalDependency<TSplitCondition> {
558
+ let dependency: ?InternalDependency<TSplitCondition> = this._dependencies.get(
559
+ qualifier.name,
560
+ );
561
+
562
+ if (dependency == null) {
563
+ const newDependency: MutableInternalDependency<TSplitCondition> = {
564
+ name: qualifier.name,
565
+ asyncType: qualifier.asyncType,
566
+ locs: [],
567
+ index: this._dependencies.size,
568
+ };
569
+
570
+ if (qualifier.optional) {
571
+ newDependency.isOptional = true;
572
+ }
573
+
574
+ dependency = newDependency;
575
+ this._dependencies.set(qualifier.name, dependency);
576
+ } else {
577
+ const original = dependency;
578
+ dependency = collapseDependencies(original, qualifier);
579
+ if (original !== dependency) {
580
+ this._dependencies.set(qualifier.name, dependency);
581
+ }
582
+ }
583
+
584
+ return dependency;
585
+ }
586
+
587
+ getDependencies(): Array<InternalDependency<TSplitCondition>> {
588
+ return Array.from(this._dependencies.values());
589
+ }
590
+ }
591
+
592
+ function collapseDependencies<TSplitCondition>(
593
+ dependency: InternalDependency<TSplitCondition>,
594
+ qualifier: ImportQualifier,
595
+ ): InternalDependency<TSplitCondition> {
596
+ let collapsed = dependency;
597
+
598
+ // A previously optionally required dependency was required non-optionaly.
599
+ // Mark it non optional for the whole module
600
+ if (collapsed.isOptional && !qualifier.optional) {
601
+ collapsed = {
602
+ ...dependency,
603
+ isOptional: false,
604
+ };
605
+ }
606
+
607
+ // A previously asynchronously (or prefetch) required module was required synchronously.
608
+ // Make the dependency sync.
609
+ if (collapsed.asyncType != null && qualifier.asyncType == null) {
610
+ collapsed = {...dependency, asyncType: null};
611
+ }
612
+
613
+ // A prefetched dependency was required async in the module. Mark it as async.
614
+ if (collapsed.asyncType === 'prefetch' && qualifier.asyncType === 'async') {
615
+ collapsed = {
616
+ ...dependency,
617
+ asyncType: 'async',
618
+ };
619
+ }
620
+
621
+ return collapsed;
622
+ }
623
+
436
624
  module.exports = collectDependencies;