Skip to content

Commit fc29a11

Browse files
motiz88facebook-github-bot
authored andcommittedJun 16, 2022
Fix incremental build bug with parallel edges to the same module
Summary: When two imports from the same origin module resolve to the same target module, Metro can get a little confused. Consider the following example: ``` import './Module/index'; ┌─────────────────────────────────────────┐ │ ▼ ┌───────────┐ import './Module'; ┌──────────────────┐ │ /Entry.js │ ──────────────────────────▶ │ /Module/index.js │ └───────────┘ └──────────────────┘ ``` If, while the app is running, we delete *one* of the imports but not the other, Metro will stop correctly propagating updates of `/Module/index.js` to its "parent", `/Entry.js`. Fast Refresh will **appear** to work (the UI indicator will flash briefly) but the app will reflect the *old* contents of `/Module/index.js`. This happens because DeltaBundler's data structure for keeping track of a module's inverse dependencies is a set keyed on absolute paths, which doesn't account for parallel edges ( = multiple inverse dependencies from the same origin module). Here we change this data structure to a `CountingSet` - a modified `Set` that only deletes items when the number of `delete(item)` calls matches the number of `add(item)` calls. Reviewed By: huntie Differential Revision: D37194640 fbshipit-source-id: 89c190b0de3ec75b533f2d41a7024f17d31c59b0
1 parent 3877d2d commit fc29a11

File tree

11 files changed

+462
-24
lines changed

11 files changed

+462
-24
lines changed
 

‎packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/bytecode-test.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
'use strict';
1313

