metro 0.76.3 → 0.76.5

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 (33) hide show
  1. package/package.json +24 -23
  2. package/src/Assets.js +35 -3
  3. package/src/Assets.js.flow +38 -3
  4. package/src/Bundler/util.js +0 -72
  5. package/src/Bundler/util.js.flow +0 -98
  6. package/src/DeltaBundler/DeltaCalculator.js +7 -0
  7. package/src/DeltaBundler/DeltaCalculator.js.flow +7 -0
  8. package/src/DeltaBundler/Graph.js +108 -26
  9. package/src/DeltaBundler/Graph.js.flow +119 -26
  10. package/src/DeltaBundler/Serializers/baseJSBundle.js +1 -0
  11. package/src/DeltaBundler/Serializers/baseJSBundle.js.flow +1 -0
  12. package/src/DeltaBundler/Serializers/getRamBundleInfo.js +1 -0
  13. package/src/DeltaBundler/Serializers/getRamBundleInfo.js.flow +3 -1
  14. package/src/DeltaBundler/Serializers/helpers/getSourceMapInfo.js +1 -0
  15. package/src/DeltaBundler/Serializers/helpers/getSourceMapInfo.js.flow +3 -0
  16. package/src/DeltaBundler/Serializers/helpers/js.js +4 -1
  17. package/src/DeltaBundler/Serializers/helpers/js.js.flow +4 -1
  18. package/src/DeltaBundler/Serializers/hmrJSBundle.js +2 -1
  19. package/src/DeltaBundler/Serializers/hmrJSBundle.js.flow +2 -1
  20. package/src/DeltaBundler/Serializers/sourceMapGenerator.js +1 -0
  21. package/src/DeltaBundler/Serializers/sourceMapGenerator.js.flow +10 -12
  22. package/src/DeltaBundler/Serializers/sourceMapObject.js.flow +3 -8
  23. package/src/DeltaBundler/Serializers/sourceMapString.js.flow +2 -4
  24. package/src/DeltaBundler/Transformer.js +1 -0
  25. package/src/DeltaBundler/Transformer.js.flow +1 -0
  26. package/src/DeltaBundler/types.d.ts +1 -0
  27. package/src/DeltaBundler/types.flow.js.flow +10 -7
  28. package/src/Server.js +52 -14
  29. package/src/Server.js.flow +61 -16
  30. package/src/lib/getAppendScripts.js +1 -0
  31. package/src/lib/getAppendScripts.js.flow +12 -10
  32. package/src/lib/parseOptionsFromUrl.js +4 -3
  33. package/src/lib/parseOptionsFromUrl.js.flow +4 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metro",
3
- "version": "0.76.3",
3
+ "version": "0.76.5",
4
4
  "description": "🚇 The JavaScript bundler for React Native.",
5
5
  "main": "src/index.js",
6
6
  "bin": "src/cli.js",
@@ -30,26 +30,27 @@
30
30
  "error-stack-parser": "^2.0.6",
31
31
  "graceful-fs": "^4.2.4",
32
32
  "hermes-parser": "0.8.0",
33
- "image-size": "^0.6.0",
33
+ "image-size": "^1.0.2",
34
34
  "invariant": "^2.2.4",
35
35
  "jest-worker": "^27.2.0",
36
+ "jsc-safe-url": "^0.2.2",
36
37
  "lodash.throttle": "^4.1.1",
37
- "metro-babel-transformer": "0.76.3",
38
- "metro-cache": "0.76.3",
39
- "metro-cache-key": "0.76.3",
40
- "metro-config": "0.76.3",
41
- "metro-core": "0.76.3",
42
- "metro-file-map": "0.76.3",
43
- "metro-inspector-proxy": "0.76.3",
44
- "metro-minify-terser": "0.76.3",
45
- "metro-minify-uglify": "0.76.3",
46
- "metro-react-native-babel-preset": "0.76.3",
47
- "metro-resolver": "0.76.3",
48
- "metro-runtime": "0.76.3",
49
- "metro-source-map": "0.76.3",
50
- "metro-symbolicate": "0.76.3",
51
- "metro-transform-plugins": "0.76.3",
52
- "metro-transform-worker": "0.76.3",
38
+ "metro-babel-transformer": "0.76.5",
39
+ "metro-cache": "0.76.5",
40
+ "metro-cache-key": "0.76.5",
41
+ "metro-config": "0.76.5",
42
+ "metro-core": "0.76.5",
43
+ "metro-file-map": "0.76.5",
44
+ "metro-inspector-proxy": "0.76.5",
45
+ "metro-minify-terser": "0.76.5",
46
+ "metro-minify-uglify": "0.76.5",
47
+ "metro-react-native-babel-preset": "0.76.5",
48
+ "metro-resolver": "0.76.5",
49
+ "metro-runtime": "0.76.5",
50
+ "metro-source-map": "0.76.5",
51
+ "metro-symbolicate": "0.76.5",
52
+ "metro-transform-plugins": "0.76.5",
53
+ "metro-transform-worker": "0.76.5",
53
54
  "mime-types": "^2.1.27",
