Skip to content

Commit f520077

Browse files
alan-agius4alexeagle
authored andcommitted
fix(@angular-devkit/build-optimizer): wrap es2015 class expressions for better tree-shaking
ClassExpressions such as the below are not treeshakable unless we wrap them in an IIFE ```js let AggregateColumnDirective = class AggregateColumnDirective { constructor(viewContainerRef) { } }; AggregateColumnDirective = __decorate([ Directive({}), __metadata("design:paramtypes", [ViewContainerRef]) ], AggregateColumnDirective); ``` With this change we wrap the above in an IIFE and mark it as a PURE function. ```js const AggregateColumnDirective = /*@__PURE__*/ (() => { let AggregateColumnDirective = class AggregateColumnDirective { constructor(viewContainerRef) { } }; AggregateColumnDirective = __decorate([ Directive({}), __metadata("design:paramtypes", [ViewContainerRef]) ], AggregateColumnDirective); return AggregateColumnDirective; })(); ``` With this pattern if the class is unused it will be dropped. Note: In future we should rename `wrap-enums` to something more generic, and combine class-fold with this transformer especially considering the future fix that needs to be done for #14610 Fixes #14577
1 parent bebd8b6 commit f520077

File tree

2 files changed

+480
-255
lines changed

2 files changed

+480
-255
lines changed

packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts

+96-8
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function visitBlockStatements(
6161
};
6262

6363
// 'oIndex' is the original statement index; 'uIndex' is the updated statement index
64-
for (let oIndex = 0, uIndex = 0; oIndex < statements.length; oIndex++, uIndex++) {
64+
for (let oIndex = 0, uIndex = 0; oIndex < statements.length - 1; oIndex++, uIndex++) {
6565
const currentStatement = statements[oIndex];
6666

6767
// these can't contain an enum declaration
@@ -74,9 +74,14 @@ function visitBlockStatements(
7474
// * be a variable statement
7575
// * have only one declaration
7676
// * have an identifer as a declaration name
77-
if (oIndex < statements.length - 1
78-
&& ts.isVariableStatement(currentStatement)
79-
&& currentStatement.declarationList.declarations.length === 1) {
77+
78+
// ClassExpression declarations must:
79+
// * not be last statement
80+
// * be a variable statement
81+
// * have only one declaration
82+
// * have an ClassExpression as a initializer
83+
if (ts.isVariableStatement(currentStatement)
84+
&& currentStatement.declarationList.declarations.length === 1) {
8085

8186
const variableDeclaration = currentStatement.declarationList.declarations[0];
8287
if (ts.isIdentifier(variableDeclaration.name)) {
@@ -148,6 +153,23 @@ function visitBlockStatements(
148153
oIndex += enumStatements.length;
149154
continue;
150155
}
156+
} else if (ts.isClassExpression(variableDeclaration.initializer)) {
157+
const classStatements = findClassExpressionStatements(name, statements, oIndex);
158+
if (!classStatements) {
159+
continue;
160+
}
161+
162+
if (!updatedStatements) {
163+
updatedStatements = [...statements];
164+
}
165+
166+
updatedStatements.splice(uIndex, classStatements.length, createWrappedClass(
167+
name,
168+
classStatements,
169+
));
170+
171+
oIndex += classStatements.length - 1;
172+
continue;
151173
}
152174
}
153175
}
@@ -389,7 +411,6 @@ function updateHostNode(
389411
hostNode: ts.VariableStatement,
390412
expression: ts.Expression,
391413
): ts.Statement {
392-
393414
// Update existing host node with the pure comment before the variable declaration initializer.
394415
const variableDeclaration = hostNode.declarationList.declarations[0];
395416
const outerVarStmt = ts.updateVariableStatement(
@@ -411,6 +432,54 @@ function updateHostNode(
411432
return outerVarStmt;
412433
}
413434

435+
/**
436+
* Find class expression statements.
437+
*
438+
* The classExpressions block to wrap in an iife must
439+
* - end with an ExpressionStatement
440+
* - it's expression must be a BinaryExpression
441+
* - have the same name
442+
*
443+
* ```
444+
let Foo = class Foo {};
445+
Foo = __decorate([]);
446+
```
447+
*/
448+
function findClassExpressionStatements(
449+
name: string,
450+
statements: ts.NodeArray<ts.Statement>,
451+
statementIndex: number,
452+
): ts.Statement[] | undefined {
453+
let index = statementIndex + 1;
454+
let statement = statements[index];
455+
456+
while (ts.isExpressionStatement(statement)) {
457+
const expression = statement.expression;
458+
if (ts.isCallExpression(expression)) {
459+
// Ex:
460+
// __decorate([propDecorator()], FooClass, "propertyName", void 0);
461+
// __decorate$1([propDecorator()], FooClass, "propertyName", void 0);
462+
const callExpression = expression.expression;
463+
if (!ts.isIdentifier(callExpression) || !/^__decorate(\$\d+)?$/.test(callExpression.text)) {
464+
break;
465+
}
466+
}
467+
468+
if (
469+
ts.isBinaryExpression(expression)
470+
&& ts.isIdentifier(expression.left)
471+
&& expression.left.getText() === name
472+
) {
473+
// Ex: FooClass = __decorate([Component()], FooClass);
474+
return statements.slice(statementIndex, index + 1);
475+
}
476+
477+
statement = statements[++index];
478+
}
479+
480+
return undefined;
481+
}
482+
414483
function updateEnumIife(
415484
hostNode: ts.VariableStatement,
416485
iife: ts.CallExpression,
@@ -474,11 +543,9 @@ function createWrappedEnum(
474543
name: string,
475544
hostNode: ts.VariableStatement,
476545
statements: Array<ts.Statement>,
477-
literalInitializer: ts.ObjectLiteralExpression | undefined,
546+
literalInitializer: ts.ObjectLiteralExpression = ts.createObjectLiteral(),
478547
addExportModifier = false,
479548
): ts.Statement {
480-
literalInitializer = literalInitializer || ts.createObjectLiteral();
481-
482549
const node = addExportModifier
483550
? ts.updateVariableStatement(
484551
hostNode,
@@ -504,3 +571,24 @@ function createWrappedEnum(
504571

505572
return updateHostNode(node, addPureComment(ts.createParen(iife)));
506573
}
574+
575+
function createWrappedClass(
576+
name: string,
577+
statements: ts.Statement[],
578+
): ts.Statement {
579+
const pureIife = addPureComment(
580+
ts.createImmediatelyInvokedArrowFunction([
581+
...statements,
582+
ts.createReturn(ts.createIdentifier(name)),
583+
]),
584+
);
585+
586+
return ts.createVariableStatement(
587+
undefined,
588+
ts.createVariableDeclarationList([
589+
ts.createVariableDeclaration(name, undefined, pureIife),
590+
],
591+
ts.NodeFlags.Const,
592+
),
593+
);
594+
}

0 commit comments

Comments
 (0)