metro 0.81.0 → 0.81.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.
Files changed (36) hide show
  1. package/package.json +27 -21
  2. package/src/Assets.js +8 -24
  3. package/src/Assets.js.flow +9 -28
  4. package/src/Bundler.js +8 -2
  5. package/src/Bundler.js.flow +10 -2
  6. package/src/DeltaBundler/DeltaCalculator.js.flow +3 -14
  7. package/src/DeltaBundler/Serializers/hmrJSBundle.js +5 -2
  8. package/src/DeltaBundler/Serializers/hmrJSBundle.js.flow +8 -2
  9. package/src/DeltaBundler/Transformer.js +17 -4
  10. package/src/DeltaBundler/Transformer.js.flow +26 -5
  11. package/src/HmrServer.js.flow +3 -0
  12. package/src/commands/dependencies.js +13 -10
  13. package/src/commands/dependencies.js.flow +17 -14
  14. package/src/index.flow.js +6 -1
  15. package/src/index.flow.js.flow +9 -5
  16. package/src/integration_tests/metro.config.js +1 -1
  17. package/src/lib/TerminalReporter.js +3 -0
  18. package/src/lib/TerminalReporter.js.flow +3 -0
  19. package/src/lib/getMaxWorkers.js +1 -1
  20. package/src/lib/getMaxWorkers.js.flow +2 -1
  21. package/src/lib/getPreludeCode.js +2 -2
  22. package/src/lib/getPreludeCode.js.flow +2 -2
  23. package/src/lib/reporting.js +4 -2
  24. package/src/lib/reporting.js.flow +9 -2
  25. package/src/lib/transformHelpers.js +5 -0
  26. package/src/lib/transformHelpers.js.flow +5 -0
  27. package/src/node-haste/DependencyGraph/createFileMap.js +9 -5
  28. package/src/node-haste/DependencyGraph/createFileMap.js.flow +10 -5
  29. package/src/node-haste/DependencyGraph.js +16 -4
  30. package/src/node-haste/DependencyGraph.js.flow +26 -9
  31. package/src/shared/output/RamBundle/write-sourcemap.js +1 -1
  32. package/src/shared/output/RamBundle/write-sourcemap.js.flow +1 -1
  33. package/src/shared/output/bundle.flow.js +1 -1
  34. package/src/shared/output/bundle.flow.js.flow +2 -2
  35. package/src/shared/output/writeFile.js +1 -2
  36. package/src/shared/output/writeFile.js.flow +5 -8
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "metro",
3
- "version": "0.81.0",
3
+ "version": "0.81.2",
4
4
  "description": "🚇 The JavaScript bundler for React Native.",
5
5
  "main": "src/index.js",
6
6
  "bin": "src/cli.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./package.json": "./package.json",