14+
import CountingSet from '../../../../lib/CountingSet';
15+
1416
const createModuleIdFactory = require('../../../../lib/createModuleIdFactory');
1517
const {wrapModule} = require('../bytecode');
1618
const {compile, validateBytecodeModule} = require('metro-hermes-compiler');
@@ -43,7 +45,7 @@ beforeEach(() => {
4345
],
4446
]),
4547
getSource: () => Buffer.from(''),
46-
inverseDependencies: new Set(),
48+
inverseDependencies: new CountingSet(),
4749
output: [
4850
{
4951
data: {

‎packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
'use strict';
1313

14+
import CountingSet from '../../../../lib/CountingSet';
15+
1416
const createModuleIdFactory = require('../../../../lib/createModuleIdFactory');
1517
const {wrapModule} = require('../js');
1618

@@ -36,7 +38,7 @@ beforeEach(() => {
3638
],
3739
]),
3840
getSource: () => Buffer.from(''),
39-
inverseDependencies: new Set(),
41+
inverseDependencies: new CountingSet(),
4042
output: [
4143
{
4244
data: {

‎packages/metro/src/DeltaBundler/__tests__/__snapshots__/traverseDependencies-test.js.snap

+8-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Object {
1717
},
1818
},
1919
"getSource": [Function],
20-
"inverseDependencies": Set {},
20+
"inverseDependencies": Array [],
2121
"output": Array [
2222
Object {
2323
"data": Object {
@@ -54,9 +54,9 @@ Object {
5454
},
5555
},
5656
"getSource": [Function],
57-
"inverseDependencies": Set {
57+
"inverseDependencies": Array [
5858
"/bundle",
59-
},
59+
],
6060
"output": Array [
6161
Object {
6262
"data": Object {
@@ -72,9 +72,9 @@ Object {
7272
"/bar" => Object {
7373
"dependencies": Map {},
7474
"getSource": [Function],
75-
"inverseDependencies": Set {
75+
"inverseDependencies": Array [
7676
"/foo",
77-
},
77+
],
7878
"output": Array [
7979
Object {
8080
"data": Object {
@@ -90,9 +90,9 @@ Object {
9090
"/baz" => Object {
9191
"dependencies": Map {},
9292
"getSource": [Function],
93-
"inverseDependencies": Set {
93+
"inverseDependencies": Array [
9494
"/foo",
95-
},
95+
],
9696
"output": Array [
9797
Object {
9898
"data": Object {
@@ -139,7 +139,7 @@ Object {
139139
},
140140
},
141141
"getSource": [Function],
142-
"inverseDependencies": Set {},
142+
"inverseDependencies": Array [],
143143
"output": Array [
144144
Object {
145145
"data": Object {

‎packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js

+101-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import type {Graph, TransformResultDependency} from '../types.flow';
1313

14+
import CountingSet from '../../lib/CountingSet';
1415
import nullthrows from 'nullthrows';
1516

1617
const {
@@ -202,7 +203,7 @@ async function traverseDependencies(paths, graph, options) {
202203
);
203204
const actualInverseDependencies = new Map();
204205
for (const [path, module] of graph.dependencies) {
205-
actualInverseDependencies.set(path, module.inverseDependencies);
206+
actualInverseDependencies.set(path, new Set(module.inverseDependencies));
206207
}
207208
expect(actualInverseDependencies).toEqual(expectedInverseDependencies);
208209

@@ -314,7 +315,7 @@ it('should populate all the inverse dependencies', async () => {
314315

315316
expect(
316317
nullthrows(graph.dependencies.get('/bar')).inverseDependencies,
317-
).toEqual(new Set(['/foo', '/bundle']));
318+
).toEqual(new CountingSet(['/foo', '/bundle']));
318319
});
319320

320321
it('should return an empty result when there are no changes', async () => {
@@ -501,7 +502,7 @@ describe('edge cases', () => {
501502

502503
expect(
503504
nullthrows(graph.dependencies.get('/foo')).inverseDependencies,
504-
).toEqual(new Set(['/bundle', '/baz']));
505+
).toEqual(new CountingSet(['/baz', '/bundle']));
505506
});
506507

507508
it('should handle renames correctly', async () => {
@@ -2049,7 +2050,7 @@ describe('reorderGraph', () => {
20492050
getSource: () => Buffer.from('// source'),
20502051
// NOTE: inverseDependencies is traversal state/output, not input, so we
20512052
// don't pre-populate it.
2052-
inverseDependencies: new Set(),
2053+
inverseDependencies: new CountingSet(),
20532054
});
20542055

20552056
const graph = createGraph({
@@ -2168,3 +2169,99 @@ describe('optional dependencies', () => {
21682169
).rejects.toThrow();
21692170
});
21702171
});
2172+
2173+
describe('parallel edges', () => {
2174+
it('add twice w/ same key, build and remove once', async () => {
2175+
// Create a second edge between /foo and /bar.
2176+
Actions.addDependency('/foo', '/bar', undefined);
2177+
2178+
await initialTraverseDependencies(graph, options);
2179+
2180+
const fooDeps = nullthrows(graph.dependencies.get('/foo')).dependencies;
2181+
const fooDepsResolved = [...fooDeps.values()].map(dep => dep.absolutePath);
2182+
// We dedupe the dependencies because they have the same `name`.
2183+
expect(fooDepsResolved).toEqual(['/bar', '/baz']);
2184+
2185+
// Remove one of the edges between /foo and /bar (arbitrarily)
2186+
Actions.removeDependency('/foo', '/bar');
2187+
2188+
expect(
2189+
getPaths(await traverseDependencies([...files], graph, options)),
2190+
).toEqual({
2191+
added: new Set(),
2192+
modified: new Set(['/foo']),
2193+
deleted: new Set(),
2194+
});
2195+
});
2196+
2197+
it('add twice w/ same key, build and remove twice', async () => {
2198+
// Create a second edge between /foo and /bar.
2199+
Actions.addDependency('/foo', '/bar', undefined);
2200+
2201+
await initialTraverseDependencies(graph, options);
2202+
2203+
const fooDeps = nullthrows(graph.dependencies.get('/foo')).dependencies;
2204+
const fooDepsResolved = [...fooDeps.values()].map(dep => dep.absolutePath);
2205+
// We dedupe the dependencies because they have the same `name`.
2206+
expect(fooDepsResolved).toEqual(['/bar', '/baz']);
2207+
2208+
// Remove both edges between /foo and /bar
2209+
Actions.removeDependency('/foo', '/bar');
2210+
Actions.removeDependency('/foo', '/bar');
2211+
2212+
expect(
2213+
getPaths(await traverseDependencies([...files], graph, options)),
2214+
).toEqual({
2215+
added: new Set(),
2216+
modified: new Set(['/foo']),
2217+
deleted: new Set(['/bar']),
2218+
});
2219+
});
2220+
2221+
it('add twice w/ different keys, build and remove once', async () => {
2222+
// Create a second edge between /foo and /bar, with a different `name`.
2223+
Actions.addDependency('/foo', '/bar', undefined, 'bar-second');
2224+
2225+
await initialTraverseDependencies(graph, options);
2226+
2227+
const fooDeps = nullthrows(graph.dependencies.get('/foo')).dependencies;
2228+
const fooDepsResolved = [...fooDeps.values()].map(dep => dep.absolutePath);
2229+
// We don't dedupe the dependencies because they have different `name`s.
2230+
expect(fooDepsResolved).toEqual(['/bar', '/baz', '/bar']);
2231+
2232+
// Remove one of the edges between /foo and /bar (arbitrarily)
2233+
Actions.removeDependency('/foo', '/bar');
2234+
2235+
expect(
2236+
getPaths(await traverseDependencies([...files], graph, options)),
2237+
).toEqual({
2238+
added: new Set(),
2239+
modified: new Set(['/foo']),
2240+
deleted: new Set(),
2241+
});
2242+
});
2243+
2244+
it('add twice w/ different keys, build and remove twice', async () => {
2245+
// Create a second edge between /foo and /bar, with a different `name`.
2246+
Actions.addDependency('/foo', '/bar', undefined, 'bar-second');
2247+
2248+
await initialTraverseDependencies(graph, options);
2249+
2250+
const fooDeps = nullthrows(graph.dependencies.get('/foo')).dependencies;
2251+
const fooDepsResolved = [...fooDeps.values()].map(dep => dep.absolutePath);
2252+
// We don't dedupe the dependencies because they have different `name`s.
2253+
expect(fooDepsResolved).toEqual(['/bar', '/baz', '/bar']);
2254+
2255+
// Remove both edges between /foo and /bar
2256+
Actions.removeDependency('/foo', '/bar');
2257+
Actions.removeDependency('/foo', '/bar');
2258+
2259+
expect(
2260+
getPaths(await traverseDependencies([...files], graph, options)),
2261+
).toEqual({
2262+
added: new Set(),
2263+
modified: new Set(['/foo']),
2264+
deleted: new Set(['/bar']),
2265+
});
2266+
});
2267+
});

‎packages/metro/src/DeltaBundler/graphOperations.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import type {
3939
TransformResultDependency,
4040
} from './types.flow';
4141

42+
import CountingSet from '../lib/CountingSet';
43+
4244
const invariant = require('invariant');
4345
const nullthrows = require('nullthrows');
4446

@@ -104,7 +106,7 @@ type Delta = $ReadOnly<{
104106

105107
// A place to temporarily track inverse dependencies for a module while it is
106108
// being processed but has not been added to `graph.dependencies` yet.
107-
earlyInverseDependencies: Map<string, Set<string>>,
109+
earlyInverseDependencies: Map<string, CountingSet<string>>,
108110
}>;
109111

110112
type InternalOptions<T> = $ReadOnly<{
@@ -277,7 +279,8 @@ async function processModule<T>(
277279
);
278280

279281
const previousModule = graph.dependencies.get(path) || {
280-
inverseDependencies: delta.earlyInverseDependencies.get(path) || new Set(),
282+
inverseDependencies:
283+
delta.earlyInverseDependencies.get(path) || new CountingSet(),
281284
path,
282285
};
283286
const previousDependencies = previousModule.dependencies || new Map();
@@ -397,7 +400,7 @@ async function addDependency<T>(
397400
delta.added.add(path);
398401
delta.modified.delete(path);
399402
}
400-
delta.earlyInverseDependencies.set(path, new Set([parentModule.path]));
403+
delta.earlyInverseDependencies.set(path, new CountingSet());
401404

402405
options.onDependencyAdd();
403406
module = await processModule(path, graph, delta, options);

‎packages/metro/src/DeltaBundler/types.flow.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import type {RequireContextParams} from '../ModuleGraph/worker/collectDependenci
1414
import type {PrivateState} from './graphOperations';
1515
import type {JsTransformOptions} from 'metro-transform-worker';
1616

17+
import CountingSet from '../lib/CountingSet';
18+
1719
export type MixedOutput = {
1820
+data: mixed,
1921
+type: string,
@@ -62,7 +64,7 @@ export type Dependency = {
6264

6365
export type Module<T = MixedOutput> = {
6466
+dependencies: Map<string, Dependency>,
65-
+inverseDependencies: Set<string>,
67+
+inverseDependencies: CountingSet<string>,
6668
+output: $ReadOnlyArray<T>,
6769
+path: string,
6870
+getSource: () => Buffer,

‎packages/metro/src/integration_tests/__tests__/buildGraph-test.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
'use strict';
1212

13+
import CountingSet from '../../lib/CountingSet';
14+
1315
const Metro = require('../../..');
1416
const path = require('path');
1517

@@ -50,7 +52,7 @@ it('should build the dependency graph', async () => {
5052
expect(graph.dependencies.get(entryPoint)).toEqual(
5153
expect.objectContaining({
5254
path: entryPoint,
53-
inverseDependencies: new Set(),
55+
inverseDependencies: new CountingSet(),
5456
output: [
5557
expect.objectContaining({
5658
type: 'js/module',

‎packages/metro/src/lib/CountingSet.js

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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+
*/
10+
11+
export interface ReadOnlyCountingSet<T> extends Iterable<T> {
12+
has(item: T): boolean;
13+
@@iterator(): Iterator<T>;
14+
+size: number;
15+
count(item: T): number;
16+
forEach<ThisT>(
17+
callbackFn: (
18+
this: ThisT,
19+
value: T,
20+
key: T,
21+
set: ReadOnlyCountingSet<T>,
22+
) => mixed,
23+
24+
// NOTE: Should be optional, but Flow seems happy to infer undefined here
25+
// which is what we want.
26+
thisArg: ThisT,
27+
): void;
28+
}
29+
30+
/**
31+
* A Set that only deletes a given item when the number of delete(item) calls
32+
* matches the number of add(item) calls. Iteration and `size` are in terms of
33+
* *unique* items.
34+
*/
35+
export default class CountingSet<T> implements ReadOnlyCountingSet<T> {
36+
#map: Map<T, number> = new Map();
37+
38+
constructor(items?: Iterable<T>) {
39+
if (items) {
40+
if (items instanceof CountingSet) {
41+
this.#map = new Map(items.#map);
42+
} else {
43+
for (const item of items) {
44+
this.add(item);
45+
}
46+
}
47+
}
48+
}
49+
50+
has(item: T): boolean {
51+
return this.#map.has(item);
52+
}
53+
54+
add(item: T): void {
55+
const newCount = this.count(item) + 1;
56+
this.#map.set(item, newCount);
57+
}
58+
59+
delete(item: T): void {
60+
const newCount = this.count(item) - 1;
61+
if (newCount <= 0) {
62+
this.#map.delete(item);
63+
} else {
64+
this.#map.set(item, newCount);
65+
}
66+
}
67+
68+
keys(): Iterator<T> {
69+
return this.#map.keys();
70+
}
71+
72+
values(): Iterator<T> {
73+
return this.#map.keys();
74+
}
75+
76+
*entries(): Iterator<[T, T]> {
77+
for (const item of this) {
78+
yield [item, item];
79+
}
80+
}
81+
82+
// Iterate over unique entries
83+
// $FlowIssue[unsupported-syntax]
84+
[Symbol.iterator](): Iterator<T> {
85+
return this.values();
86+
}
87+
88+
/*::
89+
// For Flow's benefit
90+
@@iterator(): Iterator<T> {
91+
return this.values();
92+
}
93+
*/
94+
95+
// Number of unique entries
96+
// $FlowIssue[unsafe-getters-setters]
97+
get size(): number {
98+
return this.#map.size;
99+
}
100+
101+
count(item: T): number {
102+
return this.#map.get(item) ?? 0;
103+
}
104+
105+
clear(): void {
106+
this.#map.clear();
107+
}
108+
109+
forEach<ThisT>(
110+
callbackFn: (this: ThisT, value: T, key: T, set: CountingSet<T>) => mixed,
111+
thisArg: ThisT,
112+
): void {
113+
for (const item of this) {
114+
callbackFn.call(thisArg, item, item, this);
115+
}
116+
}
117+
118+
// For Jest purposes. Ideally a custom serializer would be enough, but in
119+
// practice there is hardcoded magic for Set in toEqual (etc) that we cannot
120+
// extend to custom collection classes. Instead let's assume values are
121+
// sortable ( = strings) and make this look like an array with some stable
122+
// order.
123+
toJSON(): mixed {
124+
return [...this].sort();
125+
}
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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+
* @emails oncall+metro_bundler
10+
*/
11+
12+
import CountingSet from '../CountingSet';
13+
14+
describe('CountingSet', () => {
15+
test('basic add/delete', () => {
16+
const set = new CountingSet();
17+
18+
set.add('a');
19+
expect(set.has('a')).toBe(true);
20+
expect(set.count('a')).toBe(1);
21+
expect(set.size).toBe(1);
22+
23+
set.delete('a');
24+
expect(set.has('a')).toBe(false);
25+
expect(set.count('a')).toBe(0);
26+
expect(set.size).toBe(0);
27+
});
28+
29+
test('multiple add/delete', () => {
30+
const set = new CountingSet();
31+
32+
set.add('a');
33+
set.add('a');
34+
expect(set.has('a')).toBe(true);
35+
expect(set.count('a')).toBe(2);
36+
expect(set.size).toBe(1);
37+
38+
set.delete('a');
39+
expect(set.has('a')).toBe(true);
40+
expect(set.count('a')).toBe(1);
41+
expect(set.size).toBe(1);
42+
43+
set.delete('a');
44+
expect(set.has('a')).toBe(false);
45+
expect(set.count('a')).toBe(0);
46+
expect(set.size).toBe(0);
47+
});
48+
49+
test('more deletes than adds', () => {
50+
const set = new CountingSet();
51+
52+
set.add('a');
53+
set.delete('a');
54+
set.delete('a');
55+
expect(set.has('a')).toBe(false);
56+
expect(set.count('a')).toBe(0);
57+
expect(set.size).toBe(0);
58+
});
59+
60+
test('delete nonexistent value', () => {
61+
const set = new CountingSet();
62+
63+
set.delete('a');
64+
expect(set.has('a')).toBe(false);
65+
expect(set.count('a')).toBe(0);
66+
expect(set.size).toBe(0);
67+
});
68+
69+
test('construct from array', () => {
70+
const set = new CountingSet(['a', 'b', 'c', 'a']);
71+
expect(set.has('a')).toBe(true);
72+
expect(set.has('b')).toBe(true);
73+
expect(set.has('c')).toBe(true);
74+
expect(set.count('a')).toBe(2);
75+
expect(set.count('b')).toBe(1);
76+
expect(set.count('c')).toBe(1);
77+
expect(set.size).toBe(3);
78+
});
79+
80+
test('construct from Set', () => {
81+
const set = new CountingSet(new Set(['a', 'b', 'c']));
82+
expect(set.has('a')).toBe(true);
83+
expect(set.has('b')).toBe(true);
84+
expect(set.has('c')).toBe(true);
85+
expect(set.count('a')).toBe(1);
86+
expect(set.count('b')).toBe(1);
87+
expect(set.count('c')).toBe(1);
88+
expect(set.size).toBe(3);
89+
});
90+
91+
test('construct from CountingSet', () => {
92+
const originalSet = new CountingSet(['a', 'a', 'b', 'c']);
93+
const set = new CountingSet(originalSet);
94+
originalSet.clear();
95+
96+
expect(set.has('a')).toBe(true);
97+
expect(set.has('b')).toBe(true);
98+
expect(set.has('c')).toBe(true);
99+
expect(set.count('a')).toBe(2);
100+
expect(set.count('b')).toBe(1);
101+
expect(set.count('c')).toBe(1);
102+
expect(set.size).toBe(3);
103+
});
104+
105+
test('clear', () => {
106+
const set = new CountingSet(['a', 'a', 'b', 'c']);
107+
108+
set.clear();
109+
expect(set.size).toBe(0);
110+
expect(set.has('a')).toBe(false);
111+
expect(set.count('a')).toBe(0);
112+
expect(set.has('b')).toBe(false);
113+
expect(set.count('b')).toBe(0);
114+
expect(set.has('c')).toBe(false);
115+
expect(set.count('c')).toBe(0);
116+
});
117+
118+
test('forEach', () => {
119+
const set = new CountingSet(['a', 'a', 'b', 'c']);
120+
// TODO: Migrate to callback.mock.contexts when we upgrade to Jest 28
121+
const contexts = [];
122+
const callback = jest.fn(function captureContext() {
123+
contexts.push(this);
124+
});
125+
126+
set.forEach(callback);
127+
expect(callback.mock.calls).toEqual([
128+
['a', 'a', set],
129+
['b', 'b', set],
130+
['c', 'c', set],
131+
]);
132+
expect(contexts).toEqual([undefined, undefined, undefined]);
133+
});
134+
135+
test('forEach with context', () => {
136+
const set = new CountingSet(['a', 'a', 'b', 'c']);
137+
// TODO: Migrate to callback.mock.contexts when we upgrade to Jest 28
138+
const contexts = [];
139+
const callback = jest.fn(function captureContext() {
140+
contexts.push(this);
141+
});
142+
143+
const context = {};
144+
set.forEach(callback, context);
145+
expect(callback.mock.calls).toEqual([
146+
['a', 'a', set],
147+
['b', 'b', set],
148+
['c', 'c', set],
149+
]);
150+
expect(contexts).toEqual([context, context, context]);
151+
});
152+
153+
test('spread', () => {
154+
const set = new CountingSet();
155+
156+
set.add('a');
157+
set.add('a');
158+
set.add('b');
159+
set.add('c');
160+
161+
expect([...set]).toEqual(['a', 'b', 'c']);
162+
});
163+
164+
test('keys()', () => {
165+
const set = new CountingSet();
166+
167+
set.add('a');
168+
set.add('a');
169+
set.add('b');
170+
set.add('c');
171+
172+
expect([...set.keys()]).toEqual(['a', 'b', 'c']);
173+
});
174+
175+
test('values()', () => {
176+
const set = new CountingSet();
177+
178+
set.add('a');
179+
set.add('a');
180+
set.add('b');
181+
set.add('c');
182+
183+
expect([...set.values()]).toEqual(['a', 'b', 'c']);
184+
});
185+
186+
test('entries()', () => {
187+
const set = new CountingSet();
188+
189+
set.add('a');
190+
set.add('a');
191+
set.add('b');
192+
set.add('c');
193+
194+
expect([...set.entries()]).toEqual([
195+
['a', 'a'],
196+
['b', 'b'],
197+
['c', 'c'],
198+
]);
199+
});
200+
});

‎packages/metro/src/lib/getAppendScripts.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
import type {Module} from '../DeltaBundler';
1414

15+
import CountingSet from './CountingSet';
16+
1517
const getInlineSourceMappingURL = require('../DeltaBundler/Serializers/helpers/getInlineSourceMappingURL');
1618
const sourceMapString = require('../DeltaBundler/Serializers/sourceMapString');
1719
const countLines = require('./countLines');
@@ -56,7 +58,7 @@ function getAppendScripts<T: number | string>(
5658
path: '$$importBundleNames',
5759
dependencies: new Map(),
5860
getSource: (): Buffer => Buffer.from(''),
59-
inverseDependencies: new Set(),
61+
inverseDependencies: new CountingSet(),
6062
output: [
6163
{
6264
type: 'js/script/virtual',
@@ -82,7 +84,7 @@ function getAppendScripts<T: number | string>(
8284
path: `require-${path}`,
8385
dependencies: new Map(),
8486
getSource: (): Buffer => Buffer.from(''),
85-
inverseDependencies: new Set(),
87+
inverseDependencies: new CountingSet(),
8688
output: [
8789
{
8890
type: 'js/script/virtual',
@@ -113,7 +115,7 @@ function getAppendScripts<T: number | string>(
113115
path: 'source-map',
114116
dependencies: new Map(),
115117
getSource: (): Buffer => Buffer.from(''),
116-
inverseDependencies: new Set(),
118+
inverseDependencies: new CountingSet(),
117119
output: [
118120
{
119121
type: 'js/script/virtual',
@@ -133,7 +135,7 @@ function getAppendScripts<T: number | string>(
133135
path: 'source-url',
134136
dependencies: new Map(),
135137
getSource: (): Buffer => Buffer.from(''),
136-
inverseDependencies: new Set(),
138+
inverseDependencies: new CountingSet(),
137139
output: [
138140
{
139141
type: 'js/script/virtual',

‎packages/metro/src/lib/getPrependedScripts.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import type DeltaBundler, {Module} from '../DeltaBundler';
1515
import type {TransformInputOptions} from '../DeltaBundler/types.flow';
1616
import type {ConfigT} from 'metro-config/src/configTypes.flow';
1717

18+
import CountingSet from './CountingSet';
19+
1820
const countLines = require('./countLines');
1921
const getPreludeCode = require('./getPreludeCode');
2022
const transformHelpers = require('./transformHelpers');
@@ -88,7 +90,7 @@ function _getPrelude({
8890
return {
8991
dependencies: new Map(),
9092
getSource: (): Buffer => Buffer.from(code),
91-
inverseDependencies: new Set(),
93+
inverseDependencies: new CountingSet(),
9294
path: name,
9395
output: [
9496
{

0 commit comments

Comments
 (0)
Please sign in to comment.