metro 0.82.1 → 0.82.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.
Files changed (37) hide show
  1. package/package.json +17 -16
  2. package/src/Bundler.js.flow +1 -1
  3. package/src/DeltaBundler/Serializers/getRamBundleInfo.js.flow +1 -1
  4. package/src/DeltaBundler/Transformer.js.flow +1 -1
  5. package/src/DeltaBundler/Worker.flow.js +3 -0
  6. package/src/DeltaBundler/Worker.flow.js.flow +3 -0
  7. package/src/DeltaBundler/WorkerFarm.js +1 -0
  8. package/src/DeltaBundler/WorkerFarm.js.flow +8 -1
  9. package/src/DeltaBundler/types.flow.js.flow +2 -6
  10. package/src/HmrServer.js +1 -1
  11. package/src/HmrServer.js.flow +1 -1
  12. package/src/IncrementalBundler.js.flow +1 -1
  13. package/src/ModuleGraph/worker/collectDependencies.js +4 -0
  14. package/src/ModuleGraph/worker/collectDependencies.js.flow +6 -0
  15. package/src/ModuleGraph/worker/importLocationsPlugin.js +14 -19
  16. package/src/ModuleGraph/worker/importLocationsPlugin.js.flow +30 -24
  17. package/src/Server/symbolicate.js.flow +1 -1
  18. package/src/Server.js +22 -6
  19. package/src/Server.js.flow +37 -11
  20. package/src/index.flow.js +13 -1
  21. package/src/index.flow.js.flow +28 -7
  22. package/src/lib/JsonReporter.js.flow +1 -0
  23. package/src/lib/TerminalReporter.js.flow +1 -0
  24. package/src/lib/formatBundlingError.js +11 -8
  25. package/src/lib/formatBundlingError.js.flow +12 -9
  26. package/src/lib/parseJsonBody.js +35 -0
  27. package/src/lib/parseJsonBody.js.flow +60 -0
  28. package/src/lib/parseOptionsFromUrl.js.flow +5 -1
  29. package/src/lib/transformHelpers.js.flow +1 -1
  30. package/src/node-haste/DependencyGraph/createFileMap.js.flow +1 -1
  31. package/src/node-haste/DependencyGraph.js.flow +1 -1
  32. package/src/shared/output/RamBundle.js +0 -1
  33. package/src/shared/output/RamBundle.js.flow +0 -1
  34. package/src/shared/output/bundle.flow.js +0 -1
  35. package/src/shared/output/bundle.flow.js.flow +0 -1
  36. package/src/shared/types.d.ts +0 -12
  37. package/src/shared/types.flow.js.flow +1 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metro",
3
- "version": "0.82.1",
3
+ "version": "0.82.3",
4
4
  "description": "🚇 The JavaScript bundler for React Native.",
5
5
  "main": "src/index.js",
6
6
  "bin": "src/cli.js",
@@ -36,24 +36,24 @@
36
36
  "error-stack-parser": "^2.0.6",
37
37
  "flow-enums-runtime": "^0.0.6",
38
38
  "graceful-fs": "^4.2.4",
39
- "hermes-parser": "0.25.1",
39
+ "hermes-parser": "0.28.1",
40
40
  "image-size": "^1.0.2",
41
41
  "invariant": "^2.2.4",
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.82.1",
46
- "metro-cache": "0.82.1",
47
- "metro-cache-key": "0.82.1",
48
- "metro-config": "0.82.1",
49
- "metro-core": "0.82.1",
50
- "metro-file-map": "0.82.1",
51
- "metro-resolver": "0.82.1",
52
- "metro-runtime": "0.82.1",
53
- "metro-source-map": "0.82.1",
54
- "metro-symbolicate": "0.82.1",
55
- "metro-transform-plugins": "0.82.1",
56
- "metro-transform-worker": "0.82.1",
45
+ "metro-babel-transformer": "0.82.3",
46
+ "metro-cache": "0.82.3",
47
+ "metro-cache-key": "0.82.3",
48
+ "metro-config": "0.82.3",
49
+ "metro-core": "0.82.3",
50
+ "metro-file-map": "0.82.3",
51
+ "metro-resolver": "0.82.3",
52
+ "metro-runtime": "0.82.3",
53
+ "metro-source-map": "0.82.3",
54
+ "metro-symbolicate": "0.82.3",
55
+ "metro-transform-plugins": "0.82.3",
56
+ "metro-transform-worker": "0.82.3",
57
57
  "mime-types": "^2.1.27",
58
58
  "nullthrows": "^1.1.1",
59
59
  "serialize-error": "^2.1.0",
