Skip to content

Commit abd8ec9

Browse files
committed
fix(@angular/build): prevent IIFE wrapper interleaving for adjacent classes in minified files
In 22.1.0, the Babel-based advancedOptimizations pass was replaced with oxc-parser + magic-string. When wrapping a class declaration in a pure IIFE, appendLeft was used for the opener and appendRight for the closer. In minified bundles where sibling top-level class declarations are adjacent without whitespace (classA.end === classB.start), magic-string emits appendLeft content before appendRight content at a shared index. This caused class B's IIFE opener to be emitted ahead of class A's IIFE closer, interleaving the wrappers and resulting in duplicate symbol declarations in subsequent build phases. This change swaps the insertion methods to use appendRight for the opening fragment and appendLeft for the closing fragment (and when splitting default exports), ensuring correct ordering at shared boundaries. closes #33699
1 parent c8eaf59 commit abd8ec9

2 files changed

Lines changed: 111 additions & 9 deletions

File tree

packages/angular/build/src/tools/babel/plugins/adjust-static-class-members_oxc_spec.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,4 +998,106 @@ describe('adjust-static-class-members oxc-transform implementation', () => {
998998
`,
999999
}),
10001000
);
1001+
1002+
it(
1003+
'wraps adjacent class declarations without interleaving',
1004+
testCase({
1005+
input: 'class A{static s=[1]}class B{static s=[2]}',
1006+
expected: `
1007+
let A = /*#__PURE__*/ (() => {
1008+
class A {
1009+
static s = [1]
1010+
}
1011+
return A;
1012+
})();
1013+
let B = /*#__PURE__*/ (() => {
1014+
class B {
1015+
static s = [2]
1016+
}
1017+
return B;
1018+
})();
1019+
`,
1020+
}),
1021+
);
1022+
1023+
it(
1024+
'wraps adjacent exported class declarations without interleaving',
1025+
testCase({
1026+
input: 'export class A{static s=[1]}export class B{static s=[2]}',
1027+
expected: `
1028+
export let A = /*#__PURE__*/ (() => {
1029+
class A {
1030+
static s = [1]
1031+
}
1032+
return A;
1033+
})();
1034+
export let B = /*#__PURE__*/ (() => {
1035+
class B {
1036+
static s = [2]
1037+
}
1038+
return B;
1039+
})();
1040+
`,
1041+
}),
1042+
);
1043+
1044+
it(
1045+
'wraps adjacent default export and class declarations without interleaving',
1046+
testCase({
1047+
input: 'export default class A{static s=[1]}class B{static s=[2]}',
1048+
expected: `
1049+
let A = /*#__PURE__*/ (() => {
1050+
class A {
1051+
static s = [1]
1052+
}
1053+
return A;
1054+
})();
1055+
export { A as default };
1056+
let B = /*#__PURE__*/ (() => {
1057+
class B {
1058+
static s = [2]
1059+
}
1060+
return B;
1061+
})();
1062+
`,
1063+
}),
1064+
);
1065+
1066+
it(
1067+
'splits default export without interleaving with adjacent class declaration',
1068+
testCase({
1069+
input: 'export default class A{}class B{static s=[2]}',
1070+
expected: `
1071+
class A {}
1072+
export { A as default };
1073+
let B = /*#__PURE__*/ (() => {
1074+
class B {
1075+
static s = [2]
1076+
}
1077+
return B;
1078+
})();
1079+
`,
1080+
}),
1081+
);
1082+
1083+
it(
1084+
'wraps adjacent variable class declarations without interleaving',
1085+
testCase({
1086+
input: 'let A=class A{static s=[1]};class B{static s=[2]}',
1087+
expected: `
1088+
let A = /*#__PURE__*/ (() => {
1089+
let A = class A {
1090+
static s = [1]
1091+
};
1092+
return A;
1093+
})();
1094+
let B = /*#__PURE__*/ (() => {
1095+
class B {
1096+
static s = [2]
1097+
}
1098+
return B;
1099+
})();
1100+
`,
1101+
}),
1102+
);
10011103
});

packages/angular/build/src/tools/babel/plugins/oxc-transform.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -584,25 +584,25 @@ export function transform(filename: string, code: string, options: OxcTransformO
584584
// 1. Remove `export default `
585585
s.overwrite(statement.start, classNode.start, '');
586586
// 2. Wrap in IIFE
587-
s.appendLeft(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`);
588-
s.appendRight(
587+
s.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`);
588+
s.appendLeft(
589589
lastStatement.end,
590590
`\nreturn ${classIdName};\n})();\nexport { ${classIdName} as default };`,
591591
);
592592
} else if (isExportNamed) {
593593
// 1. Export is kept, turn `class` into `let ClassName = IIFE`
594-
s.appendLeft(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`);
595-
s.appendRight(lastStatement.end, `\nreturn ${classIdName};\n})();`);
594+
s.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`);
595+
s.appendLeft(lastStatement.end, `\nreturn ${classIdName};\n})();`);
596596
} else if (isVariableClass) {
597597
// Wrap class inside init: `/*#__PURE__*/ (() => { let ClassName = class ClassName {}; return ClassName; })()`
598-
s.appendLeft(classNode.start, `/*#__PURE__*/ (() => {\nlet ${classIdName} = `);
598+
s.appendRight(classNode.start, `/*#__PURE__*/ (() => {\nlet ${classIdName} = `);
599599
const terminator = activeWrapPaths.length === 0 ? ';' : '';
600600
const iifeClosing = activeWrapPaths.length === 0 ? '})()' : '})();';
601-
s.appendRight(lastStatement.end, `${terminator}\nreturn ${classIdName};\n${iifeClosing}`);
601+
s.appendLeft(lastStatement.end, `${terminator}\nreturn ${classIdName};\n${iifeClosing}`);
602602
} else {
603603
// Standard ClassDeclaration
604-
s.appendLeft(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`);
605-
s.appendRight(lastStatement.end, `\nreturn ${classIdName};\n})();`);
604+
s.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`);
605+
s.appendLeft(lastStatement.end, `\nreturn ${classIdName};\n})();`);
606606
}
607607

608608
markEdited(statement.start, lastStatement.end);
@@ -612,7 +612,7 @@ export function transform(filename: string, code: string, options: OxcTransformO
612612
} else if (isExportDefault && !hasPotentialSideEffects) {
613613
// Splitting default export even when not wrapped
614614
s.overwrite(statement.start, classNode.start, '');
615-
s.appendRight(classNode.end, `\nexport { ${classIdName} as default };`);
615+
s.appendLeft(classNode.end, `\nexport { ${classIdName} as default };`);
616616
markEdited(statement.start, classNode.end);
617617
}
618618
}

0 commit comments

Comments
 (0)