54
55
  "node-fetch": "^2.2.0",
55
56
  "nullthrows": "^1.1.1",
@@ -62,15 +63,15 @@
62
63
  "yargs": "^17.6.2"
63
64
  },
64
65
  "devDependencies": {
65
- "@babel/plugin-transform-flow-strip-types": "^7.0.0",
66
+ "@babel/plugin-transform-flow-strip-types": "^7.20.0",
66
67
  "babel-jest": "^29.2.1",
67
68
  "dedent": "^0.7.0",
68
69
  "jest-snapshot": "^26.5.2",
69
70
  "jest-snapshot-serializer-raw": "^1.2.0",
70
- "metro-babel-register": "0.76.3",
71
- "metro-memory-fs": "0.76.3",
72
- "metro-react-native-babel-preset": "0.76.3",
73
- "metro-react-native-babel-transformer": "0.76.3",
71
+ "metro-babel-register": "0.76.5",
72
+ "metro-memory-fs": "0.76.5",
73
+ "metro-react-native-babel-preset": "0.76.5",
74
+ "metro-react-native-babel-transformer": "0.76.5",
74
75
  "mock-req": "^0.2.0",
75
76
  "mock-res": "^0.6.0",
76
77
  "stack-trace": "^0.0.10"
package/src/Assets.js CHANGED
@@ -11,15 +11,45 @@
11
11
 
12
12
  "use strict";
13
13
 
14
- const { isAssetTypeAnImage } = require("./Bundler/util");
15
14
  const AssetPaths = require("./node-haste/lib/AssetPaths");
16
15
  const crypto = require("crypto");
17
16
  const denodeify = require("denodeify");
18
17
  const fs = require("fs");
19
- const imageSize = require("image-size");
18
+ const getImageSize = require("image-size");
20
19
  const path = require("path");
21
20
  const readDir = denodeify(fs.readdir);
22
21
  const readFile = denodeify(fs.readFile);
22
+ // Test extension against all types supported by image-size module.
23
+ // If it's not one of these, we won't treat it as an image.
24
+ function isAssetTypeAnImage(type) {
25
+ return (
26
+ [
27
+ "png",
28
+ "jpg",
29
+ "jpeg",
30
+ "bmp",
31
+ "gif",
32
+ "webp",
33
+ "psd",
34
+ "svg",
35
+ "tiff",
36
+ "ktx",
37
+ ].indexOf(type) !== -1
38
+ );
39
+ }
40
+ function getAssetSize(type, content, filePath) {
41
+ if (!isAssetTypeAnImage(type)) {
42
+ return null;
43
+ }
44
+ if (content.length === 0) {
45
+ throw new Error(`Image asset \`${filePath}\` cannot be an empty file.`);
46
+ }
47
+ const { width, height } = getImageSize(content);
48
+ return {
49
+ width,
50
+ height,
51
+ };
52
+ }
23
53
  const hashFiles = denodeify(function hashFilesCb(files, hash, callback) {
24
54
  if (!files.length) {
25
55
  callback(null);
@@ -141,7 +171,7 @@ async function getAssetData(
141
171
  const isImageInput = assetInfo.files[0].includes(".zip/")
142
172
  ? fs.readFileSync(assetInfo.files[0])
143
173
  : assetInfo.files[0];
144
- const dimensions = isImage ? imageSize(isImageInput) : null;
174
+ const dimensions = isImage ? getImageSize(isImageInput) : null;
145
175
  const scale = assetInfo.scales[0];
146
176
  const assetData = {
147
177
  __packager_asset: true,
@@ -227,6 +257,8 @@ function pathBelongsToRoots(pathToCheck, roots) {
227
257
  }
228
258
  module.exports = {
229
259
  getAsset,
260
+ getAssetSize,
230
261
  getAssetData,
231
262
  getAssetFiles,
263
+ isAssetTypeAnImage,
232
264
  };
@@ -13,12 +13,11 @@
13
13
 
14
14
  import type {AssetPath} from './node-haste/lib/AssetPaths';
15
15
 
16
- const {isAssetTypeAnImage} = require('./Bundler/util');
17
16
  const AssetPaths = require('./node-haste/lib/AssetPaths');
18
17
  const crypto = require('crypto');
19
18
  const denodeify = require('denodeify');
20
19
  const fs = require('fs');
21
- const imageSize = require('image-size');
20
+ const getImageSize = require('image-size');
22
21
  const path = require('path');
23
22
 
24
23
  const readDir = denodeify(fs.readdir);
@@ -56,6 +55,40 @@ export type AssetDataFiltered = {
56
55
  ...
57
56
  };
58
57
 
58
+ // Test extension against all types supported by image-size module.
59
+ // If it's not one of these, we won't treat it as an image.
60
+ function isAssetTypeAnImage(type: string): boolean {
61
+ return (
62
+ [
63
+ 'png',
64
+ 'jpg',
65
+ 'jpeg',
66
+ 'bmp',
67
+ 'gif',
68
+ 'webp',
69
+ 'psd',
70
+ 'svg',
71
+ 'tiff',
72
+ 'ktx',
73
+ ].indexOf(type) !== -1
74
+ );
75
+ }
76
+
77
+ function getAssetSize(
78
+ type: string,
79
+ content: Buffer,
80
+ filePath: string,
81
+ ): ?{+width: number, +height: number} {
82
+ if (!isAssetTypeAnImage(type)) {
83
+ return null;
84
+ }
85
+ if (content.length === 0) {
86
+ throw new Error(`Image asset \`${filePath}\` cannot be an empty file.`);
87
+ }
88
+ const {width, height} = getImageSize(content);
89
+ return {width, height};
90
+ }
91
+
59
92
  export type AssetData = AssetDataWithoutFiles & {+files: Array<string>, ...};
60
93
 
61
94
  export type AssetDataPlugin = (
@@ -209,7 +242,7 @@ async function getAssetData(
209
242
  const isImageInput = assetInfo.files[0].includes('.zip/')
210
243
  ? fs.readFileSync(assetInfo.files[0])
211
244
  : assetInfo.files[0];
212
- const dimensions = isImage ? imageSize(isImageInput) : null;
245
+ const dimensions = isImage ? getImageSize(isImageInput) : null;
213
246
  const scale = assetInfo.scales[0];
214
247
 
215
248
  const assetData = {
@@ -317,6 +350,8 @@ function pathBelongsToRoots(
317
350
 
318
351
  module.exports = {
319
352
  getAsset,
353
+ getAssetSize,
320
354
  getAssetData,
321
355
  getAssetFiles,
356
+ isAssetTypeAnImage,
322
357
  };
@@ -14,10 +14,6 @@
14
14
  const babylon = require("@babel/parser");
15
15
  const template = require("@babel/template").default;
16
16
  const babelTypes = require("@babel/types");
17
- const nullthrows = require("nullthrows");
18
-
19
- // Structure of the object: dir.name.scale = asset
20
-
21
17
  const assetPropertyBlockList = new Set(["files", "fileSystemLocation", "path"]);
22
18
  function generateAssetCodeFileAst(assetRegistryPath, assetDescriptor) {
23
19
  const properDescriptor = filterObject(
@@ -44,72 +40,6 @@ function generateAssetCodeFileAst(assetRegistryPath, assetDescriptor) {
44
40
  ])
45
41
  );
46
42
  }
47
-
48
- /**
49
- * Generates the code involved in requiring an asset, but to be loaded remotely.
50
- * If the asset cannot be found within the map, then it falls back to the
51
- * standard asset.
52
- */
53
- function generateRemoteAssetCodeFileAst(
54
- assetUtilsPath,
55
- assetDescriptor,
56
- remoteServer,
57
- remoteFileMap
58
- ) {
59
- const t = babelTypes;
60
- const file = remoteFileMap[assetDescriptor.fileSystemLocation];
61
- const descriptor = file && file[assetDescriptor.name];
62
- const data = {};
63
- if (!descriptor) {
64
- return null;
65
- }
66
- for (const scale in descriptor) {
67
- data[+scale] = descriptor[+scale].handle;
68
- }
69
-
70
- // {2: 'path/to/image@2x', 3: 'path/to/image@3x', ...}
71
- const astData = babylon.parseExpression(JSON.stringify(data));
72
-
73
- // URI to remote server
74
- const URI = t.stringLiteral(remoteServer);
75
-
76
- // Size numbers.
77
- const WIDTH = t.numericLiteral(nullthrows(assetDescriptor.width));
78
- const HEIGHT = t.numericLiteral(nullthrows(assetDescriptor.height));
79
- const buildRequire = template.program(`
80
- const {pickScale, getUrlCacheBreaker}= require(ASSET_UTILS_PATH);
81
- module.exports = {
82
- "width": WIDTH,
83
- "height": HEIGHT,
84
- "uri": URI + OBJECT_AST[pickScale(SCALE_ARRAY)] + getUrlCacheBreaker()
85
- };
86
- `);
87
- return t.file(
88
- buildRequire({
89
- WIDTH,
90
- HEIGHT,
91
- URI,
92
- OBJECT_AST: astData,
93
- ASSET_UTILS_PATH: t.stringLiteral(assetUtilsPath),
94
- SCALE_ARRAY: t.arrayExpression(
95
- Object.keys(descriptor)
96
- .map(Number)
97
- .sort((a, b) => a - b)
98
- .map((scale) => t.numericLiteral(scale))
99
- ),
100
- })
101
- );
102
- }
103
-
104
- // Test extension against all types supported by image-size module.
105
- // If it's not one of these, we won't treat it as an image.
106
- function isAssetTypeAnImage(type) {
107
- return (
108
- ["png", "jpg", "jpeg", "bmp", "gif", "webp", "psd", "svg", "tiff"].indexOf(
109
- type
110
- ) !== -1
111
- );
112
- }
113
43
  function filterObject(object, blockList) {
114
44
  const copied = {
115
45
  ...object,
@@ -190,6 +120,4 @@ class ArrayMap extends Map {
190
120
  module.exports = {
191
121
  createRamBundleGroups,
192
122
  generateAssetCodeFileAst,
193
- generateRemoteAssetCodeFileAst,
194
- isAssetTypeAnImage,
195
123
  };
@@ -18,31 +18,6 @@ import type {File} from '@babel/types';
18
18
  const babylon = require('@babel/parser');
19
19
  const template = require('@babel/template').default;
20
20
  const babelTypes = require('@babel/types');
21
- const nullthrows = require('nullthrows');
22
-
23
- // Structure of the object: dir.name.scale = asset
24
- export type RemoteFileMap = {
25
- [string]: {
26
- [string]: {
27
- [number]: {
28
- handle: string,
29
- hash: string,
30
- ...
31
- },
32
- ...
33
- },
34
- ...
35
- },
36
- __proto__: null,
37
- ...
38
- };
39
-
40
- // Structure of the object: platform.dir.name.scale = asset
41
- export type PlatformRemoteFileMap = {
42
- [string]: RemoteFileMap,
43
- __proto__: null,
44
- ...
45
- };
46
21
 
47
22
  type SubTree<T: ModuleTransportLike> = (
48
23
  moduleTransport: T,
@@ -81,77 +56,6 @@ function generateAssetCodeFileAst(
81
56
  );
82
57
  }
83
58
 
84
- /**
85
- * Generates the code involved in requiring an asset, but to be loaded remotely.
86
- * If the asset cannot be found within the map, then it falls back to the
87
- * standard asset.
88
- */
89
- function generateRemoteAssetCodeFileAst(
90
- assetUtilsPath: string,
91
- assetDescriptor: AssetDataWithoutFiles,
92
- remoteServer: string,
93
- remoteFileMap: RemoteFileMap,
94
- ): ?File {
95
- const t = babelTypes;
96
-
97
- const file = remoteFileMap[assetDescriptor.fileSystemLocation];
98
- const descriptor = file && file[assetDescriptor.name];
99
- const data: {[number]: string} = {};
100
-
101
- if (!descriptor) {
102
- return null;
103
- }
104
-
105
- for (const scale in descriptor) {
106
- data[+scale] = descriptor[+scale].handle;
107
- }
108
-
109
- // {2: 'path/to/image@2x', 3: 'path/to/image@3x', ...}
110
- const astData = babylon.parseExpression(JSON.stringify(data));
111
-
112
- // URI to remote server
113
- const URI = t.stringLiteral(remoteServer);
114
-
115
- // Size numbers.
116
- const WIDTH = t.numericLiteral(nullthrows(assetDescriptor.width));
117
- const HEIGHT = t.numericLiteral(nullthrows(assetDescriptor.height));
118
-
119
- const buildRequire = template.program(`
120
- const {pickScale, getUrlCacheBreaker}= require(ASSET_UTILS_PATH);
121
- module.exports = {
122
- "width": WIDTH,
123
- "height": HEIGHT,
124
- "uri": URI + OBJECT_AST[pickScale(SCALE_ARRAY)] + getUrlCacheBreaker()
125
- };
126
- `);
127
-
128
- return t.file(
129
- buildRequire({
130
- WIDTH,
131
- HEIGHT,
132
- URI,
133
- OBJECT_AST: astData,
134
- ASSET_UTILS_PATH: t.stringLiteral(assetUtilsPath),
135
- SCALE_ARRAY: t.arrayExpression(
136
- Object.keys(descriptor)
137
- .map(Number)
138
- .sort((a: number, b: number) => a - b)
139
- .map((scale: number) => t.numericLiteral(scale)),
140
- ),
141
- }),
142
- );
143
- }
144
-
145
- // Test extension against all types supported by image-size module.
146
- // If it's not one of these, we won't treat it as an image.
147
- function isAssetTypeAnImage(type: string): boolean {
148
- return (
149
- ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff'].indexOf(
150
- type,
151
- ) !== -1
152
- );
153
- }
154
-
155
59
  function filterObject(
156
60
  object: AssetDataWithoutFiles,
157
61
  blockList: Set<string>,
@@ -246,6 +150,4 @@ class ArrayMap<K, V> extends Map<K, Array<V>> {
246
150
  module.exports = {
247
151
  createRamBundleGroups,
248
152
  generateAssetCodeFileAst,
249
- generateRemoteAssetCodeFileAst,
250
- isAssetTypeAnImage,
251
153
  };
@@ -277,10 +277,17 @@ class DeltaCalculator extends EventEmitter {
277
277
  reset: false,
278
278
  };
279
279
  }
280
+ debug("Traversing dependencies for %s paths", modifiedDependencies.length);
280
281
  const { added, modified, deleted } = await this._graph.traverseDependencies(
281
282
  modifiedDependencies,
282
283
  this._options
283
284
  );
285
+ debug(
286
+ "Calculated graph delta {added: %s, modified: %d, deleted: %d}",
287
+ added.size,
288
+ modified.size,
289
+ deleted.size
290
+ );
284
291
  return {
285
292
  added,
286
293
  modified,
@@ -323,10 +323,17 @@ class DeltaCalculator<T> extends EventEmitter {
323
323
  };
324
324
  }
325
325
 
326
+ debug('Traversing dependencies for %s paths', modifiedDependencies.length);
326
327
  const {added, modified, deleted} = await this._graph.traverseDependencies(
327
328
  modifiedDependencies,
328
329
  this._options,
329
330
  );
331
+ debug(
332
+ 'Calculated graph delta {added: %s, modified: %d, deleted: %d}',
333
+ added.size,
334
+ modified.size,
335
+ deleted.size,
336
+ );
330
337
 
331
338
  return {
332
339
  added,
@@ -134,10 +134,26 @@ class Graph {
134
134
  earlyInverseDependencies: new Map(),
135
135
  };
136
136
  const internalOptions = getInternalOptions(options);
137
+
138
+ // Record the paths that are part of the dependency graph before we start
139
+ // traversing - we'll use this to ensure we don't report modules modified
140
+ // that only exist as part of the graph mid-traversal, and to eliminate
141
+ // modules that end up in the same state that they started from the delta.
142
+ const originalModules = new Map();
137
143
  for (const path of paths) {
138
- // Start traversing from modules that are already part of the dependency graph.
139
- if (this.dependencies.get(path)) {
140
- delta.modified.add(path);
144
+ const originalModule = this.dependencies.get(path);
145
+ if (originalModule) {
146
+ originalModules.set(path, originalModule);
147
+ }
148
+ }
149
+ for (const [path] of originalModules) {
150
+ // Traverse over modules that are part of the dependency graph.
151
+ //
152
+ // Note: A given path may not be part of the graph *at this time*, in
153
+ // particular it may have been removed since we started traversing, but
154
+ // in that case the path will be visited if and when we add it back to
155
+ // the graph in a subsequent iteration.
156
+ if (this.dependencies.has(path)) {
141
157
  await this._traverseDependenciesForSingleFile(
142
158
  path,
143
159
  delta,
@@ -151,10 +167,41 @@ class Graph {
151
167
  added.set(path, nullthrows(this.dependencies.get(path)));
152
168
  }
153
169
  const modified = new Map();
154
- for (const path of delta.modified) {
155
- // Only report a module as modified if we're not already reporting it as added or deleted.
156
- if (!delta.added.has(path) && !delta.deleted.has(path)) {
157
- modified.set(path, nullthrows(this.dependencies.get(path)));
170
+
171
+ // A path in delta.modified has been processed during this traversal,
172
+ // but may not actually differ, may be new, or may have been deleted after
173
+ // processing. The actually-modified modules are the intersection of
174
+ // delta.modified with the pre-existing paths, minus modules deleted.
175
+ for (const [path, originalModule] of originalModules) {
176
+ invariant(
177
+ !delta.added.has(path),
178
+ "delta.added has %s, but this path was already in the graph.",
179
+ path
180
+ );
181
+ if (delta.modified.has(path)) {
182
+ // It's expected that a module may be both modified and subsequently
183
+ // deleted - we'll only return it as deleted.
184
+ if (!delta.deleted.has(path)) {
185
+ // If a module existed before and has not been deleted, it must be
186
+ // in the dependencies map.
187
+ const newModule = nullthrows(this.dependencies.get(path));
188
+ if (
189
+ // Module.dependencies is mutable, so it's not obviously the case
190
+ // that referential equality implies no modification. However, we
191
+ // only mutate dependencies in two cases:
192
+ // 1. Within _processModule. In that case, we always mutate a new
193
+ // module and set a new reference in this.dependencies.
194
+ // 2. During _releaseModule, when recursively removing
195
+ // dependencies. In that case, we immediately discard the module
196
+ // object.
197
+ // TODO: Refactor for more explicit immutability
198
+ newModule !== originalModule ||
199
+ transfromOutputMayDiffer(newModule, originalModule) ||
200
+ !allDependenciesEqual(newModule, originalModule)
201
+ ) {
202
+ modified.set(path, newModule);
203
+ }
204
+ }
158
205
  }
159
206
  }
160
207
  return {
@@ -203,6 +250,7 @@ class Graph {
203
250
  }
204
251
  async _processModule(path, delta, options) {
205
252
  const resolvedContext = this.#resolvedContexts.get(path);
253
+
206
254
  // Transform the file via the given option.
207
255
  // TODO: Unbind the transform method from options
208
256
  const result = await options.transform(path, resolvedContext);
@@ -214,47 +262,63 @@ class Graph {
214
262
  result.dependencies,
215
263
  options
216
264
  );
217
- const previousModule = this.dependencies.get(path) || {
218
- inverseDependencies:
219
- delta.earlyInverseDependencies.get(path) || new _CountingSet.default(),
220
- path,
221
- };
222
- const previousDependencies = previousModule.dependencies || new Map();
223
-
224
- // Update the module information.
225
- const module = {
226
- ...previousModule,
265
+ const previousModule = this.dependencies.get(path);
266
+ const previousDependencies = previousModule?.dependencies ?? new Map();
267
+ const nextModule = {
268
+ ...(previousModule ?? {
269
+ inverseDependencies:
270
+ delta.earlyInverseDependencies.get(path) ??
271
+ new _CountingSet.default(),
272
+ path,
273
+ }),
227
274
  dependencies: new Map(previousDependencies),
228
275
  getSource: result.getSource,
229
276
  output: result.output,
277
+ unstable_transformResultKey: result.unstable_transformResultKey,
230
278
  };
231
- this.dependencies.set(module.path, module);
279
+
280
+ // Update the module information.
281
+ this.dependencies.set(nextModule.path, nextModule);
232
282
 
233
283
  // Diff dependencies (1/2): remove dependencies that have changed or been removed.
284
+ let dependenciesRemoved = false;
234
285
  for (const [key, prevDependency] of previousDependencies) {
235
286
  const curDependency = currentDependencies.get(key);
236
287
  if (
237
288
  !curDependency ||
238
289
  !dependenciesEqual(prevDependency, curDependency, options)
239
290
  ) {
240
- this._removeDependency(module, key, prevDependency, delta, options);
291
+ dependenciesRemoved = true;
292
+ this._removeDependency(nextModule, key, prevDependency, delta, options);
241
293
  }
242
294
  }
243
295
 
244
296
  // Diff dependencies (2/2): add dependencies that have changed or been added.
245
- const promises = [];
297
+ const addDependencyPromises = [];
246
298
  for (const [key, curDependency] of currentDependencies) {
247
299
  const prevDependency = previousDependencies.get(key);
248
300
  if (
249
301
  !prevDependency ||
250
302
  !dependenciesEqual(prevDependency, curDependency, options)
251
303
  ) {
252
- promises.push(
253
- this._addDependency(module, key, curDependency, delta, options)
304
+ addDependencyPromises.push(
305
+ this._addDependency(nextModule, key, curDependency, delta, options)
254
306
  );
255
307
  }
256
308
  }
257
- await Promise.all(promises);
309
+ if (
310
+ previousModule &&
311
+ !transfromOutputMayDiffer(previousModule, nextModule) &&
312
+ !dependenciesRemoved &&
313
+ addDependencyPromises.length === 0
314
+ ) {
315
+ // We have not operated on nextModule, so restore previousModule
316
+ // to aid diffing.
317
+ this.dependencies.set(previousModule.path, previousModule);
318
+ return previousModule;
319
+ }
320
+ delta.modified.add(path);
321
+ await Promise.all(addDependencyPromises);
258
322
 
259
323
  // Replace dependencies with the correctly-ordered version. As long as all
260
324
  // the above promises have resolved, this will be the same map but without
@@ -264,11 +328,11 @@ class Graph {
264
328
 
265
329
  // Catch obvious errors with a cheap assertion.
266
330
  invariant(
267
- module.dependencies.size === currentDependencies.size,
331
+ nextModule.dependencies.size === currentDependencies.size,
268
332
  "Failed to add the correct dependencies"
269
333
  );
270
- module.dependencies = currentDependencies;
271
- return module;
334
+ nextModule.dependencies = currentDependencies;
335
+ return nextModule;
272
336
  }
273
337
  async _addDependency(parentModule, key, dependency, delta, options) {
274
338
  const path = dependency.absolutePath;
@@ -659,6 +723,18 @@ function dependenciesEqual(a, b, options) {
659
723
  contextParamsEqual(a.data.data.contextParams, b.data.data.contextParams))
660
724
  );
661
725
  }
726
+ function allDependenciesEqual(a, b, options) {
727
+ if (a.dependencies.size !== b.dependencies.size) {
728
+ return false;
729
+ }
730
+ for (const [key, depA] of a.dependencies) {
731
+ const depB = b.dependencies.get(key);
732
+ if (!depB || !dependenciesEqual(depA, depB, options)) {
733
+ return false;
734
+ }
735
+ }
736
+ return true;
737
+ }
662
738
  function contextParamsEqual(a, b) {
663
739
  return (
664
740
  a === b ||
@@ -671,3 +747,9 @@ function contextParamsEqual(a, b) {
671
747
  a.mode === b.mode)
672
748
  );
673
749
  }
750
+ function transfromOutputMayDiffer(a, b) {
751
+ return (
752
+ a.unstable_transformResultKey == null ||
753
+ a.unstable_transformResultKey !== b.unstable_transformResultKey
754
+ );
755
+ }