@@ -65,14 +65,15 @@
65
65
  "devDependencies": {
66
66
  "@babel/plugin-transform-flow-strip-types": "^7.25.2",
67
67
  "@babel/plugin-transform-modules-commonjs": "^7.24.8",
68
+ "@babel/plugin-transform-runtime": "^7.24.7",
68
69
  "@react-native/babel-preset": "0.78.0",
69
70
  "@react-native/metro-babel-transformer": "0.78.0",
70
71
  "babel-jest": "^29.7.0",
71
72
  "dedent": "^0.7.0",
72
73
  "jest-snapshot": "^29.7.0",
73
74
  "jest-snapshot-serializer-raw": "^1.2.0",
74
- "metro-babel-register": "0.82.1",
75
- "metro-memory-fs": "0.82.1",
75
+ "metro-babel-register": "0.82.3",
76
+ "metro-memory-fs": "0.82.3",
76
77
  "mock-req": "^0.2.0",
77
78
  "mock-res": "^0.6.0",
78
79
  "stack-trace": "^0.0.10"
@@ -14,7 +14,7 @@
14
14
  import type {TransformResultWithSource} from './DeltaBundler';
15
15
  import type {TransformOptions} from './DeltaBundler/Worker';
16
16
  import type EventEmitter from 'events';
17
- import type {ConfigT} from 'metro-config/src/configTypes.flow';
17
+ import type {ConfigT} from 'metro-config';
18
18
 
19
19
  const Transformer = require('./DeltaBundler/Transformer');
20
20
  const DependencyGraph = require('./node-haste/DependencyGraph');
@@ -17,7 +17,7 @@ import type {
17
17
  } from '../../shared/types.flow';
18
18
  import type {Module, ReadOnlyGraph, SerializerOptions} from '../types.flow';
19
19
  import type {SourceMapGeneratorOptions} from './sourceMapGenerator';
20
- import type {GetTransformOptions} from 'metro-config/src/configTypes.flow.js';
20
+ import type {GetTransformOptions} from 'metro-config';
21
21
 
22
22
  const {createRamBundleGroups} = require('../../Bundler/util');
23
23
  const getAppendScripts = require('../../lib/getAppendScripts');
@@ -13,7 +13,7 @@
13
13
 
14
14
  import type {TransformResult, TransformResultWithSource} from '../DeltaBundler';
15
15
  import type {TransformerConfig, TransformOptions} from './Worker';
16
- import type {ConfigT} from 'metro-config/src/configTypes.flow';
16
+ import type {ConfigT} from 'metro-config';
17
17
 
18
18
  import crypto from 'crypto';
19
19
 
@@ -11,6 +11,9 @@ function asDeserializedBuffer(value) {
11
11
  if (value && value.type === "Buffer") {
12
12
  return Buffer.from(value.data);
13
13
  }
14
+ if (ArrayBuffer.isView(value)) {
15
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
16
+ }
14
17
  return null;
15
18
  }
16
19
  async function transform(
@@ -66,6 +66,9 @@ function asDeserializedBuffer(value: any): Buffer | null {
66
66
  if (value && value.type === 'Buffer') {
67
67
  return Buffer.from(value.data);
68
68
  }
69
+ if (ArrayBuffer.isView(value)) {
70
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
71
+ }
69
72
  return null;
70
73
  }
71
74
 
@@ -73,6 +73,7 @@ class WorkerFarm {
73
73
  env,
74
74
  },
75
75
  numWorkers,
76
+ workerSchedulingPolicy: "in-order",
76
77
  });
77
78
  }
78
79
  _computeWorkerKey(method, filename) {
@@ -13,7 +13,7 @@
13
13
 
14
14
  import type {TransformResult} from '../DeltaBundler';
15
15
  import type {TransformerConfig, TransformOptions, Worker} from './Worker';
16
- import type {ConfigT} from 'metro-config/src/configTypes.flow';
16
+ import type {ConfigT} from 'metro-config';
17
17
  import type {Readable} from 'stream';
18
18
 
19
19
  const {Worker: JestWorker} = require('jest-worker');
@@ -128,6 +128,12 @@ class WorkerFarm {
128
128
  enableWorkerThreads: this._config.transformer.unstable_workerThreads,
129
129
  forkOptions: {env},
130
130
  numWorkers,
131
+ // Prefer using lower numbered available workers repeatedly rather than
132
+ // spreading jobs over the whole pool evenly (round-robin). This is more
133
+ // likely to use faster, hot workers earlier in graph traversal, when
134
+ // we want to be able to fan out as quickly as possible, and therefore
135
+ // gets us to full speed faster.
136
+ workerSchedulingPolicy: 'in-order',
131
137
  });
132
138
  }
133
139
 
