metro 0.82.0 → 0.82.2

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.82.0",
3
+ "version": "0.82.2",
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.0",
46
- "metro-cache": "0.82.0",
47
- "metro-cache-key": "0.82.0",
48
- "metro-config": "0.82.0",
49
- "metro-core": "0.82.0",
50
- "metro-file-map": "0.82.0",
51
- "metro-resolver": "0.82.0",
52
- "metro-runtime": "0.82.0",
53
- "metro-source-map": "0.82.0",
54
- "metro-symbolicate": "0.82.0",
55
- "metro-transform-plugins": "0.82.0",
56
- "metro-transform-worker": "0.82.0",
45
+ "metro-babel-transformer": "0.82.2",
46
+ "metro-cache": "0.82.2",
47
+ "metro-cache-key": "0.82.2",
48
+ "metro-config": "0.82.2",
49
+ "metro-core": "0.82.2",
50
+ "metro-file-map": "0.82.2",
51
+ "metro-resolver": "0.82.2",
52
+ "metro-runtime": "0.82.2",
53
+ "metro-source-map": "0.82.2",
54
+ "metro-symbolicate": "0.82.2",
55
+ "metro-transform-plugins": "0.82.2",
56
+ "metro-transform-worker": "0.82.2",
57
57
  "mime-types": "^2.1.27",
58
58
  "nullthrows": "^1.1.1",
59
59
  "serialize-error": "^2.1.0",
@@ -65,14 +65,14 @@
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
- "@react-native/babel-preset": "0.73.16",
69
- "@react-native/metro-babel-transformer": "0.73.11",
68
+ "@react-native/babel-preset": "0.78.0",
69
+ "@react-native/metro-babel-transformer": "0.78.0",
70
70
  "babel-jest": "^29.7.0",
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.82.0",
75
- "metro-memory-fs": "0.82.0",
74
+ "metro-babel-register": "0.82.2",
75
+ "metro-memory-fs": "0.82.2",
76
76
  "mock-req": "^0.2.0",
77
77
  "mock-res": "^0.6.0",
78
78
  "stack-trace": "^0.0.10"
