Skip to content

Commit 6aa3248

Browse files
sonukapoorSonu KapoorTrickyPilukastaegert
authored
fix: stabilize chunk assignment across parallel file reads (#6362)
* fix: make chunk assignment and export naming deterministic (#5902) Rollup stores parsed modules in a Map<string, Module> (modulesById). With parallel file reads, modules are inserted in whichever order their resolveId hooks complete, making iteration order non-deterministic across builds. Two code paths depended on this order: - Bundle.assignManualChunks iterated modulesById.values() directly, so a stateful manualChunks function produced different chunk assignments on different runs. Fix: sort modules alphabetically by ID before iterating. - assignExportsToMangledNames and assignExportsToNames iterated the chunk exports Set in insertion order. Fix: sort exports before assigning aliases using a stable comparator: module ID → source declaration position → base name → export key → constructor name. Synthetic variables (NamespaceVariable etc.) have no source declaration and receive Number.MAX_SAFE_INTEGER as their position so they always sort after regular variables, preventing them from stealing natural export names in CJS/AMD/UMD formats. Adds a regression test that builds the same graph twice with different resolveId delay patterns and asserts identical chunk names, hashes, and code. Updates five chunking-form snapshots whose previous expected output reflected arbitrary insertion order rather than the new deterministic sort (ES and System formats only). * Add a regression test and refine the fix * Remove unnecessary options in the test * Also add a test with mangled exports * Make sorting more stable * Fix tests --------- Co-authored-by: Sonu Kapoor <sonu@bitovi.com> Co-authored-by: TrickyPi <530257315@qq.com> Co-authored-by: Lukas Taegert-Atkinson <lukas.taegert-atkinson@tngtech.com> Co-authored-by: Lukas Taegert-Atkinson <lukastaegert@users.noreply.github.com>
1 parent 82a0fe7 commit 6aa3248

26 files changed

Lines changed: 148 additions & 35 deletions

File tree

src/utils/exportNames.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import type Variable from '../ast/variables/Variable';
2-
import RESERVED_NAMES from './RESERVED_NAMES';
32
import { toBase64 } from './base64';
3+
import RESERVED_NAMES from './RESERVED_NAMES';
44

55
export function assignExportsToMangledNames(
66
exports: ReadonlySet<Variable>,
77
exportsByName: Map<string, Variable>,
88
exportNamesByVariable: Map<Variable, string[]>
99
): void {
1010
let nameIndex = 0;
11-
for (const variable of exports) {
11+
const sortedExports = [...exports].sort((a, b) =>
12+
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
13+
);
14+
for (const variable of sortedExports) {
1215
let [exportName] = variable.name;
1316
if (exportsByName.has(exportName)) {
1417
do {

test/chunking-form/samples/dynamic-import-facade/_expected/es/generated-dynamic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ var dynamic$1 = /*#__PURE__*/Object.freeze({
1010
dynamic: dynamic
1111
});
1212

13-
export { dep as a, dynamic$1 as b, dynamic as d };
13+
export { dynamic as a, dynamic$1 as b, dep as d };
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
import { d as dynamic, a as dep } from './generated-dynamic.js';
1+
import { a as dynamic, d as dep } from './generated-dynamic.js';
22

33
console.log('main2', dynamic, dep);

test/chunking-form/samples/dynamic-import-facade/_expected/system/generated-dynamic.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ System.register([], (function (exports) {
55

66
console.log('dep');
77

8-
const dep = exports("a", 'dep');
8+
const dep = exports("d", 'dep');
99

1010
console.log('dynamic', dep);
11-
const dynamic = exports("d", 'dynamic');
11+
const dynamic = exports("a", 'dynamic');
1212

1313
var dynamic$1 = /*#__PURE__*/Object.freeze({
1414
__proto__: null,

test/chunking-form/samples/dynamic-import-facade/_expected/system/main2.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ System.register(['./generated-dynamic.js'], (function () {
33
var dynamic, dep;
44
return {
55
setters: [function (module) {
6-
dynamic = module.d;
7-
dep = module.a;
6+
dynamic = module.a;
7+
dep = module.d;
88
}],
99
execute: (function () {
1010

test/chunking-form/samples/manual-chunks-dynamic-facades/_expected/es/generated-dynamic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ var dynamic1 = /*#__PURE__*/Object.freeze({
2424
DYNAMIC_3: DYNAMIC_3
2525
});
2626

27-
export { DEP as D, dynamic3 as a, dynamic1 as b, dynamic2 as d };
27+
export { DEP as D, dynamic2 as a, dynamic3 as b, dynamic1 as d };
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { D as DEP } from './generated-dynamic.js';
22

3-
Promise.all([import('./generated-dynamic.js').then(function (n) { return n.b; }), import('./generated-dynamic.js').then(function (n) { return n.d; }), import('./generated-dynamic.js').then(function (n) { return n.a; })]).then(
3+
Promise.all([import('./generated-dynamic.js').then(function (n) { return n.d; }), import('./generated-dynamic.js').then(function (n) { return n.a; }), import('./generated-dynamic.js').then(function (n) { return n.b; })]).then(
44
results => console.log(results, DEP)
55
);

test/chunking-form/samples/manual-chunks-dynamic-facades/_expected/system/generated-dynamic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ System.register([], (function (exports) {
1111
__proto__: null,
1212
DYNAMIC_2: DYNAMIC_2
1313
});
14-
exports("d", dynamic2);
14+
exports("a", dynamic2);
1515

1616
const DYNAMIC_3 = 'DYNAMIC_3';
1717

1818
var dynamic3 = /*#__PURE__*/Object.freeze({
1919
__proto__: null,
2020
DYNAMIC_3: DYNAMIC_3
2121
});
22-
exports("a", dynamic3);
22+
exports("b", dynamic3);
2323

2424
const DYNAMIC_1 = 'DYNAMIC_1';
2525

@@ -30,7 +30,7 @@ System.register([], (function (exports) {
3030
DYNAMIC_2: DYNAMIC_2,
3131
DYNAMIC_3: DYNAMIC_3
3232
});
33-
exports("b", dynamic1);
33+
exports("d", dynamic1);
3434

3535
})
3636
};

test/chunking-form/samples/manual-chunks-dynamic-facades/_expected/system/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ System.register(['./generated-dynamic.js'], (function (exports, module) {
77
}],
88
execute: (function () {
99

10-
Promise.all([module.import('./generated-dynamic.js').then(function (n) { return n.b; }), module.import('./generated-dynamic.js').then(function (n) { return n.d; }), module.import('./generated-dynamic.js').then(function (n) { return n.a; })]).then(
10+
Promise.all([module.import('./generated-dynamic.js').then(function (n) { return n.d; }), module.import('./generated-dynamic.js').then(function (n) { return n.a; }), module.import('./generated-dynamic.js').then(function (n) { return n.b; })]).then(
1111
results => console.log(results, DEP)
1212
);
1313

test/chunking-form/samples/manual-chunks-dynamic-name-conflict/_expected/es/generated-dynamic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ var dynamic1 = /*#__PURE__*/Object.freeze({
1717
DYNAMIC_B: DYNAMIC_A
1818
});
1919

20-
export { dynamic1 as a, dynamic2 as d };
20+
export { dynamic2 as a, dynamic1 as d };

0 commit comments

Comments
 (0)