metro 0.81.2 → 0.81.3

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.81.2",
3
+ "version": "0.81.3",
4
4
  "description": "🚇 The JavaScript bundler for React Native.",
5
5
  "main": "src/index.js",
6
6
  "bin": "src/cli.js",
@@ -42,18 +42,18 @@
42
42
  "jest-worker": "^29.7.0",
43
43
  "jsc-safe-url": "^0.2.2",
44
44
  "lodash.throttle": "^4.1.1",
45
- "metro-babel-transformer": "0.81.2",
46
- "metro-cache": "0.81.2",
47
- "metro-cache-key": "0.81.2",
48
- "metro-config": "0.81.2",
49
- "metro-core": "0.81.2",
50
- "metro-file-map": "0.81.2",
51
- "metro-resolver": "0.81.2",
52
- "metro-runtime": "0.81.2",
53
- "metro-source-map": "0.81.2",
54
- "metro-symbolicate": "0.81.2",
55
- "metro-transform-plugins": "0.81.2",
56
- "metro-transform-worker": "0.81.2",
45
+ "metro-babel-transformer": "0.81.3",
46
+ "metro-cache": "0.81.3",
47
+ "metro-cache-key": "0.81.3",
48
+ "metro-config": "0.81.3",
49
+ "metro-core": "0.81.3",
50
+ "metro-file-map": "0.81.3",
51
+ "metro-resolver": "0.81.3",
52
+ "metro-runtime": "0.81.3",
53
+ "metro-source-map": "0.81.3",
54
+ "metro-symbolicate": "0.81.3",
55
+ "metro-transform-plugins": "0.81.3",
56
+ "metro-transform-worker": "0.81.3",
57
57
  "mime-types": "^2.1.27",
58
58
  "nullthrows": "^1.1.1",
59
59
  "serialize-error": "^2.1.0",
@@ -71,8 +71,8 @@
71
71
  "dedent": "^0.7.0",
72
72
  "jest-snapshot": "^29.7.0",
73
73
  "jest-snapshot-serializer-raw": "^1.2.0",
74
- "metro-babel-register": "0.81.2",
75
- "metro-memory-fs": "0.81.2",
74
+ "metro-babel-register": "0.81.3",
75
+ "metro-memory-fs": "0.81.3",
76
76
  "mock-req": "^0.2.0",
77
77
  "mock-res": "^0.6.0",
78
78
  "stack-trace": "^0.0.10"