@@ -145,6 +151,7 @@ class WorkerFarm {
145
151
  _formatGenericError(err: any, filename: string): TransformError {
146
152
  const error = new TransformError(`${filename}: ${err.message}`);
147
153
 
154
+ // $FlowFixMe[unsafe-object-assign]
148
155
  return Object.assign(error, {
149
156
  stack: (err.stack || '').split('\n').slice(0, -1).join('\n'),
150
157
  lineNumber: 0,
@@ -89,13 +89,9 @@ export type ReadOnlyDependencies<T = MixedOutput> = $ReadOnlyMap<
89
89
  Module<T>,
90
90
  >;
91
91
 
92
- export type TransformInputOptions = $Diff<
92
+ export type TransformInputOptions = Omit<
93
93
  JsTransformOptions,
94
- {
95
- inlinePlatform: boolean,
96
- inlineRequires: boolean,
97
- ...
98
- },
94
+ 'inlinePlatform' | 'inlineRequires',
99
95
  >;
100
96
 
101
97
  export type GraphInputOptions = $ReadOnly<{
package/src/HmrServer.js CHANGED
@@ -35,7 +35,7 @@ class HmrServer {
35
35
  async _registerEntryPoint(client, requestUrl, sendFn) {
36
36
  requestUrl = this._config.server.rewriteRequestUrl(requestUrl);
37
37
  const clientUrl = nullthrows(url.parse(requestUrl, true));
38
- const options = parseOptionsFromUrl(
38
+ const { bundleType: _bundleType, ...options } = parseOptionsFromUrl(
39
39
  requestUrl,
40
40
  new Set(this._config.resolver.platforms)
41
41
  );
@@ -101,7 +101,7 @@ class HmrServer<TClient: Client> {
101
101
  ): Promise<void> {
102
102
  requestUrl = this._config.server.rewriteRequestUrl(requestUrl);
103
103
  const clientUrl = nullthrows(url.parse(requestUrl, true));
104
- const options = parseOptionsFromUrl(
104
+ const {bundleType: _bundleType, ...options} = parseOptionsFromUrl(
105
105
  requestUrl,
106
106
  new Set(this._config.resolver.platforms),
107
107
  );
@@ -19,7 +19,7 @@ import type {
19
19
  } from './DeltaBundler/types.flow';
20
20
  import type {GraphId} from './lib/getGraphId';
21
21
  import type {ResolverInputOptions} from './shared/types.flow';
22
- import type {ConfigT} from 'metro-config/src/configTypes.flow';
22
+ import type {ConfigT} from 'metro-config';
23
23
 
24
24
  const Bundler = require('./Bundler');
25
25
  const DeltaBundler = require('./DeltaBundler');
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ var _types = require("@babel/types");
3
4
  const generate = require("@babel/generator").default;
4
5
  const template = require("@babel/template").default;
5
6
  const traverse = require("@babel/traverse").default;
@@ -346,6 +347,9 @@ function getNearestLocFromPath(path) {
346
347
  ) {
347
348
  current = current.parentPath;
348
349
  }
350
+ if (current && (0, _types.isProgram)(current.node)) {
351
+ current = null;
352
+ }
349
353
  return current?.node.METRO_INLINE_REQUIRES_INIT_LOC ?? current?.node.loc;
350
354
  }
351
355
  function registerDependency(state, qualifier, path) {
@@ -17,6 +17,8 @@ import type {
17
17
  AsyncDependencyType,
18
18
  } from 'metro/src/DeltaBundler/types.flow.js';
19
19
 
20
+ import {isProgram} from '@babel/types';
21
+
20
22
  const generate = require('@babel/generator').default;
21
23
  const template = require('@babel/template').default;
22
24
  const traverse = require('@babel/traverse').default;
@@ -575,6 +577,10 @@ function getNearestLocFromPath(path: NodePath<>): ?BabelSourceLocation {
575
577
  ) {
576
578
  current = current.parentPath;
577
579
  }
580
+ // Do not use the location of the `Program` node
581
+ if (current && isProgram(current.node)) {
582
+ current = null;
583
+ }
578
584
  return (
579
585
  // $FlowIgnore[prop-missing] METRO_INLINE_REQUIRES_INIT_LOC is Metro-specific and not typed
580
586
  current?.node.METRO_INLINE_REQUIRES_INIT_LOC ?? current?.node.loc
@@ -1,16 +1,14 @@
1
1
  "use strict";
2
2
 
3
- const invariant = require("invariant");
4
3
  function importLocationsPlugin({ types: t }) {
5
- const importDeclarationLocs = new Set();
6
4
  return {
7
5
  visitor: {
8
- ImportDeclaration(path) {
6
+ ImportDeclaration(path, { importDeclarationLocs }) {
9
7
  if (path.node.importKind !== "type" && path.node.loc != null) {
10
8
  importDeclarationLocs.add(locToKey(path.node.loc));
11
9
  }
12
10
  },
13
- ExportDeclaration(path) {
11
+ ExportDeclaration(path, { importDeclarationLocs }) {
14
12
  if (
15
13
  path.node.source != null &&
16
14
  path.node.exportKind !== "type" &&
@@ -19,21 +17,18 @@ function importLocationsPlugin({ types: t }) {
19
17
  importDeclarationLocs.add(locToKey(path.node.loc));
20
18
  }
21
19
  },
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
- }
20
+ Program(path, state) {
21
+ state.importDeclarationLocs = new Set();
22
+ const metroMetadata = state.file.metadata;
23
+ if (!metroMetadata.metro) {
24
+ metroMetadata.metro = {
25
+ unstable_importDeclarationLocs: state.importDeclarationLocs,
26
+ };
27
+ } else {
28
+ metroMetadata.metro.unstable_importDeclarationLocs =
29
+ state.importDeclarationLocs;
30
+ }
31
+ },
37
32
  },
38
33
  };
39
34
  }
@@ -11,19 +11,27 @@
11
11
 
12
12
  'use strict';
13
13
 
14
- import type {PluginObj} from '@babel/core';
14
+ import type {File, PluginObj} from '@babel/core';
15
15
  import typeof * as Types from '@babel/types';
16
16
  import type {MetroBabelFileMetadata} from 'metro-babel-transformer';
17
17
 
18
- const invariant = require('invariant');
19
-
20
18
  type ImportDeclarationLocs = Set<string>;
21
19
 
22
- function importLocationsPlugin({types: t}: {types: Types, ...}): PluginObj<> {
23
- const importDeclarationLocs: ImportDeclarationLocs = new Set();
20
+ type State = {
21
+ importDeclarationLocs: ImportDeclarationLocs,
22
+ file: File,
23
+ ...
24
+ };
25
+
26
+ function importLocationsPlugin({
27
+ types: t,
28
+ }: {
29
+ types: Types,
30
+ ...
31
+ }): PluginObj<State> {
24
32
  return {
25
33
  visitor: {
26
- ImportDeclaration(path) {
34
+ ImportDeclaration(path, {importDeclarationLocs}) {
27
35
  if (
28
36
  // Ignore type imports
29
37
  path.node.importKind !== 'type' &&
@@ -35,7 +43,7 @@ function importLocationsPlugin({types: t}: {types: Types, ...}): PluginObj<> {
35
43
  importDeclarationLocs.add(locToKey(path.node.loc));
36
44
  }
37
45
  },
38
- ExportDeclaration(path) {
46
+ ExportDeclaration(path, {importDeclarationLocs}) {
39
47
  if (
40
48
  // If `source` is set, this is a re-export, so it declares an ESM
41
49
  // dependency.
@@ -48,25 +56,23 @@ function importLocationsPlugin({types: t}: {types: Types, ...}): PluginObj<> {
48
56
  importDeclarationLocs.add(locToKey(path.node.loc));
49
57
  }
50
58
  },
51
- },
52
- pre: ({path, metadata}) => {
53
- invariant(
54
- path && t.isProgram(path.node),
55
- 'path missing or not a program node',
56
- );
59
+ Program(path, state) {
60
+ // Initialise state
61
+ state.importDeclarationLocs = new Set();
57
62
 
58
- // $FlowFixMe[prop-missing] Babel `File` is not generically typed
59
- const metroMetadata: MetroBabelFileMetadata = metadata;
63
+ // $FlowFixMe[prop-missing] Babel `File` is not generically typed
64
+ const metroMetadata: MetroBabelFileMetadata = state.file.metadata;
60
65
 
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
- }
66
+ // Set the result on a metadata property
67
+ if (!metroMetadata.metro) {
68
+ metroMetadata.metro = {
69
+ unstable_importDeclarationLocs: state.importDeclarationLocs,
70
+ };
71
+ } else {
72
+ metroMetadata.metro.unstable_importDeclarationLocs =
73
+ state.importDeclarationLocs;
74
+ }
75
+ },
70
76
  },
71
77
  };
72
78
  }
@@ -15,7 +15,7 @@ import type {
15
15
  MetroSourceMapSegmentTuple,
16
16
  } from '../../../metro-source-map/src/source-map';
17
17
  import type {ExplodedSourceMap} from '../DeltaBundler/Serializers/getExplodedSourceMap';
18
- import type {ConfigT} from 'metro-config/src/configTypes.flow';
18
+ import type {ConfigT} from 'metro-config';
19
19
 
20
20
  const {greatestLowerBound} = require('metro-source-map/src/Consumer/search');
21
21
  const {
package/src/Server.js CHANGED
@@ -17,6 +17,7 @@ const ResourceNotFoundError = require("./IncrementalBundler/ResourceNotFoundErro
17
17
  const bundleToString = require("./lib/bundleToString");
18
18
  const formatBundlingError = require("./lib/formatBundlingError");
19
19
  const getGraphId = require("./lib/getGraphId");
20
+ const parseJsonBody = require("./lib/parseJsonBody");
20
21
  const parseOptionsFromUrl = require("./lib/parseOptionsFromUrl");
21
22
  const splitBundleOptions = require("./lib/splitBundleOptions");
22
23
  const transformHelpers = require("./lib/transformHelpers");
@@ -268,7 +269,6 @@ class Server {
268
269
  splitBundleOptions({
269
270
  ...Server.DEFAULT_BUNDLE_OPTIONS,
270
271
  ...options,
271
- bundleType: "bundle",
272
272
  });
273
273
  const { prepend, graph } = await this._bundler.buildGraph(
274
274
  entryFile,
@@ -357,7 +357,11 @@ class Server {
357
357
  this._processRequest(req, res, next).catch(next);
358
358
  };
359
359
  _parseOptions(url) {
360
- return parseOptionsFromUrl(url, new Set(this._config.resolver.platforms));
360
+ const { bundleType: _bundleType, ...bundleOptions } = parseOptionsFromUrl(
361
+ url,
362
+ new Set(this._config.resolver.platforms)
363
+ );
364
+ return bundleOptions;
361
365
  }
362
366
  _rewriteAndNormalizeUrl(requestUrl) {
363
367
  return jscSafeUrl.toNormalUrl(
@@ -471,6 +475,7 @@ class Server {
471
475
  });
472
476
  }
473
477
  _createRequestProcessor({
478
+ bundleType,
474
479
  createStartEntry,
475
480
  createEndEntry,
476
481
  build,
@@ -570,7 +575,7 @@ class Server {
570
575
  this._reporter.update({
571
576
  buildID: getBuildID(buildNumber),
572
577
  bundleDetails: {
573
- bundleType: bundleOptions.bundleType,
578
+ bundleType,
574
579
  customResolverOptions: bundleOptions.customResolverOptions,
575
580
  customTransformOptions: bundleOptions.customTransformOptions,
576
581
  dev: transformOptions.dev,
@@ -648,6 +653,7 @@ class Server {
648
653
  };
649
654
  }
650
655
  _processBundleRequest = this._createRequestProcessor({
656
+ bundleType: "bundle",
651
657
  createStartEntry(context) {
652
658
  return {
653
659
  action_name: "Requesting bundle",
@@ -826,6 +832,7 @@ class Server {
826
832
  );
827
833
  }
828
834
  _processSourceMapRequest = this._createRequestProcessor({
835
+ bundleType: "map",
829
836
  createStartEntry(context) {
830
837
  return {
831
838
  action_name: "Requesting sourcemap",
@@ -889,6 +896,7 @@ class Server {
889
896
  },
890
897
  });
891
898
  _processAssetsRequest = this._createRequestProcessor({
899
+ bundleType: "assets",
892
900
  createStartEntry(context) {
893
901
  return {
894
902
  action_name: "Requesting assets",
@@ -933,10 +941,13 @@ class Server {
933
941
  });
934
942
  async _symbolicate(req, res) {
935
943
  const getCodeFrame = (urls, symbolicatedStack) => {
944
+ const allFramesCollapsed = symbolicatedStack.every(
945
+ ({ collapse }) => collapse
946
+ );
936
947
  for (let i = 0; i < symbolicatedStack.length; i++) {
937
948
  const { collapse, column, file, lineNumber } = symbolicatedStack[i];
938
949
  if (
939
- collapse ||
950
+ (!allFramesCollapsed && collapse) ||
940
951
  lineNumber == null ||
941
952
  (file != null && urls.has(file))
942
953
  ) {
@@ -974,8 +985,13 @@ class Server {
974
985
  createActionStartEntry("Symbolicating")
975
986
  );
976
987
  debug("Start symbolication");
977
- const body = await req.rawBody;
978
- const parsedBody = JSON.parse(body);
988
+ let parsedBody;
989
+ if ("rawBody" in req) {
990
+ const body = await req.rawBody;
991
+ parsedBody = JSON.parse(body);
992
+ } else {
993
+ parsedBody = await parseJsonBody(req);
994
+ }
979
995
  const rewriteAndNormalizeStackFrame = (frame, lineNumber) => {
980
996
  invariant(
981
997
  frame != null && typeof frame === "object",
@@ -24,7 +24,7 @@ import type {
24
24
  import type {RevisionId} from './IncrementalBundler';
25
25
  import type {GraphId} from './lib/getGraphId';
26
26
  import type {Reporter} from './lib/reporting';
27
- import type {StackFrameOutput} from './Server/symbolicate';
27
+ import type {StackFrameInput, StackFrameOutput} from './Server/symbolicate';
28
28
  import type {
29
29
  BundleOptions,
30
30
  GraphOptions,
@@ -34,11 +34,10 @@ import type {
34
34
  import type {IncomingMessage} from 'connect';
35
35
  import type {ServerResponse} from 'http';
36
36
  import type {CacheStore} from 'metro-cache';
37
- import type {ConfigT, RootPerfLogger} from 'metro-config/src/configTypes.flow';
37
+ import type {ConfigT, RootPerfLogger} from 'metro-config';
38
38
  import type {
39
39
  ActionLogEntryData,
40
40
  ActionStartLogEntry,
41
- LogEntry,
42
41
  } from 'metro-core/src/Logger';
43
42
  import type {CustomResolverOptions} from 'metro-resolver/src/types';
44
43
  import type {CustomTransformOptions} from 'metro-transform-worker';
@@ -61,6 +60,7 @@ const ResourceNotFoundError = require('./IncrementalBundler/ResourceNotFoundErro
61
60
  const bundleToString = require('./lib/bundleToString');
62
61
  const formatBundlingError = require('./lib/formatBundlingError');
63
62
  const getGraphId = require('./lib/getGraphId');
63
+ const parseJsonBody = require('./lib/parseJsonBody');
64
64
  const parseOptionsFromUrl = require('./lib/parseOptionsFromUrl');
65
65
  const splitBundleOptions = require('./lib/splitBundleOptions');
66
66
  const transformHelpers = require('./lib/transformHelpers');
@@ -404,7 +404,6 @@ class Server {
404
404
  } = splitBundleOptions({
405
405
  ...Server.DEFAULT_BUNDLE_OPTIONS,
406
406
  ...options,
407
- bundleType: 'bundle',
408
407
  });
409
408
 
410
409
  const {prepend, graph} = await this._bundler.buildGraph(
@@ -469,6 +468,7 @@ class Server {
469
468
  urlObj.query.unstable_path.match(/^([^?]*)\??(.*)$/),
470
469
  );
471
470
  if (secondaryQuery) {
471
+ // $FlowFixMe[unsafe-object-assign]
472
472
  Object.assign(urlObj.query, querystring.parse(secondaryQuery));
473
473
  }
474
474
  assetPath = actualPath;
@@ -524,7 +524,11 @@ class Server {
524
524
  };
525
525
 
526
526
  _parseOptions(url: string): BundleOptions {
527
- return parseOptionsFromUrl(url, new Set(this._config.resolver.platforms));
527
+ const {bundleType: _bundleType, ...bundleOptions} = parseOptionsFromUrl(
528
+ url,
529
+ new Set(this._config.resolver.platforms),
530
+ );
531
+ return bundleOptions;
528
532
  }
529
533
 
530
534
  _rewriteAndNormalizeUrl(requestUrl: string): string {
@@ -652,16 +656,18 @@ class Server {
652
656
  }
653
657
 
654
658
  _createRequestProcessor<T>({
659
+ bundleType,
655
660
  createStartEntry,
656
661
  createEndEntry,
657
662
  build,
658
663
  delete: deleteFn,
659
664
  finish,
660
665
  }: {
666
+ +bundleType: 'assets' | 'bundle' | 'map',
661
667
  +createStartEntry: (context: ProcessStartContext) => ActionLogEntryData,
662
668
  +createEndEntry: (
663
669
  context: ProcessEndContext<T>,
664
- ) => $Rest<ActionStartLogEntry, LogEntry>,
670
+ ) => Partial<ActionStartLogEntry>,
665
671
  +build: (context: ProcessStartContext) => Promise<T>,
666
672
  +delete?: (context: ProcessDeleteContext) => Promise<void>,
667
673
  +finish: (context: ProcessEndContext<T>) => void,
@@ -796,7 +802,7 @@ class Server {
796
802
  this._reporter.update({
797
803
  buildID: getBuildID(buildNumber),
798
804
  bundleDetails: {
799
- bundleType: bundleOptions.bundleType,
805
+ bundleType,
800
806
  customResolverOptions: bundleOptions.customResolverOptions,
801
807
  customTransformOptions: bundleOptions.customTransformOptions,
802
808
  dev: transformOptions.dev,
@@ -895,6 +901,7 @@ class Server {
895
901
  bundlePerfLogger: RootPerfLogger,
896
902
  }>,
897
903
  ) => Promise<void> = this._createRequestProcessor({
904
+ bundleType: 'bundle',
898
905
  createStartEntry(context: ProcessStartContext) {
899
906
  return {
900
907
  action_name: 'Requesting bundle',
@@ -1104,6 +1111,7 @@ class Server {
1104
1111
  bundlePerfLogger: RootPerfLogger,
1105
1112
  }>,
1106
1113
  ) => Promise<void> = this._createRequestProcessor({
1114
+ bundleType: 'map',
1107
1115
  createStartEntry(context: ProcessStartContext) {
1108
1116
  return {
1109
1117
  action_name: 'Requesting sourcemap',
@@ -1175,6 +1183,7 @@ class Server {
1175
1183
  bundlePerfLogger: RootPerfLogger,
1176
1184
  }>,
1177
1185
  ) => Promise<void> = this._createRequestProcessor({
1186
+ bundleType: 'assets',
1178
1187
  createStartEntry(context: ProcessStartContext) {
1179
1188
  return {
1180
1189
  action_name: 'Requesting assets',
@@ -1220,11 +1229,17 @@ class Server {
1220
1229
  urls: Set<string>,
1221
1230
  symbolicatedStack: $ReadOnlyArray<StackFrameOutput>,
1222
1231
  ) => {
1232
+ const allFramesCollapsed = symbolicatedStack.every(
1233
+ ({collapse}) => collapse,
1234
+ );
1235
+
1223
1236
  for (let i = 0; i < symbolicatedStack.length; i++) {
1224
1237
  const {collapse, column, file, lineNumber} = symbolicatedStack[i];
1225
1238
 
1226
1239
  if (
1227
- collapse ||
1240
+ // If all the frames are collapsed then we should ignore the collapse flag
1241
+ // and always show the first valid frame.
1242
+ (!allFramesCollapsed && collapse) ||
1228
1243
  lineNumber == null ||
1229
1244
  (file != null && urls.has(file))
1230
1245
  ) {
@@ -1262,9 +1277,20 @@ class Server {
1262
1277
  createActionStartEntry('Symbolicating'),
1263
1278
  );
1264
1279
  debug('Start symbolication');
1265
- /* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
1266
- const body = await req.rawBody;
1267
- const parsedBody = JSON.parse(body);
1280
+
1281
+ let parsedBody;
1282
+ if ('rawBody' in req) {
1283
+ // TODO: Remove this branch once we are no longer targeting React Native
1284
+ // < 0.80 and Expo SDK < 53
1285
+ // $FlowFixMe[prop-missing] - rawBody assigned by legacy CLI integrations
1286
+ const body = await req.rawBody;
1287
+ parsedBody = JSON.parse(body);
1288
+ } else {
1289
+ parsedBody = (await parseJsonBody(req)) as {
1290
+ stack: $ReadOnlyArray<StackFrameInput>,
1291
+ extraData: {[string]: mixed},
1292
+ };
1293
+ }
1268
1294
 
1269
1295
  const rewriteAndNormalizeStackFrame = <T>(
1270
1296
  frame: T,
package/src/index.flow.js CHANGED
@@ -216,6 +216,7 @@ exports.runServer = async (
216
216
  exports.runBuild = async (
217
217
  config,
218
218
  {
219
+ assets = false,
219
220
  customResolverOptions,
220
221
  customTransformOptions,
221
222
  dev = false,
@@ -229,6 +230,7 @@ exports.runBuild = async (
229
230
  platform = "web",
230
231
  sourceMap = false,
231
232
  sourceMapUrl,
233
+ unstable_transformProfile,
232
234
  }
233
235
  ) => {
234
236
  const metroServer = await runMetro(config, {
@@ -246,11 +248,21 @@ exports.runBuild = async (
246
248
  onProgress,
247
249
  customResolverOptions,
248
250
  customTransformOptions,
251
+ unstable_transformProfile,
249
252
  };
250
253
  if (onBegin) {
251
254
  onBegin();
252
255
  }
253
256
  const metroBundle = await output.build(metroServer, requestOptions);
257
+ const result = {
258
+ ...metroBundle,
259
+ };
260
+ if (assets) {
261
+ result.assets = await metroServer.getAssets({
262
+ ...MetroServer.DEFAULT_BUNDLE_OPTIONS,
263
+ ...requestOptions,
264
+ });
265
+ }
254
266
  if (onComplete) {
255
267
  onComplete();
256
268
  }
@@ -271,7 +283,7 @@ exports.runBuild = async (
271
283
  })
272
284
  );
273
285
  }
274
- return metroBundle;
286
+ return result;
275
287
  } finally {
276
288
  await metroServer.end();
277
289
  }
@@ -11,18 +11,20 @@
11
11
 
12
12
  'use strict';
13
13
 
14
+ import type {AssetData} from './Assets';
14
15
  import type {ReadOnlyGraph} from './DeltaBundler';
15
16
  import type {ServerOptions} from './Server';
16
17
  import type {OutputOptions, RequestOptions} from './shared/types.flow.js';
17
18
  import type {HandleFunction} from 'connect';
18
19
  import type {Server as HttpServer} from 'http';
19
20
  import type {Server as HttpsServer} from 'https';
21
+ import type {TransformProfile} from 'metro-babel-transformer';
20
22
  import type {
21
23
  ConfigT,
22
24
  InputConfigT,
23
25
  MetroConfig,
24
26
  Middleware,
25
- } from 'metro-config/src/configTypes.flow';
27
+ } from 'metro-config';
26
28
  import type {CustomResolverOptions} from 'metro-resolver';
27
29
  import type {CustomTransformOptions} from 'metro-transform-worker';
28
30
  import typeof Yargs from 'yargs';
@@ -92,6 +94,7 @@ type BuildGraphOptions = {
92
94
 
93
95
  export type RunBuildOptions = {
94
96
  entry: string,
97
+ assets?: boolean,
95
98
  dev?: boolean,
96
99
  out?: string,
97
100
  onBegin?: () => void,
@@ -123,6 +126,14 @@ export type RunBuildOptions = {
123
126
  sourceMapUrl?: string,
124
127
  customResolverOptions?: CustomResolverOptions,
125
128
  customTransformOptions?: CustomTransformOptions,
129
+ unstable_transformProfile?: TransformProfile,
130
+ };
131
+
132
+ export type RunBuildResult = {
133
+ code: string,
134
+ map: string,
135
+ assets?: $ReadOnlyArray<AssetData>,
136
+ ...
126
137
  };
127
138
 
128
139
  type BuildCommandOptions = {} | null;
@@ -131,6 +142,9 @@ type ServeCommandOptions = {} | null;
131
142
  exports.Terminal = Terminal;
132
143
  exports.TerminalReporter = TerminalReporter;
133
144
 
145
+ export type {AssetData} from './Assets';
146
+ export type {Reporter, ReportableEvent} from './lib/reporting';
147
+ export type {TerminalReportableEvent} from './lib/TerminalReporter';
134
148
  export type {MetroConfig};
135
149
 
136
150
  async function getConfig(config: InputConfigT): Promise<ConfigT> {
@@ -369,6 +383,7 @@ exports.runServer = async (
369
383
  exports.runBuild = async (
370
384
  config: ConfigT,
371
385
  {
386
+ assets = false,
372
387
  customResolverOptions,
373
388
  customTransformOptions,
374
389
  dev = false,
@@ -382,12 +397,9 @@ exports.runBuild = async (
382
397
  platform = 'web',
383
398
  sourceMap = false,
384
399
  sourceMapUrl,
400
+ unstable_transformProfile,
385
401
  }: RunBuildOptions,
386
- ): Promise<{
387
- code: string,
388
- map: string,
389
- ...
390
- }> => {
402
+ ): Promise<RunBuildResult> => {
391
403
  const metroServer = await runMetro(config, {
392
404
  watch: false,
393
405
  });
@@ -404,6 +416,7 @@ exports.runBuild = async (
404
416
  onProgress,
405
417
  customResolverOptions,
406
418
  customTransformOptions,
419
+ unstable_transformProfile,
407
420
  };
408
421
 
409
422
  if (onBegin) {
@@ -411,6 +424,14 @@ exports.runBuild = async (
411
424
  }
412
425
 
413
426
  const metroBundle = await output.build(metroServer, requestOptions);
427
+ const result: RunBuildResult = {...metroBundle};
428
+
429
+ if (assets) {
430
+ result.assets = await metroServer.getAssets({
431
+ ...MetroServer.DEFAULT_BUNDLE_OPTIONS,
432
+ ...requestOptions,
433
+ });
434
+ }
414
435
 
415
436
  if (onComplete) {
416
437
  onComplete();
@@ -436,7 +457,7 @@ exports.runBuild = async (
436
457
  );
437
458
  }
438
459
 
439
- return metroBundle;
460
+ return result;
440
461
  } finally {
441
462
  await metroServer.end();
442
463
  }
@@ -46,6 +46,7 @@ class JsonReporter<TEvent: {[string]: any, ...}> {
46
46
  update(event: TEvent): void {
47
47
  if (event.error instanceof Error) {
48
48
  const {message, stack} = event.error;
49
+ // $FlowFixMe[unsafe-object-assign]
49
50
  event = Object.assign(event, {
50
51
  error: serializeError(event.error),
51
52
  // TODO: Preexisting issue - this writes message, stack, etc. as
@@ -384,6 +384,7 @@ class TerminalReporter {
384
384
  0.999, // make sure not to go above 99.9% to not get rounded to 100%,
385
385
  );
386
386
 
387
+ // $FlowFixMe[unsafe-object-assign]
387
388
  Object.assign(currentProgress, {
388
389
  ratio,
389
390
  transformedFileCount,
@@ -38,14 +38,17 @@ function formatBundlingError(error) {
38
38
  (error instanceof Error &&
39
39
  (error.type === "TransformError" || error.type === "NotFoundError"))
40
40
  ) {
41
- error.errors = [
42
- {
43
- description: error.message,
44
- filename: error.filename,
45
- lineNumber: error.lineNumber,
46
- },
47
- ];
48
- return serializeError(error);
41
+ return {
42
+ ...serializeError(error),
43
+ type: error.type,
44
+ errors: [
45
+ {
46
+ description: error.message,
47
+ filename: error.filename,
48
+ lineNumber: error.lineNumber,
49
+ },
50
+ ],
51
+ };
49
52
  } else if (error instanceof ResourceNotFoundError) {
50
53
  return {
51
54
  type: "ResourceNotFoundError",
@@ -62,15 +62,18 @@ function formatBundlingError(error: CustomError): FormattedError {
62
62
  (error instanceof Error &&
63
63
  (error.type === 'TransformError' || error.type === 'NotFoundError'))
64
64
  ) {
65
- error.errors = [
66
- {
67
- description: error.message,
68
- filename: error.filename,
69
- lineNumber: error.lineNumber,
70
- },
71
- ];
72
-
73
- return serializeError(error);
65
+ return {
66
+ ...serializeError(error),
67
+ // Ensure the type is passed to the client.
68
+ type: error.type,
69
+ errors: [
70
+ {
71
+ description: error.message,
72
+ filename: error.filename,
73
+ lineNumber: error.lineNumber,
74
+ },
75
+ ],
76
+ };
74
77
  } else if (error instanceof ResourceNotFoundError) {
75
78
  return {
76
79
  type: 'ResourceNotFoundError',
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ const CONTENT_TYPE = "application/json";
4
+ const SIZE_LIMIT = 100 * 1024 * 1024;
5
+ function parseJsonBody(req, options = {}) {
6
+ const { strict = true } = options;
7
+ return new Promise((resolve, reject) => {
8
+ if (strict) {
9
+ const contentType = req.headers["content-type"] || "";
10
+ if (!contentType.includes(CONTENT_TYPE)) {
11
+ reject(new Error(`Invalid content type, expected ${CONTENT_TYPE}`));
12
+ return;
13
+ }
14
+ }
15
+ let size = 0;
16
+ let data = "";
17
+ req.on("data", (chunk) => {
18
+ size += Buffer.byteLength(chunk);
19
+ if (size > SIZE_LIMIT) {
20
+ req.destroy();
21
+ reject(new Error("Request body size exceeds size limit (100MB)"));
22
+ return;
23
+ }
24
+ data += chunk;
25
+ });
26
+ req.on("end", () => {
27
+ try {
28
+ resolve(JSON.parse(data));
29
+ } catch (e) {
30
+ reject(e);
31
+ }
32
+ });
33
+ });
34
+ }
35
+ module.exports = parseJsonBody;
@@ -0,0 +1,60 @@
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
+ import type {IncomingMessage} from 'http';
13
+
14
+ const CONTENT_TYPE = 'application/json';
15
+ const SIZE_LIMIT = 100 * 1024 * 1024; // 100MB
16
+
17
+ /**
18
+ * Attempt to parse a request body as JSON.
19
+ */
20
+ function parseJsonBody(
21
+ req: IncomingMessage,
22
+ options: {strict?: boolean} = {},
23
+ ): Promise<$FlowFixMe> {
24
+ const {strict = true} = options;
25
+
26
+ return new Promise((resolve, reject) => {
27
+ if (strict) {
28
+ const contentType = req.headers['content-type'] || '';
29
+ if (!contentType.includes(CONTENT_TYPE)) {
30
+ reject(new Error(`Invalid content type, expected ${CONTENT_TYPE}`));
31
+ return;
32
+ }
33
+ }
34
+
35
+ let size = 0;
36
+ let data = '';
37
+
38
+ req.on('data', (chunk: string) => {
39
+ size += Buffer.byteLength(chunk);
40
+
41
+ if (size > SIZE_LIMIT) {
42
+ req.destroy();
43
+ reject(new Error('Request body size exceeds size limit (100MB)'));
44
+ return;
45
+ }
46
+
47
+ data += chunk;
48
+ });
49
+
50
+ req.on('end', () => {
51
+ try {
52
+ resolve(JSON.parse(data));
53
+ } catch (e) {
54
+ reject(e);
55
+ }
56
+ });
57
+ });
58
+ }
59
+
60
+ module.exports = parseJsonBody;
@@ -44,7 +44,11 @@ const getTransformProfile = (transformProfile: string): TransformProfile =>
44
44
  module.exports = function parseOptionsFromUrl(
45
45
  normalizedRequestUrl: string,
46
46
  platforms: Set<string>,
47
- ): BundleOptions {
47
+ ): {
48
+ ...BundleOptions,
49
+ // Retained for backwards compatibility, unused in Metro, to be removed.
50
+ bundleType: string,
51
+ } {
48
52
  const parsedURL = nullthrows(url.parse(normalizedRequestUrl, true)); // `true` to parse the query param as an object.
49
53
  const query = nullthrows(parsedURL.query);
50
54
  const pathname =
@@ -21,7 +21,7 @@ import type {
21
21
  import type {TransformOptions} from '../DeltaBundler/Worker';
22
22
  import type {ResolverInputOptions} from '../shared/types.flow';
23
23
  import type {RequireContext} from './contextModule';
24
- import type {ConfigT} from 'metro-config/src/configTypes.flow';
24
+ import type {ConfigT} from 'metro-config';
25
25
  import type {Type} from 'metro-transform-worker';
26
26
 
27
27
  import {getContextModuleTemplate} from './contextModuleTemplates';
@@ -9,7 +9,7 @@
9
9
  * @oncall react_native
10
10
  */
11
11
 
12
- import type {ConfigT} from 'metro-config/src/configTypes.flow';
12
+ import type {ConfigT} from 'metro-config';
13
13
 
14
14
  import MetroFileMap, {DiskCacheManager} from 'metro-file-map';
15
15
 
@@ -15,7 +15,7 @@ import type {
15
15
  } from '../DeltaBundler/types.flow';
16
16
  import type {ResolverInputOptions} from '../shared/types.flow';
17
17
  import type Package from './Package';
18
- import type {ConfigT} from 'metro-config/src/configTypes.flow';
18
+ import type {ConfigT} from 'metro-config';
19
19
  import type MetroFileMap, {
20
20
  ChangeEvent,
21
21
  FileSystem,
@@ -7,7 +7,6 @@ async function build(packagerClient, requestOptions) {
7
7
  const options = {
8
8
  ...Server.DEFAULT_BUNDLE_OPTIONS,
9
9
  ...requestOptions,
10
- bundleType: "ram",
11
10
  };
12
11
  return await packagerClient.getRamBundleInfo(options);
13
12
  }
@@ -25,7 +25,6 @@ async function build(
25
25
  const options = {
26
26
  ...Server.DEFAULT_BUNDLE_OPTIONS,
27
27
  ...requestOptions,
28
- bundleType: 'ram',
29
28
  };
30
29
  return await packagerClient.getRamBundleInfo(options);
31
30
  }
@@ -7,7 +7,6 @@ function buildBundle(packagerClient, requestOptions) {
7
7
  return packagerClient.build({
8
8
  ...Server.DEFAULT_BUNDLE_OPTIONS,
9
9
  ...requestOptions,
10
- bundleType: "bundle",
11
10
  });
12
11
  }
13
12
  function relativateSerializedMap(map, sourceMapSourcesRoot) {
@@ -29,7 +29,6 @@ function buildBundle(
29
29
  return packagerClient.build({
30
30
  ...Server.DEFAULT_BUNDLE_OPTIONS,
31
31
  ...requestOptions,
32
- bundleType: 'bundle',
33
32
  });
34
33
  }
35
34
 
@@ -23,21 +23,9 @@ import type {
23
23
  MinifierOptions,
24
24
  } from 'metro-transform-worker';
25
25
 
26
- export type BundleType =
27
- | 'bundle'
28
- | 'delta'
29
- | 'meta'
30
- | 'map'
31
- | 'ram'
32
- | 'cli'
33
- | 'hmr'
34
- | 'todo'
35
- | 'graph';
36
-
37
26
  type MetroSourceMapOrMappings = MixedSourceMap | MetroSourceMapSegmentTuple[];
38
27
 
39
28
  export interface BundleOptions {
40
- bundleType: BundleType;
41
29
  readonly customResolverOptions: CustomResolverOptions;
42
30
  customTransformOptions: CustomTransformOptions;
43
31
  dev: boolean;
@@ -26,17 +26,6 @@ import type {
26
26
  MinifierOptions,
27
27
  } from 'metro-transform-worker';
28
28
 
29
- type BundleType =
30
- | 'bundle'
31
- | 'delta'
32
- | 'meta'
33
- | 'map'
34
- | 'ram'
35
- | 'cli'
36
- | 'hmr'
37
- | 'todo'
38
- | 'graph';
39
-
40
29
  type MetroSourceMapOrMappings =
41
30
  | MixedSourceMap
42
31
  | Array<MetroSourceMapSegmentTuple>;
@@ -49,7 +38,6 @@ export enum SourcePathsMode {
49
38
  }
50
39
 
51
40
  export type BundleOptions = {
52
- bundleType: BundleType,
53
41
  +customResolverOptions: CustomResolverOptions,
54
42
  customTransformOptions: CustomTransformOptions,
55
43
  dev: boolean,
@@ -151,6 +139,7 @@ export type RequestOptions = {
151
139
  onProgress?: (transformedFileCount: number, totalFileCount: number) => void,
152
140
  +customResolverOptions?: CustomResolverOptions,
153
141
  +customTransformOptions?: CustomTransformOptions,
142
+ +unstable_transformProfile?: TransformProfile,
154
143
  };
155
144
 
156
145
  export type {MinifierOptions};