@@ -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) {
@@ -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
 
@@ -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<{
@@ -36,7 +36,7 @@ export opaque type RevisionId: string = string;
36
36
  export type OutputGraph = Graph<>;
37
37
 
38
38
  type OtherOptions = $ReadOnly<{
39
- onProgress: $PropertyType<DeltaBundlerOptions<>, 'onProgress'>,
39
+ onProgress: DeltaBundlerOptions<>['onProgress'],
40
40
  shallow: boolean,
41
41
  lazy: boolean,
42
42
  }>;
@@ -38,7 +38,7 @@ export type StackFrameOutput = $ReadOnly<{
38
38
  ...IntermediateStackFrame,
39
39
  ...
40
40
  }>;
41
- type ExplodedSourceMapModule = $ElementType<ExplodedSourceMap, number>;
41
+ type ExplodedSourceMapModule = ExplodedSourceMap[number];
42
42
  type Position = {+line1Based: number, column0Based: number};
43
43
 
44
44
  function createFunctionNameGetter(
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");
@@ -933,10 +934,13 @@ class Server {
933
934
  });
934
935
  async _symbolicate(req, res) {
935
936
  const getCodeFrame = (urls, symbolicatedStack) => {
937
+ const allFramesCollapsed = symbolicatedStack.every(
938
+ ({ collapse }) => collapse
939
+ );
936
940
  for (let i = 0; i < symbolicatedStack.length; i++) {
937
941
  const { collapse, column, file, lineNumber } = symbolicatedStack[i];
938
942
  if (
939
- collapse ||
943
+ (!allFramesCollapsed && collapse) ||
940
944
  lineNumber == null ||
941
945
  (file != null && urls.has(file))
942
946
  ) {
@@ -974,8 +978,13 @@ class Server {
974
978
  createActionStartEntry("Symbolicating")
975
979
  );
976
980
  debug("Start symbolication");
977
- const body = await req.rawBody;
978
- const parsedBody = JSON.parse(body);
981
+ let parsedBody;
982
+ if ("rawBody" in req) {
983
+ const body = await req.rawBody;
984
+ parsedBody = JSON.parse(body);
985
+ } else {
986
+ parsedBody = await parseJsonBody(req);
987
+ }
979
988
  const rewriteAndNormalizeStackFrame = (frame, lineNumber) => {
980
989
  invariant(
981
990
  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,
@@ -38,7 +38,6 @@ import type {ConfigT, RootPerfLogger} from 'metro-config/src/configTypes.flow';
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');
@@ -661,7 +661,7 @@ class Server {
661
661
  +createStartEntry: (context: ProcessStartContext) => ActionLogEntryData,
662
662
  +createEndEntry: (
663
663
  context: ProcessEndContext<T>,
664
- ) => $Rest<ActionStartLogEntry, LogEntry>,
664
+ ) => Partial<ActionStartLogEntry>,
665
665
  +build: (context: ProcessStartContext) => Promise<T>,
666
666
  +delete?: (context: ProcessDeleteContext) => Promise<void>,
667
667
  +finish: (context: ProcessEndContext<T>) => void,
@@ -1220,11 +1220,17 @@ class Server {
1220
1220
  urls: Set<string>,
1221
1221
  symbolicatedStack: $ReadOnlyArray<StackFrameOutput>,
1222
1222
  ) => {
1223
+ const allFramesCollapsed = symbolicatedStack.every(
1224
+ ({collapse}) => collapse,
1225
+ );
1226
+
1223
1227
  for (let i = 0; i < symbolicatedStack.length; i++) {
1224
1228
  const {collapse, column, file, lineNumber} = symbolicatedStack[i];
1225
1229
 
1226
1230
  if (
1227
- collapse ||
1231
+ // If all the frames are collapsed then we should ignore the collapse flag
1232
+ // and always show the first valid frame.
1233
+ (!allFramesCollapsed && collapse) ||
1228
1234
  lineNumber == null ||
1229
1235
  (file != null && urls.has(file))
1230
1236
  ) {
@@ -1262,9 +1268,20 @@ class Server {
1262
1268
  createActionStartEntry('Symbolicating'),
1263
1269
  );
1264
1270
  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);
1271
+
1272
+ let parsedBody;
1273
+ if ('rawBody' in req) {
1274
+ // TODO: Remove this branch once we are no longer targeting React Native
1275
+ // < 0.80 and Expo SDK < 53
1276
+ // $FlowFixMe[prop-missing] - rawBody assigned by legacy CLI integrations
1277
+ const body = await req.rawBody;
1278
+ parsedBody = JSON.parse(body);
1279
+ } else {
1280
+ parsedBody = (await parseJsonBody(req)) as {
1281
+ stack: $ReadOnlyArray<StackFrameInput>,
1282
+ extraData: {[string]: mixed},
1283
+ };
1284
+ }
1268
1285
 
1269
1286
  const rewriteAndNormalizeStackFrame = <T>(
1270
1287
  frame: T,
@@ -55,8 +55,7 @@ class TerminalReporter {
55
55
  chalk.reset.dim(` ${path.dirname(localPath)}/`) +
56
56
  chalk.bold(path.basename(localPath)) +
57
57
  " " +
58
- progress +
59
- "\n"
58
+ progress
60
59
  );
61
60
  }
62
61
  _logBundleBuildDone(buildID) {
@@ -241,9 +240,13 @@ class TerminalReporter {
241
240
  if (currentProgress == null) {
242
241
  return;
243
242
  }
244
- const rawRatio = transformedFileCount / totalFileCount;
245
- const conservativeRatio = Math.pow(rawRatio, 2);
246
- const ratio = Math.max(conservativeRatio, currentProgress.ratio);
243
+ const ratio = Math.min(
244
+ Math.max(
245
+ Math.pow(transformedFileCount / Math.max(totalFileCount, 10), 2),
246
+ currentProgress.ratio
247
+ ),
248
+ 0.999
249
+ );
247
250
  Object.assign(currentProgress, {
248
251
  ratio,
249
252
  transformedFileCount,
@@ -146,8 +146,7 @@ class TerminalReporter {
146
146
  chalk.reset.dim(` ${path.dirname(localPath)}/`) +
147
147
  chalk.bold(path.basename(localPath)) +
148
148
  ' ' +
149
- progress +
150
- '\n'
149
+ progress
151
150
  );
152
151
  }
153
152
 
@@ -355,9 +354,12 @@ class TerminalReporter {
355
354
  }
356
355
 
357
356
  /**
358
- * We use Math.pow(ratio, 2) to as a conservative measure of progress because
359
- * we know the `totalCount` is going to progressively increase as well. We
360
- * also prevent the ratio from going backwards.
357
+ * Because we know the `totalFileCount` is going to progressively increase
358
+ * starting with 1:
359
+ * - We use Math.max(totalFileCount, 10) to prevent the ratio to raise too
360
+ * quickly when the total file count is low. (e.g 1/2 5/6)
361
+ * - We prevent the ratio from going backwards.
362
+ * - Instead, we use Math.pow(ratio, 2) to as a conservative measure of progress.
361
363
  */
362
364
  _updateBundleProgress({
363
365
  buildID,
@@ -373,9 +375,15 @@ class TerminalReporter {
373
375
  if (currentProgress == null) {
374
376
  return;
375
377
  }
376
- const rawRatio = transformedFileCount / totalFileCount;
377
- const conservativeRatio = Math.pow(rawRatio, 2);
378
- const ratio = Math.max(conservativeRatio, currentProgress.ratio);
378
+
379
+ const ratio = Math.min(
380
+ Math.max(
381
+ Math.pow(transformedFileCount / Math.max(totalFileCount, 10), 2),
382
+ currentProgress.ratio,
383
+ ),
384
+ 0.999, // make sure not to go above 99.9% to not get rounded to 100%,
385
+ );
386
+
379
387
  Object.assign(currentProgress, {
380
388
  ratio,
381
389
  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',
@@ -26,10 +26,7 @@ const defaults = require('metro-config/src/defaults/defaults');
26
26
 
27
27
  async function getPrependedScripts(
28
28
  config: ConfigT,
29
- options: $Diff<
30
- TransformInputOptions,
31
- {type: $PropertyType<TransformInputOptions, 'type'>, ...},
32
- >,
29
+ options: Omit<TransformInputOptions, 'type'>,
33
30
  resolverOptions: ResolverInputOptions,
34
31
  bundler: Bundler,
35
32
  deltaBundler: DeltaBundler<>,
@@ -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;
@@ -98,7 +98,7 @@ export type SplitBundleOptions = {
98
98
  +transformOptions: TransformInputOptions,
99
99
  +serializerOptions: SerializerOptions,
100
100
  +graphOptions: GraphOptions,
101
- +onProgress: $PropertyType<DeltaBundlerOptions<>, 'onProgress'>,
101
+ +onProgress: DeltaBundlerOptions<>['onProgress'],
102
102
  };
103
103
 
104
104
  export type ModuleGroups = {