From 31875e9d79a46b4b8e9ee4d3c85c0f05be6f3802 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 14 Oct 2020 12:57:18 -0400 Subject: [PATCH] fix(@ngtools/webpack): support jit mode guarded class metadata removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for the alternate emit form of `ɵsetClassMetadata` that is guarded by `ngJitMode` instead of a pure annotation. --- .../remove-ivy-jit-support-calls.ts | 26 ++- .../remove-ivy-jit-support-calls_spec.ts | 150 +++++++++++++++++- 2 files changed, 166 insertions(+), 10 deletions(-) diff --git a/packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls.ts b/packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls.ts index 8efd6163c7eb..343d6e978bfa 100644 --- a/packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls.ts +++ b/packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls.ts @@ -25,14 +25,30 @@ export function removeIvyJitSupportCalls( return false; } + let shouldRemove = false; + if (ngModuleScope && ts.isBinaryExpression(innerStatement.expression)) { - return isIvyPrivateCallExpression(innerStatement.expression.right, 'ɵɵsetNgModuleScope'); + shouldRemove = isIvyPrivateCallExpression( + innerStatement.expression.right, + 'ɵɵsetNgModuleScope', + ); + } + + if (classMetadata && !shouldRemove) { + if (ts.isBinaryExpression(innerStatement.expression)) { + shouldRemove = isIvyPrivateCallExpression( + innerStatement.expression.right, + 'ɵsetClassMetadata', + ); + } else { + shouldRemove = isIvyPrivateCallExpression( + innerStatement.expression, + 'ɵsetClassMetadata', + ); + } } - return ( - classMetadata && - isIvyPrivateCallExpression(innerStatement.expression, 'ɵsetClassMetadata') - ); + return shouldRemove; }) .forEach(statement => ops.push(new RemoveNodeOperation(sourceFile, statement))); diff --git a/packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls_spec.ts b/packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls_spec.ts index 8cb927f933cf..99145f97f53e 100644 --- a/packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls_spec.ts +++ b/packages/ngtools/webpack/src/transformers/remove-ivy-jit-support-calls_spec.ts @@ -51,9 +51,39 @@ const input = tags.stripIndent` }], null, null); })(); `; +const inputNoPure = tags.stripIndent` + export class AppModule { + } + AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] }); + AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[ + BrowserModule, + AppRoutingModule + ]] }); + (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(AppModule, { declarations: [AppComponent, + ExampleComponent], imports: [BrowserModule, + AppRoutingModule] }); })(); + (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵsetClassMetadata(AppModule, [{ + type: NgModule, + args: [{ + declarations: [ + AppComponent, + ExampleComponent + ], + imports: [ + BrowserModule, + AppRoutingModule + ], + providers: [], + bootstrap: [AppComponent] + }] + }], null, null); })(); +`; + +// tslint:disable-next-line: no-big-function describe('@ngtools/webpack transformers', () => { + // tslint:disable-next-line: no-big-function describe('remove-ivy-dev-calls', () => { - it('should allow removing only set class metadata', () => { + it('should allow removing only set class metadata with pure annotation', () => { const output = tags.stripIndent` export class AppModule { } @@ -74,7 +104,28 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); }); - it('should allow removing only ng module scope', () => { + it('should allow removing only set class metadata', () => { + const output = tags.stripIndent` + export class AppModule { + } + AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] }); + AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[ + BrowserModule, + AppRoutingModule + ]] }); + (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(AppModule, { declarations: [AppComponent, + ExampleComponent], imports: [BrowserModule, + AppRoutingModule] }); })(); + `; + + const result = transform(inputNoPure, getTypeChecker => + removeIvyJitSupportCalls(true, false, getTypeChecker), + ); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); + + it('should allow removing only ng module scope with pure annotation', () => { const output = tags.stripIndent` export class AppModule { } @@ -107,7 +158,40 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); }); - it('should allow removing both set class metadata and ng module scope', () => { + it('should allow removing only ng module scope', () => { + const output = tags.stripIndent` + export class AppModule { + } + AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] }); + AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[ + BrowserModule, + AppRoutingModule + ]] }); + (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵsetClassMetadata(AppModule, [{ + type: NgModule, + args: [{ + declarations: [ + AppComponent, + ExampleComponent + ], + imports: [ + BrowserModule, + AppRoutingModule + ], + providers: [], + bootstrap: [AppComponent] + }] + }], null, null); })(); + `; + + const result = transform(inputNoPure, getTypeChecker => + removeIvyJitSupportCalls(false, true, getTypeChecker), + ); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); + + it('should allow removing both set class metadata and ng module scope with pure annotation', () => { const output = tags.stripIndent` export class AppModule { } @@ -125,7 +209,25 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); }); - it('should allow removing neither set class metadata nor ng module scope', () => { + it('should allow removing both set class metadata and ng module scope', () => { + const output = tags.stripIndent` + export class AppModule { + } + AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] }); + AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[ + BrowserModule, + AppRoutingModule + ]] }); + `; + + const result = transform(inputNoPure, getTypeChecker => + removeIvyJitSupportCalls(true, true, getTypeChecker), + ); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); + + it('should allow removing neither set class metadata nor ng module scope with pure annotation', () => { const result = transform(input, getTypeChecker => removeIvyJitSupportCalls(false, false, getTypeChecker), ); @@ -133,7 +235,15 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${input}`); }); - it('should strip unused imports when removing set class metadata and ng module scope', () => { + it('should allow removing neither set class metadata nor ng module scope', () => { + const result = transform(inputNoPure, getTypeChecker => + removeIvyJitSupportCalls(false, false, getTypeChecker), + ); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${inputNoPure}`); + }); + + it('should strip unused imports when removing set class metadata and ng module scope with pure annotation', () => { const imports = tags.stripIndent` import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; @@ -164,5 +274,35 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); }); + it('should strip unused imports when removing set class metadata and ng module scope', () => { + const imports = tags.stripIndent` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + import { AppRoutingModule } from './app-routing.module'; + import { AppComponent } from './app.component'; + import { ExampleComponent } from './example/example.component'; + import * as i0 from "@angular/core"; + `; + + const output = tags.stripIndent` + import { BrowserModule } from '@angular/platform-browser'; + import { AppRoutingModule } from './app-routing.module'; + import { AppComponent } from './app.component'; + import * as i0 from "@angular/core"; + export class AppModule { + } + AppModule.ɵmod = i0.ɵɵdefineNgModule({ type: AppModule, bootstrap: [AppComponent] }); + AppModule.ɵinj = i0.ɵɵdefineInjector({ factory: function AppModule_Factory(t) { return new (t || AppModule)(); }, providers: [], imports: [[ + BrowserModule, + AppRoutingModule + ]] }); + `; + + const result = transform(imports + inputNoPure, getTypeChecker => + removeIvyJitSupportCalls(true, true, getTypeChecker), + ); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); }); });