From 82b97280f3e4826ba396930e5805b9053f550500 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 30 Jul 2019 17:24:54 -0700 Subject: [PATCH] fix(ivy): speed up ngtsc if project has no templates to check (#31922) If a project being built with ngtsc has no templates to check, then ngtsc previously generated an empty typecheck file. This seems to trigger some pathological behavior in TS where the entire user program is re-checked, which is extremely expensive. This likely has to do with the fact that the empty file is not considered an ES module, meaning the module structure of the program has changed. This commit causes an export to be produced in the typecheck file regardless of its other contents, which guarantees that it will be an ES module. The pathological behavior is avoided and template type-checking is fast once again. PR Close #31922 --- .../ngtsc/typecheck/src/type_check_file.ts | 5 +++++ .../typecheck/test/type_constructor_spec.ts | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts index eadc68b5fd..9f1fb7c708 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts @@ -59,6 +59,11 @@ export class TypeCheckFile extends Environment { source += printer.printNode(ts.EmitHint.Unspecified, stmt, this.contextFile) + '\n'; } + // Ensure the template type-checking file is an ES module. Otherwise, it's interpreted as some + // kind of global namespace in TS, which forces a full re-typecheck of the user's program that + // is somehow more expensive than the initial parse. + source += '\nexport const IS_A_MODULE = true;\n'; + return ts.createSourceFile( this.fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts index 36744e9b42..0cf603313f 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts @@ -13,12 +13,14 @@ import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflectio import {getDeclaration, makeProgram} from '../../testing'; import {getRootDirs} from '../../util/src/typescript'; import {TypeCheckContext} from '../src/context'; +import {TypeCheckFile} from '../src/type_check_file'; import {ALL_ENABLED_CONFIG} from './test_utils'; runInEachFileSystem(() => { describe('ngtsc typechecking', () => { let _: typeof absoluteFrom; let LIB_D_TS: TestFile; + let TYPE_CHECK_TS: TestFile; beforeEach(() => { _ = absoluteFrom; @@ -29,12 +31,25 @@ runInEachFileSystem(() => { type Pick = { [P in K]: T[P]; }; type NonNullable = T extends null | undefined ? never : T;` }; + TYPE_CHECK_TS = { + name: _('/_typecheck_.ts'), + contents: ` + export const IS_A_MODULE = true; + ` + }; + }); + + it('should not produce an empty SourceFile when there is nothing to typecheck', () => { + const file = + new TypeCheckFile(_('/_typecheck_.ts'), ALL_ENABLED_CONFIG, new ReferenceEmitter([])); + const sf = file.render(); + expect(sf.statements.length).toBe(1); }); describe('ctors', () => { it('compiles a basic type constructor', () => { const files: TestFile[] = [ - LIB_D_TS, { + LIB_D_TS, TYPE_CHECK_TS, { name: _('/main.ts'), contents: ` class TestClass { @@ -72,7 +87,7 @@ TestClass.ngTypeCtor({value: 'test'}); it('should not consider query fields', () => { const files: TestFile[] = [ - LIB_D_TS, { + LIB_D_TS, TYPE_CHECK_TS, { name: _('/main.ts'), contents: `class TestClass { value: any; }`, }