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
This commit is contained in:
Alex Rickabaugh 2019-07-30 17:24:54 -07:00
parent ecffbda664
commit 82b97280f3
2 changed files with 22 additions and 2 deletions

View File

@ -59,6 +59,11 @@ export class TypeCheckFile extends Environment {
source += printer.printNode(ts.EmitHint.Unspecified, stmt, this.contextFile) + '\n'; 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( return ts.createSourceFile(
this.fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); this.fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
} }

View File

@ -13,12 +13,14 @@ import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflectio
import {getDeclaration, makeProgram} from '../../testing'; import {getDeclaration, makeProgram} from '../../testing';
import {getRootDirs} from '../../util/src/typescript'; import {getRootDirs} from '../../util/src/typescript';
import {TypeCheckContext} from '../src/context'; import {TypeCheckContext} from '../src/context';
import {TypeCheckFile} from '../src/type_check_file';
import {ALL_ENABLED_CONFIG} from './test_utils'; import {ALL_ENABLED_CONFIG} from './test_utils';
runInEachFileSystem(() => { runInEachFileSystem(() => {
describe('ngtsc typechecking', () => { describe('ngtsc typechecking', () => {
let _: typeof absoluteFrom; let _: typeof absoluteFrom;
let LIB_D_TS: TestFile; let LIB_D_TS: TestFile;
let TYPE_CHECK_TS: TestFile;
beforeEach(() => { beforeEach(() => {
_ = absoluteFrom; _ = absoluteFrom;
@ -29,12 +31,25 @@ runInEachFileSystem(() => {
type Pick<T, K extends keyof T> = { [P in K]: T[P]; }; type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
type NonNullable<T> = T extends null | undefined ? never : T;` type NonNullable<T> = 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', () => { describe('ctors', () => {
it('compiles a basic type constructor', () => { it('compiles a basic type constructor', () => {
const files: TestFile[] = [ const files: TestFile[] = [
LIB_D_TS, { LIB_D_TS, TYPE_CHECK_TS, {
name: _('/main.ts'), name: _('/main.ts'),
contents: ` contents: `
class TestClass<T extends string> { class TestClass<T extends string> {
@ -72,7 +87,7 @@ TestClass.ngTypeCtor({value: 'test'});
it('should not consider query fields', () => { it('should not consider query fields', () => {
const files: TestFile[] = [ const files: TestFile[] = [
LIB_D_TS, { LIB_D_TS, TYPE_CHECK_TS, {
name: _('/main.ts'), name: _('/main.ts'),
contents: `class TestClass { value: any; }`, contents: `class TestClass { value: any; }`,
} }