10
+ "./private/*": "./src/*.js",
11
+ "./src": "./src/index.js",
12
+ "./src/*.js": "./src/*.js",
13
+ "./src/*": "./src/*.js"
14
+ },
7
15
  "repository": {
8
16
  "type": "git",
9
17
  "url": "git@github.com:facebook/metro.git"
@@ -25,33 +33,31 @@
25
33
  "ci-info": "^2.0.0",
26
34
  "connect": "^3.6.5",
27
35
  "debug": "^2.2.0",
28
- "denodeify": "^1.2.1",
29
36
  "error-stack-parser": "^2.0.6",
30
37
  "flow-enums-runtime": "^0.0.6",
31
38
  "graceful-fs": "^4.2.4",
32
- "hermes-parser": "0.24.0",
39
+ "hermes-parser": "0.25.1",
33
40
  "image-size": "^1.0.2",
34
41
  "invariant": "^2.2.4",
35
- "jest-worker": "^29.6.3",
42
+ "jest-worker": "^29.7.0",
36
43
  "jsc-safe-url": "^0.2.2",
37
44
  "lodash.throttle": "^4.1.1",
38
- "metro-babel-transformer": "0.81.0",
39
- "metro-cache": "0.81.0",
40
- "metro-cache-key": "0.81.0",
41
- "metro-config": "0.81.0",
42
- "metro-core": "0.81.0",
43
- "metro-file-map": "0.81.0",
44
- "metro-resolver": "0.81.0",
45
- "metro-runtime": "0.81.0",
46
- "metro-source-map": "0.81.0",
47
- "metro-symbolicate": "0.81.0",
48
- "metro-transform-plugins": "0.81.0",
49
- "metro-transform-worker": "0.81.0",
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",
50
57
  "mime-types": "^2.1.27",
51
58
  "nullthrows": "^1.1.1",
52
59
  "serialize-error": "^2.1.0",
53
60
  "source-map": "^0.5.6",
54
- "strip-ansi": "^6.0.0",
55
61
  "throat": "^5.0.0",
56
62
  "ws": "^7.5.10",
57
63
  "yargs": "^17.6.2"
@@ -61,12 +67,12 @@
61
67
  "@babel/plugin-transform-modules-commonjs": "^7.24.8",
62
68
  "@react-native/babel-preset": "0.73.16",
63
69
  "@react-native/metro-babel-transformer": "0.73.11",
64
- "babel-jest": "^29.6.3",
70
+ "babel-jest": "^29.7.0",
65
71
  "dedent": "^0.7.0",
66
- "jest-snapshot": "^29.6.3",
72
+ "jest-snapshot": "^29.7.0",
67
73
  "jest-snapshot-serializer-raw": "^1.2.0",
68
- "metro-babel-register": "0.81.0",
69
- "metro-memory-fs": "0.81.0",
74
+ "metro-babel-register": "0.81.2",
75
+ "metro-memory-fs": "0.81.2",
70
76
  "mock-req": "^0.2.0",
71
77
  "mock-res": "^0.6.0",
72
78
  "stack-trace": "^0.0.10"
package/src/Assets.js CHANGED
@@ -2,12 +2,9 @@
2
2
 
3
3
  const AssetPaths = require("./node-haste/lib/AssetPaths");
4
4
  const crypto = require("crypto");
5
- const denodeify = require("denodeify");
6
5
  const fs = require("fs");
7
6
  const getImageSize = require("image-size");
8
7
  const path = require("path");
9
- const readDir = denodeify(fs.readdir);
10
- const readFile = denodeify(fs.readFile);
11
8
  function isAssetTypeAnImage(type) {
12
9
  return (
13
10
  [
@@ -37,22 +34,6 @@ function getAssetSize(type, content, filePath) {
37
34
  height,
38
35
  };
39
36
  }
40
- const hashFiles = denodeify(function hashFilesCb(files, hash, callback) {
41
- if (!files.length) {
42
- callback(null);
43
- return;
44
- }
45
- const file = files.shift();
46
- fs.readFile(file, (err, data) => {
47
- if (err) {
48
- callback(err);
49
- return;
50
- } else {
51
- hash.update(data);
52
- hashFilesCb(files, hash, callback);
53
- }
54
- });
55
- });
56
37
  function buildAssetMap(dir, files, platform) {
57
38
  const platforms = new Set(platform != null ? [platform] : []);
58
39
  const assets = files.map((file) => AssetPaths.tryParse(file, platforms));
@@ -93,7 +74,7 @@ function getAssetKey(assetName, platform) {
93
74
  async function getAbsoluteAssetRecord(assetPath, platform = null) {
94
75
  const filename = path.basename(assetPath);
95
76
  const dir = path.dirname(assetPath);
96
- const files = await readDir(dir);
77
+ const files = await fs.promises.readdir(dir);
97
78
  const assetData = AssetPaths.parse(
98
79
  filename,
99
80
  new Set(platform != null ? [platform] : [])
@@ -124,8 +105,11 @@ async function getAbsoluteAssetInfo(assetPath, platform = null) {
124
105
  const { name, type } = nameData;
125
106
  const { scales, files } = await getAbsoluteAssetRecord(assetPath, platform);
126
107
  const hasher = crypto.createHash("md5");
127
- if (files.length > 0) {
128
- await hashFiles(Array.from(files), hasher);
108
+ const fileData = await Promise.all(
109
+ files.map((file) => fs.promises.readFile(file))
110
+ );
111
+ for (const data of fileData) {
112
+ hasher.update(data);
129
113
  }
130
114
  return {
131
115
  files,
@@ -207,10 +191,10 @@ async function getAsset(
207
191
  const record = await getAbsoluteAssetRecord(absolutePath, platform);
208
192
  for (let i = 0; i < record.scales.length; i++) {
209
193
  if (record.scales[i] >= assetData.resolution) {
210
- return readFile(record.files[i]);
194
+ return fs.promises.readFile(record.files[i]);
211
195
  }
212
196
  }
213
- return readFile(record.files[record.files.length - 1]);
197
+ return fs.promises.readFile(record.files[record.files.length - 1]);
214
198
  }
215
199
  function pathBelongsToRoots(pathToCheck, roots) {
216
200
  for (const rootFolder of roots) {
@@ -15,14 +15,10 @@ import type {AssetPath} from './node-haste/lib/AssetPaths';
15
15
 
16
16
  const AssetPaths = require('./node-haste/lib/AssetPaths');
17
17
  const crypto = require('crypto');
18
- const denodeify = require('denodeify');
19
18
  const fs = require('fs');
20
19
  const getImageSize = require('image-size');
21
20
  const path = require('path');
22
21
 
23
- const readDir = denodeify(fs.readdir);
24
- const readFile = denodeify(fs.readFile);
25
-
26
22
  export type AssetInfo = {
27
23
  +files: Array<string>,
28
24
  +hash: string,
@@ -95,25 +91,6 @@ export type AssetDataPlugin = (
95
91
  assetData: AssetData,
96
92
  ) => AssetData | Promise<AssetData>;
97
93
 
98
- const hashFiles = denodeify(function hashFilesCb(files, hash, callback): void {
99
- if (!files.length) {
100
- callback(null);
101
- return;
102
- }
103
-
104
- const file = files.shift();
105
-
106
- fs.readFile(file, (err, data: Buffer) => {
107
- if (err) {
108
- callback(err);
109
- return;
110
- } else {
111
- hash.update(data);
112
- hashFilesCb(files, hash, callback);
113
- }
114
- });
115
- });
116
-
117
94
  function buildAssetMap(
118
95
  dir: string,
119
96
  files: $ReadOnlyArray<string>,
@@ -168,7 +145,7 @@ async function getAbsoluteAssetRecord(
168
145
  ): Promise<{files: Array<string>, scales: Array<number>}> {
169
146
  const filename = path.basename(assetPath);
170
147
  const dir = path.dirname(assetPath);
171
- const files = await readDir(dir);
148
+ const files = await fs.promises.readdir(dir);
172
149
 
173
150
  const assetData = AssetPaths.parse(
174
151
  filename,
@@ -210,8 +187,12 @@ async function getAbsoluteAssetInfo(
210
187
  const {scales, files} = await getAbsoluteAssetRecord(assetPath, platform);
211
188
  const hasher = crypto.createHash('md5');
212
189
 
213
- if (files.length > 0) {
214
- await hashFiles(Array.from(files), hasher);
190
+ const fileData = await Promise.all(
191
+ files.map(file => fs.promises.readFile(file)),
192
+ );
193
+
194
+ for (const data of fileData) {
195
+ hasher.update(data);
215
196
  }
216
197
 
217
198
  return {files, hash: hasher.digest('hex'), name, scales, type};
@@ -328,11 +309,11 @@ async function getAsset(
328
309
 
329
310
  for (let i = 0; i < record.scales.length; i++) {
330
311
  if (record.scales[i] >= assetData.resolution) {
331
- return readFile(record.files[i]);
312
+ return fs.promises.readFile(record.files[i]);
332
313
  }
333
314
  }
334
315
 
335
- return readFile(record.files[record.files.length - 1]);
316
+ return fs.promises.readFile(record.files[record.files.length - 1]);
336
317
  }
337
318
 
338
319
  function pathBelongsToRoots(
package/src/Bundler.js CHANGED
@@ -11,8 +11,14 @@ class Bundler {
11
11
  config.reporter.update({
12
12
  type: "transformer_load_started",
13
13
  });
14
- this._transformer = new Transformer(config, (...args) =>
15
- this._depGraph.getSha1(...args)
14
+ this._transformer = new Transformer(
15
+ config,
16
+ config.watcher.unstable_lazySha1
17
+ ? {
18
+ unstable_getOrComputeSha1: (filePath) =>
19
+ this._depGraph.unstable_getOrComputeSha1(filePath),
20
+ }
21
+ : (...args) => this._depGraph.getSha1(...args)
16
22
  );
17
23
  config.reporter.update({
18
24
  type: "transformer_load_done",
@@ -36,8 +36,16 @@ class Bundler {
36
36
  .ready()
37
37
  .then(() => {
38
38
  config.reporter.update({type: 'transformer_load_started'});
39
- this._transformer = new Transformer(config, (...args) =>
40
- this._depGraph.getSha1(...args),
39
+ this._transformer = new Transformer(
40
+ config,
41
+ config.watcher.unstable_lazySha1
42
+ ? // This object-form API is expected to replace passing a function
43
+ // once lazy SHA1 is stable. This will be a breaking change.
44
+ {
45
+ unstable_getOrComputeSha1: filePath =>
46
+ this._depGraph.unstable_getOrComputeSha1(filePath),
47
+ }
48
+ : (...args) => this._depGraph.getSha1(...args),
41
49
  );
42
50
  config.reporter.update({type: 'transformer_load_done'});
43
51
  })
@@ -13,7 +13,7 @@
13
13
 
14
14
  import type {DeltaResult, Options} from './types.flow';
15
15
  import type {RootPerfLogger} from 'metro-config';
16
- import type {ChangeEventMetadata} from 'metro-file-map';
16
+ import type {ChangeEvent} from 'metro-file-map';
17
17
 
18
18
  import {Graph} from './Graph';
19
19
  import path from 'path';
@@ -173,9 +173,7 @@ class DeltaCalculator<T> extends EventEmitter {
173
173
  return this._graph;
174
174
  }
175
175
 
176
- /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
177
- * LTI update could not be added via codemod */
178
- _handleMultipleFileChanges = changeEvent => {
176
+ _handleMultipleFileChanges = (changeEvent: ChangeEvent) => {
179
177
  changeEvent.eventsQueue.forEach(eventInfo => {
180
178
  this._handleFileChange(eventInfo, changeEvent.logger);
181
179
  });
@@ -187,16 +185,7 @@ class DeltaCalculator<T> extends EventEmitter {
187
185
  * when the delta needs to be calculated.
188
186
  */
189
187
  _handleFileChange = (
190
- {
191
- type,
192
- filePath,
193
- metadata,
194
- }: {
195
- type: string,
196
- filePath: string,
197
- metadata: ChangeEventMetadata,
198
- ...
199
- },
188
+ {type, filePath, metadata}: ChangeEvent['eventsQueue'][number],
200
189
  logger: ?RootPerfLogger,
201
190
  ): mixed => {
202
191
  debug('Handling %s: %s (type: %s)', type, filePath, metadata.type);
@@ -10,7 +10,9 @@ function generateModules(sourceModules, graph, options) {
10
10
  for (const module of sourceModules) {
11
11
  if (isJsModule(module)) {
12
12
  const getURL = (extension) => {
13
- options.clientUrl.pathname = path.relative(
13
+ const moduleUrl = url.parse(url.format(options.clientUrl), true);
14
+ moduleUrl.search = "";
15
+ moduleUrl.pathname = path.relative(
14
16
  options.serverRoot ?? options.projectRoot,
15
17
  path.join(
16
18
  path.dirname(module.path),
@@ -19,7 +21,8 @@ function generateModules(sourceModules, graph, options) {
19
21
  extension
20
22
  )
21
23
  );
22
- return url.format(options.clientUrl);
24
+ delete moduleUrl.query.excludeSource;
25
+ return url.format(moduleUrl);
23
26
  };
24
27
  const sourceMappingURL = getURL("map");
25
28
  const sourceURL = jscSafeUrl.toJscSafeUrl(getURL("bundle"));
@@ -41,7 +41,12 @@ function generateModules(
41
41
  if (isJsModule(module)) {
42
42
  // Construct a bundle URL for this specific module only
43
43
  const getURL = (extension: 'bundle' | 'map') => {
44
- options.clientUrl.pathname = path.relative(
44
+ const moduleUrl = url.parse(url.format(options.clientUrl), true);
45
+ // the legacy url object is parsed with both "search" and "query" fields.
46
+ // for the "query" field to be used when formatting the object bach to string, the "search" field must be empty.
47
+ // https://nodejs.org/api/url.html#urlformaturlobject:~:text=If%20the%20urlObject.search%20property%20is%20undefined
48
+ moduleUrl.search = '';
49
+ moduleUrl.pathname = path.relative(
45
50
  options.serverRoot ?? options.projectRoot,
46
51
  path.join(
47
52
  path.dirname(module.path),
@@ -50,7 +55,8 @@ function generateModules(
50
55
  extension,
51
56
  ),
52
57
  );
53
- return url.format(options.clientUrl);
58
+ delete moduleUrl.query.excludeSource;
59
+ return url.format(moduleUrl);
54
60
  };
55
61
 
56
62
  const sourceMappingURL = getURL('map');
@@ -11,11 +11,14 @@ const fs = require("fs");
11
11
  const { Cache, stableHash } = require("metro-cache");
12
12
  const path = require("path");
13
13
  class Transformer {
14
- constructor(config, getSha1Fn) {
14
+ constructor(config, getSha1FnOrOpts) {
15
15
  this._config = config;
16
16
  this._config.watchFolders.forEach(verifyRootExists);
17
17
  this._cache = new Cache(config.cacheStores);
18
- this._getSha1 = getSha1Fn;
18
+ this._getSha1 =
19
+ typeof getSha1FnOrOpts === "function"
20
+ ? getSha1FnOrOpts
21
+ : getSha1FnOrOpts.unstable_getOrComputeSha1;
19
22
  const {
20
23
  getTransformOptions: _getTransformOptions,
21
24
  transformVariants: _transformVariants,
@@ -83,13 +86,23 @@ class Transformer {
83
86
  unstable_transformProfile,
84
87
  ]);
85
88
  let sha1;
89
+ let content;
86
90
  if (fileBuffer) {
87
91
  sha1 = _crypto.default
88
92
  .createHash("sha1")
89
93
  .update(fileBuffer)
90
94
  .digest("hex");
95
+ content = fileBuffer;
91
96
  } else {
92
- sha1 = this._getSha1(filePath);
97
+ const result = await this._getSha1(filePath);
98
+ if (typeof result === "string") {
99
+ sha1 = result;
100
+ } else {
101
+ sha1 = result.sha1;
102
+ if (result.content) {
103
+ content = result.content;
104
+ }
105
+ }
93
106
  }
94
107
  let fullKey = Buffer.concat([partialKey, Buffer.from(sha1, "hex")]);
95
108
  let result;
@@ -110,7 +123,7 @@ class Transformer {
110
123
  : await this._workerFarm.transform(
111
124
  localPath,
112
125
  transformerOptions,
113
- fileBuffer
126
+ content
114
127
  );
115
128
  if (sha1 !== data.sha1) {
116
129
  fullKey = Buffer.concat([partialKey, Buffer.from(data.sha1, "hex")]);
@@ -24,19 +24,30 @@ const fs = require('fs');
24
24
  const {Cache, stableHash} = require('metro-cache');
25
25
  const path = require('path');
26
26
 
27
+ type LazySha1Fn = string => Promise<{content?: Buffer, sha1: string}>;
28
+ type EagerSha1Fn = string => string;
29
+
27
30
  class Transformer {
28
31
  _config: ConfigT;
29
32
  _cache: Cache<TransformResult<>>;
30
33
  _baseHash: string;
31
- _getSha1: string => string;
34
+ _getSha1: EagerSha1Fn | LazySha1Fn;
32
35
  _workerFarm: WorkerFarm;
33
36
 
34
- constructor(config: ConfigT, getSha1Fn: string => string) {
37
+ constructor(
38
+ config: ConfigT,
39
+ getSha1FnOrOpts:
40
+ | $ReadOnly<{unstable_getOrComputeSha1: LazySha1Fn}>
41
+ | EagerSha1Fn,
42
+ ) {
35
43
  this._config = config;
36
44
 
37
45
  this._config.watchFolders.forEach(verifyRootExists);
38
46
  this._cache = new Cache(config.cacheStores);
39
- this._getSha1 = getSha1Fn;
47
+ this._getSha1 =
48
+ typeof getSha1FnOrOpts === 'function'
49
+ ? getSha1FnOrOpts
50
+ : getSha1FnOrOpts.unstable_getOrComputeSha1;
40
51
 
41
52
  // Remove the transformer config params that we don't want to pass to the
42
53
  // transformer. We should change the config object and move them away so we
@@ -129,11 +140,21 @@ class Transformer {
129
140
  ]);
130
141
 
131
142
  let sha1: string;
143
+ let content: ?Buffer;
132
144
  if (fileBuffer) {
133
145
  // Shortcut for virtual modules which provide the contents with the filename.
134
146
  sha1 = crypto.createHash('sha1').update(fileBuffer).digest('hex');
147
+ content = fileBuffer;
135
148
  } else {
136
- sha1 = this._getSha1(filePath);
149
+ const result = await this._getSha1(filePath);
150
+ if (typeof result === 'string') {
151
+ sha1 = result;
152
+ } else {
153
+ sha1 = result.sha1;
154
+ if (result.content) {
155
+ content = result.content;
156
+ }
157
+ }
137
158
  }
138
159
 
139
160
  let fullKey = Buffer.concat([partialKey, Buffer.from(sha1, 'hex')]);
@@ -158,7 +179,7 @@ class Transformer {
158
179
  : await this._workerFarm.transform(
159
180
  localPath,
160
181
  transformerOptions,
161
- fileBuffer,
182
+ content,
162
183
  );
163
184
 
164
185
  // Only re-compute the full key if the SHA-1 changed. This is because
@@ -169,6 +169,9 @@ class HmrServer<TClient: Client> {
169
169
  runModule: runModule || 'false',
170
170
  shallow: 'true',
171
171
  };
172
+ // the legacy url object is parsed with both "search" and "query" fields.
173
+ // for the "query" field to be used when formatting the object bach to string, the "search" field must be empty.
174
+ // https://nodejs.org/api/url.html#urlformaturlobject:~:text=If%20the%20urlObject.search%20property%20is%20undefined
172
175
  clientUrl.search = '';
173
176
 
174
177
  clientGroup = {
@@ -28,16 +28,19 @@ async function dependencies(args, config) {
28
28
  const outStream =
29
29
  args.output != null ? fs.createWriteStream(args.output) : process.stdout;
30
30
  const server = new Server(config);
31
- const deps = await server.getOrderedDependencyPaths(options);
32
- deps.forEach((modulePath) => {
33
- const isInsideProjectRoots =
34
- config.watchFolders.filter((root) => modulePath.startsWith(root)).length >
35
- 0;
36
- if (isInsideProjectRoots) {
37
- outStream.write(modulePath + "\n");
38
- }
39
- });
40
- await server.end();
31
+ try {
32
+ const deps = await server.getOrderedDependencyPaths(options);
33
+ deps.forEach((modulePath) => {
34
+ const isInsideProjectRoots =
35
+ config.watchFolders.filter((root) => modulePath.startsWith(root))
36
+ .length > 0;
37
+ if (isInsideProjectRoots) {
38
+ outStream.write(modulePath + "\n");
39
+ }
40
+ });
41
+ } finally {
42
+ await server.end();
43
+ }
41
44
  return args.output != null
42
45
  ? promisify(outStream.end).bind(outStream)()
43
46
  : Promise.resolve();
@@ -60,21 +60,24 @@ async function dependencies(args: Args, config: ConfigT) {
60
60
  args.output != null ? fs.createWriteStream(args.output) : process.stdout;
61
61
 
62
62
  const server = new Server(config);
63
- const deps = await server.getOrderedDependencyPaths(options);
64
- deps.forEach(modulePath => {
65
- // Temporary hack to disable listing dependencies not under this directory.
66
- // Long term, we need either
67
- // (a) JS code to not depend on anything outside this directory, or
68
- // (b) Come up with a way to declare this dependency in Buck.
69
- const isInsideProjectRoots =
70
- config.watchFolders.filter(root => modulePath.startsWith(root)).length >
71
- 0;
72
- if (isInsideProjectRoots) {
73
- outStream.write(modulePath + '\n');
74
- }
75
- });
63
+ try {
64
+ const deps = await server.getOrderedDependencyPaths(options);
65
+ deps.forEach(modulePath => {
66
+ // Temporary hack to disable listing dependencies not under this directory.
67
+ // Long term, we need either
68
+ // (a) JS code to not depend on anything outside this directory, or
69
+ // (b) Come up with a way to declare this dependency in Buck.
70
+ const isInsideProjectRoots =
71
+ config.watchFolders.filter(root => modulePath.startsWith(root)).length >
72
+ 0;
73
+ if (isInsideProjectRoots) {
74
+ outStream.write(modulePath + '\n');
75
+ }
76
+ });
77
+ } finally {
78
+ await server.end();
79
+ }
76
80
 
77
- await server.end();
78
81
  return args.output != null
79
82
  ? // $FlowFixMe[method-unbinding]
80
83
  promisify(outStream.end).bind(outStream)()
package/src/index.flow.js CHANGED
@@ -264,7 +264,12 @@ exports.runBuild = async (
264
264
  dev,
265
265
  platform,
266
266
  };
267
- await output.save(metroBundle, outputOptions, console.log);
267
+ await output.save(metroBundle, outputOptions, (message) =>
268
+ config.reporter.update({
269
+ type: "bundle_save_log",
270
+ message,
271
+ })
272
+ );
268
273
  }
269
274
  return metroBundle;
270
275
  } finally {
@@ -98,7 +98,7 @@ export type RunBuildOptions = {
98
98
  onComplete?: () => void,
99
99
  onProgress?: (transformedFileCount: number, totalFileCount: number) => void,
100
100
  minify?: boolean,
101
- output?: {
101
+ output?: $ReadOnly<{
102
102
  build: (
103
103
  MetroServer,
104
104
  RequestOptions,
@@ -114,10 +114,10 @@ export type RunBuildOptions = {
114
114
  ...
115
115
  },
116
116
  OutputOptions,
117
- (...args: Array<string>) => void,
117
+ (logMessage: string) => void,
118
118
  ) => Promise<mixed>,
119
119
  ...
120
- },
120
+ }>,
121
121
  platform?: string,
122
122
  sourceMap?: boolean,
123
123
  sourceMapUrl?: string,
@@ -428,8 +428,12 @@ exports.runBuild = async (
428
428
  platform,
429
429
  };
430
430
 
431
- // eslint-disable-next-line no-console
432
- await output.save(metroBundle, outputOptions, console.log);
431
+ await output.save(metroBundle, outputOptions, message =>
432
+ config.reporter.update({
433
+ type: 'bundle_save_log',
434
+ message,
435
+ }),
436
+ );
433
437
  }
434
438
 
435
439
  return metroBundle;
@@ -11,7 +11,7 @@ module.exports = {
11
11
  },
12
12
  watchFolders: [path.resolve(__dirname, "../../../")],
13
13
  server: {
14
- port: 10028,
14
+ port: 0,
15
15
  },
16
16
  resolver: {
17
17
  blockList: [/excluded_from_file_map\.js$/],
@@ -136,6 +136,9 @@ class TerminalReporter {
136
136
  case "bundle_build_done":
137
137
  this._logBundleBuildDone(event.buildID);
138
138
  break;
139
+ case "bundle_save_log":
140
+ this.terminal.log("LOG:" + event.message);
141
+ break;
139
142
  case "bundle_build_failed":
140
143
  this._logBundleBuildFailed(event.buildID);
141
144
  break;
@@ -237,6 +237,9 @@ class TerminalReporter {
237
237
  case 'bundle_build_done':
238
238
  this._logBundleBuildDone(event.buildID);
239
239
  break;
240
+ case 'bundle_save_log':
241
+ this.terminal.log('LOG:' + event.message);
242
+ break;
240
243
  case 'bundle_build_failed':
241
244
  this._logBundleBuildFailed(event.buildID);
242
245
  break;
@@ -2,7 +2,7 @@
2
2
 
3
3
  const os = require("os");
4
4
  module.exports = (workers) => {
5
- const cores = os.cpus().length;
5
+ const cores = os.availableParallelism();
6
6
  return typeof workers === "number" && Number.isInteger(workers)
7
7
  ? Math.min(cores, workers > 0 ? workers : 1)
8
8
  : Math.max(1, Math.ceil(cores * (0.5 + 0.5 * Math.exp(-cores * 0.07)) - 1));
@@ -14,7 +14,8 @@
14
14
  const os = require('os');
15
15
 
16
16
  module.exports = (workers: ?number): number => {
17
- const cores = os.cpus().length;
17
+ // $FlowFixMe[prop-missing] Missing Flow lib def for availableParallelism
18
+ const cores = os.availableParallelism();
18
19
  return typeof workers === 'number' && Number.isInteger(workers)
19
20
  ? Math.min(cores, workers > 0 ? workers : 1)
20
21
  : Math.max(1, Math.ceil(cores * (0.5 + 0.5 * Math.exp(-cores * 0.07)) - 1));
@@ -7,10 +7,10 @@ function getPreludeCode({
7
7
  requireCycleIgnorePatterns,
8
8
  }) {
9
9
  const vars = [
10
- "__BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now()",
10
+ "__BUNDLE_START_TIME__=globalThis.nativePerformanceNow?nativePerformanceNow():Date.now()",
11
11
  `__DEV__=${String(isDev)}`,
12
12
  ...formatExtraVars(extraVars),
13
- "process=this.process||{}",
13
+ "process=globalThis.process||{}",
14
14
  `__METRO_GLOBAL_PREFIX__='${globalPrefix}'`,
15
15
  ];
16
16
  if (isDev) {
@@ -25,10 +25,10 @@ function getPreludeCode({
25
25
  const vars = [
26
26
  // Ensure these variable names match the ones referenced in metro-runtime
27
27
  // require.js
28
- '__BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now()',
28
+ '__BUNDLE_START_TIME__=globalThis.nativePerformanceNow?nativePerformanceNow():Date.now()',
29
29
  `__DEV__=${String(isDev)}`,
30
30
  ...formatExtraVars(extraVars),
31
- 'process=this.process||{}',
31
+ 'process=globalThis.process||{}',
32
32
  `__METRO_GLOBAL_PREFIX__='${globalPrefix}'`,
33
33
  ];
34
34
 
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
3
  const chalk = require("chalk");
4
- const stripAnsi = require("strip-ansi");
5
4
  const util = require("util");
6
5
  function logWarning(terminal, format, ...args) {
7
6
  const str = util.format(format, ...args);
@@ -11,7 +10,10 @@ function logError(terminal, format, ...args) {
11
10
  terminal.log(
12
11
  "%s %s",
13
12
  chalk.red.inverse.bold(" ERROR "),
14
- util.format(chalk.supportsColor ? format : stripAnsi(format), ...args)
13
+ util.format(
14
+ chalk.supportsColor ? format : util.stripVTControlCharacters(format),
15
+ ...args
16
+ )
15
17
  );
16
18
  }
17
19
  function logInfo(terminal, format, ...args) {
@@ -17,7 +17,6 @@ import type {CustomResolverOptions} from 'metro-resolver';
17
17
  import type {CustomTransformOptions} from 'metro-transform-worker';
18
18
 
19
19
  const chalk = require('chalk');
20
- const stripAnsi = require('strip-ansi');
21
20
  const util = require('util');
22
21
 
23
22
  export type BundleDetails = {
@@ -62,6 +61,11 @@ export type ReportableEvent =
62
61
  type: 'bundle_build_failed',
63
62
  ...
64
63
  }
64
+ | {
65
+ type: 'bundle_save_log',
66
+ message: string,
67
+ ...
68
+ }
65
69
  | {
66
70
  buildID: string,
67
71
  bundleDetails: BundleDetails,
@@ -207,7 +211,10 @@ function logError(
207
211
  // in various places outside of where Metro is currently running.
208
212
  // If the current terminal does not support color, we'll strip the colors
209
213
  // here.
210
- util.format(chalk.supportsColor ? format : stripAnsi(format), ...args),
214
+ util.format(
215
+ chalk.supportsColor ? format : util.stripVTControlCharacters(format),
216
+ ...args,
217
+ ),
211
218
  );
212
219
  }
213
220
 
@@ -12,6 +12,7 @@ const baseIgnoredInlineRequires = [
12
12
  "react",
13
13
  "react/jsx-dev-runtime",
14
14
  "react/jsx-runtime",
15
+ "react-compiler-runtime",
15
16
  "react-native",
16
17
  ];
17
18
  async function calcTransformerOptions(
@@ -78,6 +79,10 @@ async function calcTransformerOptions(
78
79
  );
79
80
  return {
80
81
  ...baseOptions,
82
+ inlinePlatform:
83
+ transform?.unstable_inlinePlatform != null
84
+ ? transform.unstable_inlinePlatform
85
+ : true,
81
86
  inlineRequires: transform?.inlineRequires || false,
82
87
  experimentalImportSupport: transform?.experimentalImportSupport || false,
83
88
  unstable_disableES6Transforms:
@@ -41,6 +41,7 @@ const baseIgnoredInlineRequires = [
41
41
  'react',
42
42
  'react/jsx-dev-runtime',
43
43
  'react/jsx-runtime',
44
+ 'react-compiler-runtime',
44
45
  'react-native',
45
46
  ];
46
47
 
@@ -112,6 +113,10 @@ async function calcTransformerOptions(
112
113
 
113
114
  return {
114
115
  ...baseOptions,
116
+ inlinePlatform:
117
+ transform?.unstable_inlinePlatform != null
118
+ ? transform.unstable_inlinePlatform
119
+ : true,
115
120
  inlineRequires: transform?.inlineRequires || false,
116
121
  experimentalImportSupport: transform?.experimentalImportSupport || false,
117
122
  unstable_disableES6Transforms:
@@ -61,19 +61,23 @@ function createFileMap(config, options) {
61
61
  ? null
62
62
  : config.resolver.dependencyExtractor;
63
63
  const computeDependencies = dependencyExtractor != null;
64
+ const watch = options?.watch == null ? !ci.isCI : options.watch;
65
+ const { enabled: autoSaveEnabled, ...autoSaveOpts } =
66
+ config.watcher.unstable_autoSaveCache ?? {};
67
+ const autoSave = watch && autoSaveEnabled ? autoSaveOpts : false;
64
68
  return _metroFileMap.default.create({
65
69
  cacheManagerFactory:
66
70
  config?.unstable_fileMapCacheManagerFactory ??
67
- ((buildParameters) =>
68
- new _metroFileMap.DiskCacheManager({
69
- buildParameters,
71
+ ((factoryParams) =>
72
+ new _metroFileMap.DiskCacheManager(factoryParams, {
70
73
  cacheDirectory:
71
74
  config.fileMapCacheDirectory ?? config.hasteMapCacheDirectory,
72
75
  cacheFilePrefix: options?.cacheFilePrefix,
76
+ autoSave,
73
77
  })),
74
78
  perfLoggerFactory: config.unstable_perfLoggerFactory,
75
79
  computeDependencies,
76
- computeSha1: true,
80
+ computeSha1: !config.watcher.unstable_lazySha1,
77
81
  dependencyExtractor: config.resolver.dependencyExtractor,
78
82
  enableHastePackages: config?.resolver.enableGlobalPackages,
79
83
  enableSymlinks: true,
@@ -101,7 +105,7 @@ function createFileMap(config, options) {
101
105
  roots: config.watchFolders,
102
106
  throwOnModuleCollision: options?.throwOnModuleCollision ?? true,
103
107
  useWatchman: config.resolver.useWatchman,
104
- watch: options?.watch == null ? !ci.isCI : options.watch,
108
+ watch,
105
109
  watchmanDeferStates: config.watcher.watchman.deferStates,
106
110
  });
107
111
  }
@@ -68,19 +68,24 @@ function createFileMap(
68
68
  : config.resolver.dependencyExtractor;
69
69
  const computeDependencies = dependencyExtractor != null;
70
70
 
71
+ const watch = options?.watch == null ? !ci.isCI : options.watch;
72
+ const {enabled: autoSaveEnabled, ...autoSaveOpts} =
73
+ config.watcher.unstable_autoSaveCache ?? {};
74
+ const autoSave = watch && autoSaveEnabled ? autoSaveOpts : false;
75
+
71
76
  return MetroFileMap.create({
72
77
  cacheManagerFactory:
73
78
  config?.unstable_fileMapCacheManagerFactory ??
74
- (buildParameters =>
75
- new DiskCacheManager({
76
- buildParameters,
79
+ (factoryParams =>
80
+ new DiskCacheManager(factoryParams, {
77
81
  cacheDirectory:
78
82
  config.fileMapCacheDirectory ?? config.hasteMapCacheDirectory,
79
83
  cacheFilePrefix: options?.cacheFilePrefix,
84
+ autoSave,
80
85
  })),
81
86
  perfLoggerFactory: config.unstable_perfLoggerFactory,
82
87
  computeDependencies,
83
- computeSha1: true,
88
+ computeSha1: !config.watcher.unstable_lazySha1,
84
89
  dependencyExtractor: config.resolver.dependencyExtractor,
85
90
  enableHastePackages: config?.resolver.enableGlobalPackages,
86
91
  enableSymlinks: true,
@@ -105,7 +110,7 @@ function createFileMap(
105
110
  roots: config.watchFolders,
106
111
  throwOnModuleCollision: options?.throwOnModuleCollision ?? true,
107
112
  useWatchman: config.resolver.useWatchman,
108
- watch: options?.watch == null ? !ci.isCI : options.watch,
113
+ watch,
109
114
  watchmanDeferStates: config.watcher.watchman.deferStates,
110
115
  });
111
116
  }
@@ -24,6 +24,13 @@ function getOrCreateMap(map, field) {
24
24
  }
25
25
  return subMap;
26
26
  }
27
+ const missingSha1Error = (mixedPath) =>
28
+ new Error(`Failed to get the SHA-1 for: ${mixedPath}.
29
+ Potential causes:
30
+ 1) The file is not watched. Ensure it is under the configured \`projectRoot\` or \`watchFolders\`.
31
+ 2) Check \`blockList\` in your metro.config.js and make sure it isn't excluding the file path.
32
+ 3) The file may have been deleted since it was resolved - try refreshing your app.
33
+ 4) Otherwise, this is a bug in Metro or the configured resolver - please report it.`);
27
34
  class DependencyGraph extends EventEmitter {
28
35
  constructor(config, options) {
29
36
  super();
@@ -37,6 +44,7 @@ class DependencyGraph extends EventEmitter {
37
44
  hasReducedPerformance: !!hasReducedPerformance,
38
45
  });
39
46
  const fileMap = createFileMap(config, {
47
+ throwOnModuleCollision: false,
40
48
  watch,
41
49
  });
42
50
  fileMap.setMaxListeners(1000);
@@ -178,13 +186,17 @@ class DependencyGraph extends EventEmitter {
178
186
  getSha1(filename) {
179
187
  const sha1 = this._fileSystem.getSha1(filename);
180
188
  if (!sha1) {
181
- throw new ReferenceError(`SHA-1 for file ${filename} is not computed.
182
- Potential causes:
183
- 1) You have symlinks in your project - watchman does not follow symlinks.
184
- 2) Check \`blockList\` in your metro.config.js and make sure it isn't excluding the file path.`);
189
+ throw missingSha1Error(filename);
185
190
  }
186
191
  return sha1;
187
192
  }
193
+ async unstable_getOrComputeSha1(mixedPath) {
194
+ const result = await this._fileSystem.getOrComputeSha1(mixedPath);
195
+ if (!result || !result.sha1) {
196
+ throw missingSha1Error(mixedPath);
197
+ }
198
+ return result;
199
+ }
188
200
  getWatcher() {
189
201
  return this._haste;
190
202
  }
@@ -55,6 +55,14 @@ function getOrCreateMap<T>(
55
55
  return subMap;
56
56
  }
57
57
 
58
+ const missingSha1Error = (mixedPath: string) =>
59
+ new Error(`Failed to get the SHA-1 for: ${mixedPath}.
60
+ Potential causes:
61
+ 1) The file is not watched. Ensure it is under the configured \`projectRoot\` or \`watchFolders\`.
62
+ 2) Check \`blockList\` in your metro.config.js and make sure it isn't excluding the file path.
63
+ 3) The file may have been deleted since it was resolved - try refreshing your app.
64
+ 4) Otherwise, this is a bug in Metro or the configured resolver - please report it.`);
65
+
58
66
  class DependencyGraph extends EventEmitter {
59
67
  _config: ConfigT;
60
68
  _haste: MetroFileMap;
@@ -101,7 +109,10 @@ class DependencyGraph extends EventEmitter {
101
109
  type: 'dep_graph_loading',
102
110
  hasReducedPerformance: !!hasReducedPerformance,
103
111
  });
104
- const fileMap = createFileMap(config, {watch});
112
+ const fileMap = createFileMap(config, {
113
+ throwOnModuleCollision: false,
114
+ watch,
115
+ });
105
116
 
106
117
  // We can have a lot of graphs listening to Haste for changes.
107
118
  // Bump this up to silence the max listeners EventEmitter warning.
@@ -255,19 +266,25 @@ class DependencyGraph extends EventEmitter {
255
266
 
256
267
  getSha1(filename: string): string {
257
268
  const sha1 = this._fileSystem.getSha1(filename);
258
-
259
269
  if (!sha1) {
260
- throw new ReferenceError(
261
- `SHA-1 for file ${filename} is not computed.
262
- Potential causes:
263
- 1) You have symlinks in your project - watchman does not follow symlinks.
264
- 2) Check \`blockList\` in your metro.config.js and make sure it isn't excluding the file path.`,
265
- );
270
+ throw missingSha1Error(filename);
266
271
  }
267
-
268
272
  return sha1;
269
273
  }
270
274
 
275
+ /**
276
+ * Used when watcher.unstable_lazySha1 is true
277
+ */
278
+ async unstable_getOrComputeSha1(
279
+ mixedPath: string,
280
+ ): Promise<{content?: Buffer, sha1: string}> {
281
+ const result = await this._fileSystem.getOrComputeSha1(mixedPath);
282
+ if (!result || !result.sha1) {
283
+ throw missingSha1Error(mixedPath);
284
+ }
285
+ return result;
286
+ }
287
+
271
288
  getWatcher(): EventEmitter {
272
289
  return this._haste;
273
290
  }
@@ -6,7 +6,7 @@ function writeSourcemap(fileName, contents, log) {
6
6
  return Promise.resolve();
7
7
  }
8
8
  log("Writing sourcemap output to:", fileName);
9
- const writeMap = writeFile(fileName, contents, null);
9
+ const writeMap = writeFile(fileName, contents);
10
10
  writeMap.then(() => log("Done writing sourcemap output"));
11
11
  return writeMap;
12
12
  }
@@ -22,7 +22,7 @@ function writeSourcemap(
22
22
  return Promise.resolve();
23
23
  }
24
24
  log('Writing sourcemap output to:', fileName);
25
- const writeMap = writeFile(fileName, contents, null);
25
+ const writeMap = writeFile(fileName, contents);
26
26
  // $FlowFixMe[unused-promise]
27
27
  writeMap.then(() => log('Done writing sourcemap output'));
28
28
  return writeMap;
@@ -37,7 +37,7 @@ async function saveBundleAndMap(bundle, options, log) {
37
37
  }
38
38
  writeFns.push(async () => {
39
39
  log(`Writing sourcemap output to: ${sourcemapOutput}`);
40
- await writeFile(sourcemapOutput, map, null);
40
+ await writeFile(sourcemapOutput, map);
41
41
  log("Done writing sourcemap output");
42
42
  });
43
43
  }
@@ -49,7 +49,7 @@ async function saveBundleAndMap(
49
49
  ...
50
50
  },
51
51
  options: OutputOptions,
52
- log: (...args: Array<string>) => void,
52
+ log: string => void,
53
53
  ): Promise<mixed> {
54
54
  const {
55
55
  bundleOutput,
@@ -76,7 +76,7 @@ async function saveBundleAndMap(
76
76
 
77
77
  writeFns.push(async () => {
78
78
  log(`Writing sourcemap output to: ${sourcemapOutput}`);
79
- await writeFile(sourcemapOutput, map, null);
79
+ await writeFile(sourcemapOutput, map);
80
80
  log('Done writing sourcemap output');
81
81
  });
82
82
  }
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const denodeify = require("denodeify");
4
3
  const fs = require("fs");
5
4
  const throat = require("throat");
6
- const writeFile = throat(128, denodeify(fs.writeFile));
5
+ const writeFile = throat(128, fs.promises.writeFile);
7
6
  module.exports = writeFile;
@@ -4,22 +4,19 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @flow
7
+ * @flow strict-local
8
8
  * @format
9
9
  * @oncall react_native
10
10
  */
11
11
 
12
12
  'use strict';
13
13
 
14
- const denodeify = require('denodeify');
15
14
  const fs = require('fs');
16
15
  const throat = require('throat');
17
16
 
18
- type WriteFn = (
19
- file: string,
20
- data: string | Buffer,
21
- encoding?: ?string,
22
- ) => Promise<mixed>;
23
- const writeFile: WriteFn = throat(128, denodeify(fs.writeFile));
17
+ const writeFile: typeof fs.promises.writeFile = throat(
18
+ 128,
19
+ fs.promises.writeFile,
20
+ );
24
21
 
25
22
  module.exports = writeFile;