Skip to content

Commit 4fe0326

Browse files
crisbetodgp1130
authored andcommitted
fix(@ngtools/webpack): account for arrow function IIFE
Updates the logic for removing Angular JIT calls to account for arrow-function-based IIFEs. Currently Angular doesn't generate arrow functions, but it's being explored in angular/angular#51637.
1 parent 6b08efa commit 4fe0326

File tree

2 files changed

+108
-12
lines changed

2 files changed

+108
-12
lines changed

packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,24 @@ export function removeIvyJitSupportCalls(
1818
const removedNodes: ts.Node[] = [];
1919

2020
const visitNode: ts.Visitor = (node: ts.Node) => {
21-
const innerStatement = ts.isExpressionStatement(node) && getIifeStatement(node);
22-
if (innerStatement) {
21+
const innerExpression = ts.isExpressionStatement(node) ? getIifeExpression(node) : null;
22+
if (innerExpression) {
2323
if (
2424
ngModuleScope &&
25-
ts.isBinaryExpression(innerStatement.expression) &&
26-
isIvyPrivateCallExpression(innerStatement.expression.right, 'ɵɵsetNgModuleScope')
25+
ts.isBinaryExpression(innerExpression) &&
26+
isIvyPrivateCallExpression(innerExpression.right, 'ɵɵsetNgModuleScope')
2727
) {
28-
removedNodes.push(innerStatement);
28+
removedNodes.push(innerExpression);
2929

3030
return undefined;
3131
}
3232

3333
if (classMetadata) {
34-
const expression = ts.isBinaryExpression(innerStatement.expression)
35-
? innerStatement.expression.right
36-
: innerStatement.expression;
34+
const expression = ts.isBinaryExpression(innerExpression)
35+
? innerExpression.right
36+
: innerExpression;
3737
if (isIvyPrivateCallExpression(expression, 'ɵsetClassMetadata')) {
38-
removedNodes.push(innerStatement);
38+
removedNodes.push(innerExpression);
3939

4040
return undefined;
4141
}
@@ -75,7 +75,7 @@ export function removeIvyJitSupportCalls(
7575
}
7676

7777
// Each Ivy private call expression is inside an IIFE
78-
function getIifeStatement(exprStmt: ts.ExpressionStatement): null | ts.ExpressionStatement {
78+
function getIifeExpression(exprStmt: ts.ExpressionStatement): null | ts.Expression {
7979
const expression = exprStmt.expression;
8080
if (!expression || !ts.isCallExpression(expression) || expression.arguments.length !== 0) {
8181
return null;
@@ -87,10 +87,14 @@ function getIifeStatement(exprStmt: ts.ExpressionStatement): null | ts.Expressio
8787
}
8888

8989
const funExpr = parenExpr.expression.expression;
90-
if (!ts.isFunctionExpression(funExpr)) {
90+
if (!ts.isFunctionExpression(funExpr) && !ts.isArrowFunction(funExpr)) {
9191
return null;
9292
}
9393

94+
if (!ts.isBlock(funExpr.body)) {
95+
return funExpr.body;
96+
}
97+
9498
const innerStmts = funExpr.body.statements;
9599
if (innerStmts.length !== 1) {
96100
return null;
@@ -101,7 +105,7 @@ function getIifeStatement(exprStmt: ts.ExpressionStatement): null | ts.Expressio
101105
return null;
102106
}
103107

104-
return innerExprStmt;
108+
return innerExprStmt.expression;
105109
}
106110

107111
function isIvyPrivateCallExpression(expression: ts.Expression, name: string) {

packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls_spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,62 @@ const inputNoPure = tags.stripIndent`
8181
}], null, null); })();
8282
`;
8383

84+
const inputArrowFnWithBody = tags.stripIndent`
85+
export class AppModule {
86+
}
87+
AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] });
88+
AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[
89+
BrowserModule,
90+
AppRoutingModule
91+
]] });
92+
(() => { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(AppModule, { declarations: [AppComponent,
93+
ExampleComponent], imports: [BrowserModule,
94+
AppRoutingModule] }); })();
95+
(() => { i0.ɵsetClassMetadata(AppModule, [{
96+
type: NgModule,
97+
args: [{
98+
declarations: [
99+
AppComponent,
100+
ExampleComponent
101+
],
102+
imports: [
103+
BrowserModule,
104+
AppRoutingModule
105+
],
106+
providers: [],
107+
bootstrap: [AppComponent]
108+
}]
109+
}], null, null); })();
110+
`;
111+
112+
const inputArrowFnWithImplicitReturn = tags.stripIndent`
113+
export class AppModule {
114+
}
115+
AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] });
116+
AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[
117+
BrowserModule,
118+
AppRoutingModule
119+
]] });
120+
(() => (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(AppModule, { declarations: [AppComponent,
121+
ExampleComponent], imports: [BrowserModule,
122+
AppRoutingModule] }))();
123+
(() => i0.ɵsetClassMetadata(AppModule, [{
124+
type: NgModule,
125+
args: [{
126+
declarations: [
127+
AppComponent,
128+
ExampleComponent
129+
],
130+
imports: [
131+
BrowserModule,
132+
AppRoutingModule
133+
],
134+
providers: [],
135+
bootstrap: [AppComponent]
136+
}]
137+
}], null, null))();
138+
`;
139+
84140
describe('@ngtools/webpack transformers', () => {
85141
describe('remove-ivy-dev-calls', () => {
86142
it('should allow removing only set class metadata with pure annotation', () => {
@@ -304,5 +360,41 @@ describe('@ngtools/webpack transformers', () => {
304360

305361
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
306362
});
363+
364+
it('should remove setClassMetadata and setNgModuleScope calls inside arrow-function-based IIFEs that have bodies', () => {
365+
const output = tags.stripIndent`
366+
export class AppModule {
367+
}
368+
AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] });
369+
AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[
370+
BrowserModule,
371+
AppRoutingModule
372+
]] });
373+
`;
374+
375+
const result = transform(inputArrowFnWithBody, (getTypeChecker) =>
376+
removeIvyJitSupportCalls(true, true, getTypeChecker),
377+
);
378+
379+
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
380+
});
381+
382+
it('should remove setClassMetadata and setNgModuleScope calls inside arrow-function-based IIFEs that have an implicit return', () => {
383+
const output = tags.stripIndent`
384+
export class AppModule {
385+
}
386+
AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] });
387+
AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[
388+
BrowserModule,
389+
AppRoutingModule
390+
]] });
391+
`;
392+
393+
const result = transform(inputArrowFnWithImplicitReturn, (getTypeChecker) =>
394+
removeIvyJitSupportCalls(true, true, getTypeChecker),
395+
);
396+
397+
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
398+
});
307399
});
308400
});

0 commit comments

Comments
 (0)