@@ -44,6 +44,11 @@ export type TransformResultDependency = $ReadOnly<{
44
44
  * If not null, this dependency is due to a dynamic `import()` or `__prefetchImport()` call.
45
45
  */
46
46
  asyncType: AsyncDependencyType | null,
47
+ /**
48
+ * True if the dependency is declared with a static "import x from 'y'" or
49
+ * an import() call.
50
+ */
51
+ isESMImport: boolean,
47
52
  /**
48
53
  * The dependency is enclosed in a try/catch block.
49
54
  */
package/src/HmrServer.js CHANGED
@@ -54,6 +54,7 @@ class HmrServer {
54
54
  data: {
55
55
  key: entryFile,
56
56
  asyncType: null,
57
+ isESMImport: false,
57
58
  locs: [],
58
59
  },
59
60
  }
@@ -125,6 +125,7 @@ class HmrServer<TClient: Client> {
125
125
  data: {
126
126
  key: entryFile,
127
127
  asyncType: null,
128
+ isESMImport: false,
128
129
  locs: [],
129
130
  },
130
131
  },
@@ -21,6 +21,7 @@ function collectDependencies(ast, options) {
21
21
  keepRequireNames: options.keepRequireNames,
22
22
  allowOptionalDependencies: options.allowOptionalDependencies,
23
23
  unstable_allowRequireContext: options.unstable_allowRequireContext,
24
+ unstable_isESMImportAtSource: options.unstable_isESMImportAtSource ?? null,
24
25
  };
25
26
  const visitor = {
26
27
  CallExpression(path, state) {
@@ -32,12 +33,14 @@ function collectDependencies(ast, options) {
32
33
  if (isImport(callee)) {
33
34
  processImportCall(path, state, {
34
35
  asyncType: "async",
36
+ isESMImport: true,
35
37
  });
36
38
  return;
37
39
  }
38
40
  if (name === "__prefetchImport" && !path.scope.getBinding(name)) {
39
41
  processImportCall(path, state, {
40
42
  asyncType: "prefetch",
43
+ isESMImport: true,
41
44
  });
42
45
  return;
43
46
  }
@@ -79,6 +82,7 @@ function collectDependencies(ast, options) {
79
82
  ) {
80
83
  processImportCall(path, state, {
81
84
  asyncType: "maybeSync",
85
+ isESMImport: true,
82
86
  });
83
87
  visited.add(path.node);
84
88
  return;
@@ -222,6 +226,7 @@ function processRequireContextCall(path, state) {
222
226
  name: directory,
223
227
  contextParams,
224
228
  asyncType: null,
229
+ isESMImport: false,
225
230
  optional: isOptionalDependency(directory, path, state),
226
231
  },
227
232
  path
@@ -239,6 +244,7 @@ function processResolveWeakCall(path, state) {
239
244
  {
240
245
  name,
241
246
  asyncType: "weak",
247
+ isESMImport: false,
242
248
  optional: isOptionalDependency(name, path, state),
243
249
  },
244
250
  path
@@ -261,6 +267,7 @@ See: https://github.com/facebook/metro/pull/1343`
261
267
  {
262
268
  name: path.node.source.value,
263
269
  asyncType: null,
270
+ isESMImport: true,
264
271
  optional: false,
265
272
  },
266
273
  path
@@ -277,6 +284,7 @@ function processImportCall(path, state, options) {
277
284
  {
278
285
  name,
279
286
  asyncType: options.asyncType,
287
+ isESMImport: options.isESMImport,
280
288
  optional: isOptionalDependency(name, path, state),
281
289
  },
282
290
  path
@@ -309,11 +317,20 @@ function processRequireCall(path, state) {
309
317
  transformer.transformIllegalDynamicRequire(path, state);
310
318
  return;
311
319
  }
320
+ let isESMImport = false;
321
+ if (state.unstable_isESMImportAtSource) {
322
+ const isImport = state.unstable_isESMImportAtSource;
323
+ const loc = getNearestLocFromPath(path);
324
+ if (loc) {
325
+ isESMImport = isImport(loc);
326
+ }
327
+ }
312
328
  const dep = registerDependency(
313
329
  state,
314
330
  {
315
331
  name,
316
332
  asyncType: null,
333
+ isESMImport,
317
334
  optional: isOptionalDependency(name, path, state),
318
335
  },
319
336
  path
@@ -502,12 +519,11 @@ function createModuleNameLiteral(dependency) {
502
519
  return types.stringLiteral(dependency.name);
503
520
  }
504
521
  function getKeyForDependency(qualifier) {
505
- let key = qualifier.name;
506
- const { asyncType } = qualifier;
507
- if (asyncType) {
508
- key += ["", asyncType].join("\0");
522
+ const { asyncType, contextParams, isESMImport, name } = qualifier;
523
+ let key = [name, isESMImport ? "import" : "require"].join("\0");
524
+ if (asyncType != null) {
525
+ key += "\0" + asyncType;
509
526
  }
510
- const { contextParams } = qualifier;
511
527
  if (contextParams) {
512
528
  key += [
513
529
  "",
@@ -529,6 +545,7 @@ class DependencyRegistry {
529
545
  const newDependency = {
530
546
  name: qualifier.name,
531
547
  asyncType: qualifier.asyncType,
548
+ isESMImport: qualifier.isESMImport,
532
549
  locs: [],
533
550
  index: this._dependencies.size,
534
551
  key: crypto.createHash("sha1").update(key).digest("base64"),
@@ -29,6 +29,7 @@ const {isImport} = types;
29
29
 
30
30
  type ImportDependencyOptions = $ReadOnly<{
31
31
  asyncType: AsyncDependencyType,
32
+ isESMImport: boolean,
32
33
  }>;
33
34
 
34
35
  export type Dependency = $ReadOnly<{
@@ -56,6 +57,11 @@ type DependencyData = $ReadOnly<{
56
57
  // If null, then the dependency is synchronous.
57
58
  // (ex. `require('foo')`)
58
59
  asyncType: AsyncDependencyType | null,
60
+ // If true, the dependency is declared using an ESM import, e.g.
61
+ // "import x from 'y'" or "await import('z')". A resolver should typically
62
+ // use this to assert either "import" or "require" for conditional exports
63
+ // and subpath imports.
64
+ isESMImport: boolean,
59
65
  isOptional?: boolean,
60
66
  locs: $ReadOnlyArray<BabelSourceLocation>,
61
67
  /** Context for requiring a collection of modules. */
@@ -82,6 +88,7 @@ export type State = {
82
88
  allowOptionalDependencies: AllowOptionalDependencies,
83
89
  /** Enable `require.context` statements which can be used to import multiple files in a directory. */
84
90
  unstable_allowRequireContext: boolean,
91
+ unstable_isESMImportAtSource: ?(BabelSourceLocation) => boolean,
85
92
  };
86
93
 
87
94
  export type Options = $ReadOnly<{
@@ -94,6 +101,7 @@ export type Options = $ReadOnly<{
94
101
  dependencyTransformer?: DependencyTransformer,
95
102
  /** Enable `require.context` statements which can be used to import multiple files in a directory. */
96
103
  unstable_allowRequireContext: boolean,
104
+ unstable_isESMImportAtSource?: ?(BabelSourceLocation) => boolean,
97
105
  }>;
98
106
 
99
107
  export type CollectedDependencies = $ReadOnly<{
@@ -154,6 +162,7 @@ function collectDependencies(
154
162
  keepRequireNames: options.keepRequireNames,
155
163
  allowOptionalDependencies: options.allowOptionalDependencies,
156
164
  unstable_allowRequireContext: options.unstable_allowRequireContext,
165
+ unstable_isESMImportAtSource: options.unstable_isESMImportAtSource ?? null,
157
166
  };
158
167
 
159
168
  const visitor = {
@@ -171,6 +180,7 @@ function collectDependencies(
171
180
  if (isImport(callee)) {
172
181
  processImportCall(path, state, {
173
182
  asyncType: 'async',
183
+ isESMImport: true,
174
184
  });
175
185
  return;
176
186
  }
@@ -178,6 +188,7 @@ function collectDependencies(
178
188
  if (name === '__prefetchImport' && !path.scope.getBinding(name)) {
179
189
  processImportCall(path, state, {
180
190
  asyncType: 'prefetch',
191
+ isESMImport: true,
181
192
  });
182
193
  return;
183
194
  }
@@ -235,6 +246,10 @@ function collectDependencies(
235
246
  ) {
236
247
  processImportCall(path, state, {
237
248
  asyncType: 'maybeSync',
249
+ // Treat require.unstable_importMaybeSync as an ESM import, like its
250
+ // async "await import()" counterpart. Subject to change while
251
+ // unstable_.
252
+ isESMImport: true,
238
253
  });
239
254
  visited.add(path.node);
240
255
  return;
@@ -408,6 +423,7 @@ function processRequireContextCall(
408
423
  // Capture the matching context
409
424
  contextParams,
410
425
  asyncType: null,
426
+ isESMImport: false,
411
427
  optional: isOptionalDependency(directory, path, state),
412
428
  },
413
429
  path,
@@ -433,6 +449,7 @@ function processResolveWeakCall(
433
449
  {
434
450
  name,
435
451
  asyncType: 'weak',
452
+ isESMImport: false,
436
453
  optional: isOptionalDependency(name, path, state),
437
454
  },
438
455
  path,
@@ -458,6 +475,7 @@ See: https://github.com/facebook/metro/pull/1343`,
458
475
  {
459
476
  name: path.node.source.value,
460
477
  asyncType: null,
478
+ isESMImport: true,
461
479
  optional: false,
462
480
  },
463
481
  path,
@@ -481,6 +499,7 @@ function processImportCall(
481
499
  {
482
500
  name,
483
501
  asyncType: options.asyncType,
502
+ isESMImport: options.isESMImport,
484
503
  optional: isOptionalDependency(name, path, state),
485
504
  },
486
505
  path,
@@ -523,11 +542,21 @@ function processRequireCall(
523
542
  return;
524
543
  }
525
544
 
545
+ let isESMImport = false;
546
+ if (state.unstable_isESMImportAtSource) {
547
+ const isImport = state.unstable_isESMImportAtSource;
548
+ const loc = getNearestLocFromPath(path);
549
+ if (loc) {
550
+ isESMImport = isImport(loc);
551
+ }
552
+ }
553
+
526
554
  const dep = registerDependency(
527
555
  state,
528
556
  {
529
557
  name,
530
558
  asyncType: null,
559
+ isESMImport,
531
560
  optional: isOptionalDependency(name, path, state),
532
561
  },
533
562
  path,
@@ -555,6 +584,7 @@ function getNearestLocFromPath(path: NodePath<>): ?BabelSourceLocation {
555
584
  export type ImportQualifier = $ReadOnly<{
556
585
  name: string,
557
586
  asyncType: AsyncDependencyType | null,
587
+ isESMImport: boolean,
558
588
  optional: boolean,
559
589
  contextParams?: RequireContextParams,
560
590
  }>;
@@ -801,13 +831,16 @@ function createModuleNameLiteral(dependency: InternalDependency) {
801
831
 
802
832
  /**
803
833
  * Given an import qualifier, return a key used to register the dependency.
804
- * Generally this return the `ImportQualifier.name` property, but more
805
- * attributes can be appended to distinguish various combinations that would
806
- * otherwise conflict.
834
+ * Attributes can be appended to distinguish various combinations that would
835
+ * otherwise be considered the same dependency edge.
836
+ *
837
+ * For example, the following dependencies would collapse into a single edge
838
+ * if they simply utilized the `name` property:
807
839
  *
808
- * For example, the following case would have collision issues if they all utilized the `name` property:
809
840
  * ```
810
841
  * require('./foo');
842
+ * import foo from './foo'
843
+ * await import('./foo')
811
844
  * require.context('./foo');
812
845
  * require.context('./foo', true, /something/);
813
846
  * require.context('./foo', false, /something/);
@@ -817,14 +850,13 @@ function createModuleNameLiteral(dependency: InternalDependency) {
817
850
  * This method should be utilized by `registerDependency`.
818
851
  */
819
852
  function getKeyForDependency(qualifier: ImportQualifier): string {
820
- let key = qualifier.name;
853
+ const {asyncType, contextParams, isESMImport, name} = qualifier;
821
854
 
822
- const {asyncType} = qualifier;
823
- if (asyncType) {
824
- key += ['', asyncType].join('\0');
855
+ let key = [name, isESMImport ? 'import' : 'require'].join('\0');
856
+ if (asyncType != null) {
857
+ key += '\0' + asyncType;
825
858
  }
826
859
 
827
- const {contextParams} = qualifier;
828
860
  // Add extra qualifiers when using `require.context` to prevent collisions.
829
861
  if (contextParams) {
830
862
  // NOTE(EvanBacon): Keep this synchronized with `RequireContextParams`, if any other properties are added
@@ -854,6 +886,7 @@ class DependencyRegistry {
854
886
  const newDependency: MutableInternalDependency = {
855
887
  name: qualifier.name,
856
888
  asyncType: qualifier.asyncType,
889
+ isESMImport: qualifier.isESMImport,
857
890
  locs: [],
858
891
  index: this._dependencies.size,
859
892
  key: crypto.createHash('sha1').update(key).digest('base64'),
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ const invariant = require("invariant");
4
+ function importLocationsPlugin({ types: t }) {
5
+ const importDeclarationLocs = new Set();
6
+ return {
7
+ visitor: {
8
+ ImportDeclaration(path) {
9
+ if (path.node.importKind !== "type" && path.node.loc != null) {
10
+ importDeclarationLocs.add(locToKey(path.node.loc));
11
+ }
12
+ },
13
+ ExportDeclaration(path) {
14
+ if (
15
+ path.node.source != null &&
16
+ path.node.exportKind !== "type" &&
17
+ path.node.loc != null
18
+ ) {
19
+ importDeclarationLocs.add(locToKey(path.node.loc));
20
+ }
21
+ },
22
+ },
23
+ pre: ({ path, metadata }) => {
24
+ invariant(
25
+ path && t.isProgram(path.node),
26
+ "path missing or not a program node"
27
+ );
28
+ const metroMetadata = metadata;
29
+ if (!metroMetadata.metro) {
30
+ metroMetadata.metro = {
31
+ unstable_importDeclarationLocs: importDeclarationLocs,
32
+ };
33
+ } else {
34
+ metroMetadata.metro.unstable_importDeclarationLocs =
35
+ importDeclarationLocs;
36
+ }
37
+ },
38
+ };
39
+ }
40
+ const MISSING_LOC = {
41
+ line: -1,
42
+ column: -1,
43
+ };
44
+ function locToKey(loc) {
45
+ const { start = MISSING_LOC, end = MISSING_LOC } = loc;
46
+ return `${start.line},${start.column}:${end.line},${end.column}`;
47
+ }
48
+ module.exports = {
49
+ importLocationsPlugin,
50
+ locToKey,
51
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ * @oncall react_native
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ import type {PluginObj} from '@babel/core';
15
+ import typeof * as Types from '@babel/types';
16
+ import type {MetroBabelFileMetadata} from 'metro-babel-transformer';
17
+
18
+ const invariant = require('invariant');
19
+
20
+ type ImportDeclarationLocs = Set<string>;
21
+
22
+ function importLocationsPlugin({types: t}: {types: Types, ...}): PluginObj<> {
23
+ const importDeclarationLocs: ImportDeclarationLocs = new Set();
24
+ return {
25
+ visitor: {
26
+ ImportDeclaration(path) {
27
+ if (
28
+ // Ignore type imports
29
+ path.node.importKind !== 'type' &&
30
+ // loc may not be set if this plugin runs alongside others which
31
+ // inject imports - eg Babel runtime helpers. We don't regard these
32
+ // as source import declarations.
33
+ path.node.loc != null
34
+ ) {
35
+ importDeclarationLocs.add(locToKey(path.node.loc));
36
+ }
37
+ },
38
+ ExportDeclaration(path) {
39
+ if (
40
+ // If `source` is set, this is a re-export, so it declares an ESM
41
+ // dependency.
42
+ path.node.source != null &&
43
+ // Ignore type exports
44
+ path.node.exportKind !== 'type' &&
45
+ // As above, ignore injected imports.
46
+ path.node.loc != null
47
+ ) {
48
+ importDeclarationLocs.add(locToKey(path.node.loc));
49
+ }
50
+ },
51
+ },
52
+ pre: ({path, metadata}) => {
53
+ invariant(
54
+ path && t.isProgram(path.node),
55
+ 'path missing or not a program node',
56
+ );
57
+
58
+ // $FlowFixMe[prop-missing] Babel `File` is not generically typed
59
+ const metroMetadata: MetroBabelFileMetadata = metadata;
60
+
61
+ // Set the result on a metadata property
62
+ if (!metroMetadata.metro) {
63
+ metroMetadata.metro = {
64
+ unstable_importDeclarationLocs: importDeclarationLocs,
65
+ };
66
+ } else {
67
+ metroMetadata.metro.unstable_importDeclarationLocs =
68
+ importDeclarationLocs;
69
+ }
70
+ },
71
+ };
72
+ }
73
+
74
+ // Very simple serialisation of a source location. This should remain opaque to
75
+ // the caller.
76
+ const MISSING_LOC = {line: -1, column: -1};
77
+ function locToKey(loc: BabelSourceLocation): string {
78
+ const {start = MISSING_LOC, end = MISSING_LOC} = loc;
79
+ return `${start.line},${start.column}:${end.line},${end.column}`;
80
+ }
81
+
82
+ module.exports = {importLocationsPlugin, locToKey};
package/src/Server.js CHANGED
@@ -1107,6 +1107,7 @@ class Server {
1107
1107
  key: filePath,
1108
1108
  locs: [],
1109
1109
  asyncType: null,
1110
+ isESMImport: false,
1110
1111
  },
1111
1112
  }).filePath;
1112
1113
  }
@@ -1420,7 +1420,7 @@ class Server {
1420
1420
  : this._config.projectRoot;
1421
1421
  return resolutionFn(`${rootDir}/.`, {
1422
1422
  name: filePath,
1423
- data: {key: filePath, locs: [], asyncType: null},
1423
+ data: {key: filePath, locs: [], asyncType: null, isESMImport: false},
1424
1424
  }).filePath;
1425
1425
  }
1426
1426
 
@@ -33,6 +33,7 @@ class ModuleResolver {
33
33
  data: {
34
34
  key: this._options.emptyModulePath,
35
35
  asyncType: null,
36
+ isESMImport: false,
36
37
  locs: [],
37
38
  },
38
39
  },
@@ -80,6 +81,7 @@ class ModuleResolver {
80
81
  doesFileExist,
81
82
  extraNodeModules,
82
83
  fileSystemLookup,
84
+ isESMImport: dependency.data.isESMImport,
83
85
  mainFields,
84
86
  nodeModulesPaths,
85
87
  preferNativePlatform,
@@ -118,6 +118,7 @@ class ModuleResolver<TPackage: Packageish> {
118
118
  data: {
119
119
  key: this._options.emptyModulePath,
120
120
  asyncType: null,
121
+ isESMImport: false,
121
122
  locs: [],
122
123
  },
123
124
  },
@@ -165,6 +166,7 @@ class ModuleResolver<TPackage: Packageish> {
165
166
  doesFileExist,
166
167
  extraNodeModules,
167
168
  fileSystemLookup,
169
+ isESMImport: dependency.data.isESMImport,
168
170
  mainFields,
169
171
  nodeModulesPaths,
170
172
  preferNativePlatform,
@@ -232,7 +232,8 @@ class DependencyGraph extends EventEmitter {
232
232
  const resolverOptionsKey =
233
233
  JSON.stringify(resolverOptions ?? {}, canonicalize) ?? "";
234
234
  const originKey = isSensitiveToOriginFolder ? path.dirname(from) : "";
235
- const targetKey = to;
235
+ const targetKey =
236
+ to + (dependency.data.isESMImport === true ? "\0esm" : "\0cjs");
236
237
  const platformKey = platform ?? NULL_PLATFORM;
237
238
  const mapByResolverOptions = this._resolutionCache;
238
239
  const mapByOrigin = getOrCreateMap(
@@ -339,7 +339,8 @@ class DependencyGraph extends EventEmitter {
339
339
  const resolverOptionsKey =
340
340
  JSON.stringify(resolverOptions ?? {}, canonicalize) ?? '';
341
341
  const originKey = isSensitiveToOriginFolder ? path.dirname(from) : '';
342
- const targetKey = to;
342
+ const targetKey =
343
+ to + (dependency.data.isESMImport === true ? '\0esm' : '\0cjs');
343
344
  const platformKey = platform ?? NULL_PLATFORM;
344
345
 
345
346
  // Traverse the resolver cache, which is a tree of maps