2018-04-06 09:53:10 -07:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
import {GeneratedFile} from '@angular/compiler';
|
2018-04-06 09:53:10 -07:00
|
|
|
import * as ts from 'typescript';
|
|
|
|
|
|
|
|
import * as api from '../transformers/api';
|
2019-10-24 20:24:39 +02:00
|
|
|
import {verifySupportedTypeScriptVersion} from '../typescript_support';
|
2018-04-06 09:53:10 -07:00
|
|
|
|
perf(compiler-cli): split Ivy template type-checking into multiple files (#36211)
As a performance optimization, this commit splits the single
__ngtypecheck__.ts file which was previously added to the user's program as
a container for all template type-checking code into multiple .ngtypecheck
shim files, one for each original file in the user's program.
In larger applications, the generation, parsing, and checking of this single
type-checking file was a huge performance bottleneck, with the file often
exceeding 1 MB in text content. Particularly in incremental builds,
regenerating this single file for the entire application proved especially
expensive.
This commit introduces a new strategy for template type-checking code which
makes use of a new interface, the `TypeCheckingProgramStrategy`. This
interface abstracts the process of creating a new `ts.Program` to type-check
a particular compilation, and allows the mechanism there to be kept separate
from the more complex logic around dealing with multiple .ngtypecheck files.
A new `TemplateTypeChecker` hosts that logic and interacts with the
`TypeCheckingProgramStrategy` to actually generate and return diagnostics.
The `TypeCheckContext` class, previously the workhorse of template type-
checking, is now solely focused on collecting and generating type-checking
file contents.
A side effect of implementing the new `TypeCheckingProgramStrategy` in this
way is that the API is designed to be suitable for use by the Angular
Language Service as well. The LS also needs to type-check components, but
has its own method for constructing a `ts.Program` with type-checking code.
Note that this commit does not make the actual checking of templates at all
_incremental_ just yet. That will happen in a future commit.
PR Close #36211
2020-03-04 15:50:12 -08:00
|
|
|
import {NgCompiler, NgCompilerHost} from './core';
|
2020-01-17 16:00:07 -08:00
|
|
|
import {NgCompilerOptions} from './core/api';
|
|
|
|
import {IndexedComponent} from './indexer';
|
2019-03-18 11:16:38 -07:00
|
|
|
import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
|
perf(compiler-cli): split Ivy template type-checking into multiple files (#36211)
As a performance optimization, this commit splits the single
__ngtypecheck__.ts file which was previously added to the user's program as
a container for all template type-checking code into multiple .ngtypecheck
shim files, one for each original file in the user's program.
In larger applications, the generation, parsing, and checking of this single
type-checking file was a huge performance bottleneck, with the file often
exceeding 1 MB in text content. Particularly in incremental builds,
regenerating this single file for the entire application proved especially
expensive.
This commit introduces a new strategy for template type-checking code which
makes use of a new interface, the `TypeCheckingProgramStrategy`. This
interface abstracts the process of creating a new `ts.Program` to type-check
a particular compilation, and allows the mechanism there to be kept separate
from the more complex logic around dealing with multiple .ngtypecheck files.
A new `TemplateTypeChecker` hosts that logic and interacts with the
`TypeCheckingProgramStrategy` to actually generate and return diagnostics.
The `TypeCheckContext` class, previously the workhorse of template type-
checking, is now solely focused on collecting and generating type-checking
file contents.
A side effect of implementing the new `TypeCheckingProgramStrategy` in this
way is that the API is designed to be suitable for use by the Angular
Language Service as well. The LS also needs to type-check components, but
has its own method for constructing a `ts.Program` with type-checking code.
Note that this commit does not make the actual checking of templates at all
_incremental_ just yet. That will happen in a future commit.
PR Close #36211
2020-03-04 15:50:12 -08:00
|
|
|
import {ReusedProgramStrategy} from './typecheck';
|
2018-04-06 09:53:10 -07:00
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Entrypoint to the Angular Compiler (Ivy+) which sits behind the `api.Program` interface, allowing
|
|
|
|
* it to be a drop-in replacement for the legacy View Engine compiler to tooling such as the
|
|
|
|
* command-line main() function or the Angular CLI.
|
|
|
|
*/
|
2018-04-06 09:53:10 -07:00
|
|
|
export class NgtscProgram implements api.Program {
|
2020-01-17 16:00:07 -08:00
|
|
|
private compiler: NgCompiler;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The primary TypeScript program, which is used for analysis and emit.
|
|
|
|
*/
|
2018-04-06 09:53:10 -07:00
|
|
|
private tsProgram: ts.Program;
|
2020-01-17 16:00:07 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The TypeScript program to use for the next incremental compilation.
|
|
|
|
*
|
|
|
|
* Once a TS program is used to create another (an incremental compilation operation), it can no
|
|
|
|
* longer be used to do so again.
|
|
|
|
*
|
|
|
|
* Since template type-checking uses the primary program to create a type-checking program, after
|
|
|
|
* this happens the primary program is no longer suitable for starting a subsequent compilation,
|
|
|
|
* and the template type-checking program should be used instead.
|
|
|
|
*
|
|
|
|
* Thus, the program which should be used for the next incremental compilation is tracked in
|
|
|
|
* `reuseTsProgram`, separately from the "primary" program which is always used for emit.
|
|
|
|
*/
|
2019-04-24 10:26:47 -07:00
|
|
|
private reuseTsProgram: ts.Program;
|
2018-08-28 14:13:22 -07:00
|
|
|
private closureCompilerEnabled: boolean;
|
2020-01-17 16:00:07 -08:00
|
|
|
private host: NgCompilerHost;
|
2019-03-18 11:16:38 -07:00
|
|
|
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER;
|
|
|
|
private perfTracker: PerfTracker|null = null;
|
2019-06-10 16:22:56 +01:00
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
constructor(
|
2020-01-17 16:00:07 -08:00
|
|
|
rootNames: ReadonlyArray<string>, private options: NgCompilerOptions,
|
|
|
|
delegateHost: api.CompilerHost, oldProgram?: NgtscProgram) {
|
|
|
|
// First, check whether the current TS version is supported.
|
2019-10-24 20:24:39 +02:00
|
|
|
if (!options.disableTypeScriptVersionCheck) {
|
|
|
|
verifySupportedTypeScriptVersion();
|
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
if (options.tracePerformance !== undefined) {
|
2019-03-18 11:16:38 -07:00
|
|
|
this.perfTracker = PerfTracker.zeroedToNow();
|
|
|
|
this.perfRecorder = this.perfTracker;
|
|
|
|
}
|
2018-08-28 14:13:22 -07:00
|
|
|
this.closureCompilerEnabled = !!options.annotateForClosureCompiler;
|
2018-10-16 14:47:08 -07:00
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
const reuseProgram = oldProgram && oldProgram.reuseTsProgram;
|
fix(compiler): switch to 'referencedFiles' for shim generation (#36211)
Shim generation was built on a lie.
Shims are files added to the program which aren't original files authored by
the user, but files authored effectively by the compiler. These fall into
two categories: files which will be generated (like the .ngfactory shims we
generate for View Engine compatibility) as well as files used internally in
compilation (like the __ng_typecheck__.ts file).
Previously, shim generation was driven by the `rootFiles` passed to the
compiler as input. These are effectively the `files` listed in the
`tsconfig.json`. Each shim generator (e.g. the `FactoryGenerator`) would
examine the `rootFiles` and produce a list of shim file names which it would
be responsible for generating. These names would then be added to the
`rootFiles` when the program was created.
The fatal flaw here is that `rootFiles` does not always account for all of
the files in the program. In fact, it's quite rare that it does. Users don't
typically specify every file directly in `files`. Instead, they rely on
TypeScript, during program creation, starting with a few root files and
transitively discovering all of the files in the program.
This happens, however, during `ts.createProgram`, which is too late to add
new files to the `rootFiles` list.
As a result, shim generation was only including shims for files actually
listed in the `tsconfig.json` file, and not for the transitive set of files
in the user's program as it should.
This commit completely rewrites shim generation to use a different technique
for adding files to the program, inspired by View Engine's shim generator.
In this new technique, as the program is being created and `ts.SourceFile`s
are being requested from the `NgCompilerHost`, shims for those files are
generated and a reference to them is patched onto the original file's
`ts.SourceFile.referencedFiles`. This causes TS to think that the original
file references the shim, and causes the shim to be included in the program.
The original `referencedFiles` array is saved and restored after program
creation, hiding this little hack from the rest of the system.
The new shim generation engine differentiates between two kinds of shims:
top-level shims (such as the flat module entrypoint file and
__ng_typecheck__.ts) and per-file shims such as ngfactory or ngsummary
files. The former are included via `rootFiles` as before, the latter are
included via the `referencedFiles` of their corresponding original files.
As a result of this change, shims are now correctly generated for all files
in the program, not just the ones named in `tsconfig.json`.
A few mitigating factors prevented this bug from being realized until now:
* in g3, `files` does include the transitive closure of files in the program
* in CLI apps, shims are not really used
This change also makes use of a novel technique for associating information
with source files: the use of an `NgExtension` `Symbol` to patch the
information directly onto the AST object. This is used in several
circumstances:
* For shims, metadata about a `ts.SourceFile`'s status as a shim and its
origins are held in the extension data.
* For original files, the original `referencedFiles` are stashed in the
extension data for later restoration.
The main benefit of this technique is a lot less bookkeeping around `Map`s
of `ts.SourceFile`s to various kinds of data, which need to be tracked/
invalidated as part of incremental builds.
This technique is based on designs used internally in the TypeScript
compiler and is serving as a prototype of this design in ngtsc. If it works
well, it could have benefits across the rest of the compiler.
PR Close #36211
2020-02-26 16:12:39 -08:00
|
|
|
this.host = NgCompilerHost.wrap(delegateHost, rootNames, options, reuseProgram ?? null);
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
this.tsProgram = ts.createProgram(this.host.inputFiles, options, this.host, reuseProgram);
|
2019-04-24 10:26:47 -07:00
|
|
|
this.reuseTsProgram = this.tsProgram;
|
2018-12-13 11:52:20 -08:00
|
|
|
|
fix(compiler): switch to 'referencedFiles' for shim generation (#36211)
Shim generation was built on a lie.
Shims are files added to the program which aren't original files authored by
the user, but files authored effectively by the compiler. These fall into
two categories: files which will be generated (like the .ngfactory shims we
generate for View Engine compatibility) as well as files used internally in
compilation (like the __ng_typecheck__.ts file).
Previously, shim generation was driven by the `rootFiles` passed to the
compiler as input. These are effectively the `files` listed in the
`tsconfig.json`. Each shim generator (e.g. the `FactoryGenerator`) would
examine the `rootFiles` and produce a list of shim file names which it would
be responsible for generating. These names would then be added to the
`rootFiles` when the program was created.
The fatal flaw here is that `rootFiles` does not always account for all of
the files in the program. In fact, it's quite rare that it does. Users don't
typically specify every file directly in `files`. Instead, they rely on
TypeScript, during program creation, starting with a few root files and
transitively discovering all of the files in the program.
This happens, however, during `ts.createProgram`, which is too late to add
new files to the `rootFiles` list.
As a result, shim generation was only including shims for files actually
listed in the `tsconfig.json` file, and not for the transitive set of files
in the user's program as it should.
This commit completely rewrites shim generation to use a different technique
for adding files to the program, inspired by View Engine's shim generator.
In this new technique, as the program is being created and `ts.SourceFile`s
are being requested from the `NgCompilerHost`, shims for those files are
generated and a reference to them is patched onto the original file's
`ts.SourceFile.referencedFiles`. This causes TS to think that the original
file references the shim, and causes the shim to be included in the program.
The original `referencedFiles` array is saved and restored after program
creation, hiding this little hack from the rest of the system.
The new shim generation engine differentiates between two kinds of shims:
top-level shims (such as the flat module entrypoint file and
__ng_typecheck__.ts) and per-file shims such as ngfactory or ngsummary
files. The former are included via `rootFiles` as before, the latter are
included via the `referencedFiles` of their corresponding original files.
As a result of this change, shims are now correctly generated for all files
in the program, not just the ones named in `tsconfig.json`.
A few mitigating factors prevented this bug from being realized until now:
* in g3, `files` does include the transitive closure of files in the program
* in CLI apps, shims are not really used
This change also makes use of a novel technique for associating information
with source files: the use of an `NgExtension` `Symbol` to patch the
information directly onto the AST object. This is used in several
circumstances:
* For shims, metadata about a `ts.SourceFile`'s status as a shim and its
origins are held in the extension data.
* For original files, the original `referencedFiles` are stashed in the
extension data for later restoration.
The main benefit of this technique is a lot less bookkeeping around `Map`s
of `ts.SourceFile`s to various kinds of data, which need to be tracked/
invalidated as part of incremental builds.
This technique is based on designs used internally in the TypeScript
compiler and is serving as a prototype of this design in ngtsc. If it works
well, it could have benefits across the rest of the compiler.
PR Close #36211
2020-02-26 16:12:39 -08:00
|
|
|
this.host.postProgramCreationCleanup();
|
|
|
|
|
perf(compiler-cli): split Ivy template type-checking into multiple files (#36211)
As a performance optimization, this commit splits the single
__ngtypecheck__.ts file which was previously added to the user's program as
a container for all template type-checking code into multiple .ngtypecheck
shim files, one for each original file in the user's program.
In larger applications, the generation, parsing, and checking of this single
type-checking file was a huge performance bottleneck, with the file often
exceeding 1 MB in text content. Particularly in incremental builds,
regenerating this single file for the entire application proved especially
expensive.
This commit introduces a new strategy for template type-checking code which
makes use of a new interface, the `TypeCheckingProgramStrategy`. This
interface abstracts the process of creating a new `ts.Program` to type-check
a particular compilation, and allows the mechanism there to be kept separate
from the more complex logic around dealing with multiple .ngtypecheck files.
A new `TemplateTypeChecker` hosts that logic and interacts with the
`TypeCheckingProgramStrategy` to actually generate and return diagnostics.
The `TypeCheckContext` class, previously the workhorse of template type-
checking, is now solely focused on collecting and generating type-checking
file contents.
A side effect of implementing the new `TypeCheckingProgramStrategy` in this
way is that the API is designed to be suitable for use by the Angular
Language Service as well. The LS also needs to type-check components, but
has its own method for constructing a `ts.Program` with type-checking code.
Note that this commit does not make the actual checking of templates at all
_incremental_ just yet. That will happen in a future commit.
PR Close #36211
2020-03-04 15:50:12 -08:00
|
|
|
const reusedProgramStrategy = new ReusedProgramStrategy(
|
|
|
|
this.tsProgram, this.host, this.options, this.host.shimExtensionPrefixes);
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
// Create the NgCompiler which will drive the rest of the compilation.
|
perf(compiler-cli): split Ivy template type-checking into multiple files (#36211)
As a performance optimization, this commit splits the single
__ngtypecheck__.ts file which was previously added to the user's program as
a container for all template type-checking code into multiple .ngtypecheck
shim files, one for each original file in the user's program.
In larger applications, the generation, parsing, and checking of this single
type-checking file was a huge performance bottleneck, with the file often
exceeding 1 MB in text content. Particularly in incremental builds,
regenerating this single file for the entire application proved especially
expensive.
This commit introduces a new strategy for template type-checking code which
makes use of a new interface, the `TypeCheckingProgramStrategy`. This
interface abstracts the process of creating a new `ts.Program` to type-check
a particular compilation, and allows the mechanism there to be kept separate
from the more complex logic around dealing with multiple .ngtypecheck files.
A new `TemplateTypeChecker` hosts that logic and interacts with the
`TypeCheckingProgramStrategy` to actually generate and return diagnostics.
The `TypeCheckContext` class, previously the workhorse of template type-
checking, is now solely focused on collecting and generating type-checking
file contents.
A side effect of implementing the new `TypeCheckingProgramStrategy` in this
way is that the API is designed to be suitable for use by the Angular
Language Service as well. The LS also needs to type-check components, but
has its own method for constructing a `ts.Program` with type-checking code.
Note that this commit does not make the actual checking of templates at all
_incremental_ just yet. That will happen in a future commit.
PR Close #36211
2020-03-04 15:50:12 -08:00
|
|
|
this.compiler = new NgCompiler(
|
|
|
|
this.host, options, this.tsProgram, reusedProgramStrategy, reuseProgram, this.perfRecorder);
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
2020-04-07 12:43:43 -07:00
|
|
|
getTsProgram(): ts.Program {
|
|
|
|
return this.tsProgram;
|
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
|
|
|
|
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
2020-01-17 16:00:07 -08:00
|
|
|
undefined): readonly ts.Diagnostic[] {
|
2018-04-06 09:53:10 -07:00
|
|
|
return this.tsProgram.getOptionsDiagnostics(cancellationToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
getTsSyntacticDiagnostics(
|
|
|
|
sourceFile?: ts.SourceFile|undefined,
|
2020-01-17 16:00:07 -08:00
|
|
|
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
|
2020-02-03 10:48:05 -08:00
|
|
|
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
|
|
|
if (sourceFile !== undefined) {
|
|
|
|
if (ignoredFiles.has(sourceFile)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
|
|
|
|
} else {
|
|
|
|
const diagnostics: ts.Diagnostic[] = [];
|
|
|
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
|
|
|
if (!ignoredFiles.has(sf)) {
|
|
|
|
diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return diagnostics;
|
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
getTsSemanticDiagnostics(
|
|
|
|
sourceFile?: ts.SourceFile|undefined,
|
2020-01-17 16:00:07 -08:00
|
|
|
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
|
2020-02-03 10:48:05 -08:00
|
|
|
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
|
|
|
if (sourceFile !== undefined) {
|
|
|
|
if (ignoredFiles.has(sourceFile)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
|
|
|
|
} else {
|
|
|
|
const diagnostics: ts.Diagnostic[] = [];
|
|
|
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
|
|
|
if (!ignoredFiles.has(sf)) {
|
|
|
|
diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return diagnostics;
|
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
|
|
|
undefined): readonly(ts.Diagnostic|api.Diagnostic)[] {
|
|
|
|
return this.compiler.getOptionDiagnostics();
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken|
|
|
|
|
undefined): readonly api.Diagnostic[] {
|
|
|
|
return [];
|
2018-06-26 15:01:09 -07:00
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
getNgSemanticDiagnostics(
|
2020-04-07 12:43:43 -07:00
|
|
|
fileName?: string|undefined, cancellationToken?: ts.CancellationToken|undefined):
|
|
|
|
readonly(ts.Diagnostic|api.Diagnostic)[] {
|
2020-01-17 16:00:07 -08:00
|
|
|
let sf: ts.SourceFile|undefined = undefined;
|
|
|
|
if (fileName !== undefined) {
|
|
|
|
sf = this.tsProgram.getSourceFile(fileName);
|
|
|
|
if (sf === undefined) {
|
|
|
|
// There are no diagnostics for files which don't exist in the program - maybe the caller
|
|
|
|
// has stale data?
|
|
|
|
return [];
|
2019-02-07 19:03:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
const diagnostics = this.compiler.getDiagnostics(sf);
|
|
|
|
this.reuseTsProgram = this.compiler.getNextProgram();
|
|
|
|
return diagnostics;
|
2018-08-23 14:34:55 -07:00
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
/**
|
|
|
|
* Ensure that the `NgCompiler` has properly analyzed the program, and allow for the asynchronous
|
|
|
|
* loading of any resources during the process.
|
|
|
|
*
|
|
|
|
* This is used by the Angular CLI to allow for spawning (async) child compilations for things
|
|
|
|
* like SASS files used in `styleUrls`.
|
|
|
|
*/
|
2020-04-07 12:43:43 -07:00
|
|
|
loadNgStructureAsync(): Promise<void> {
|
|
|
|
return this.compiler.analyzeAsync();
|
|
|
|
}
|
perf(ivy): reuse prior analysis work during incremental builds (#34288)
Previously, the compiler performed an incremental build by analyzing and
resolving all classes in the program (even unchanged ones) and then using
the dependency graph information to determine which .js files were stale and
needed to be re-emitted. This algorithm produced "correct" rebuilds, but the
cost of re-analyzing the entire program turned out to be higher than
anticipated, especially for component-heavy compilations.
To achieve performant rebuilds, it is necessary to reuse previous analysis
results if possible. Doing this safely requires knowing when prior work is
viable and when it is stale and needs to be re-done.
The new algorithm implemented by this commit is such:
1) Each incremental build starts with knowledge of the last known good
dependency graph and analysis results from the last successful build,
plus of course information about the set of files changed.
2) The previous dependency graph's information is used to determine the
set of source files which have "logically" changed. A source file is
considered logically changed if it or any of its dependencies have
physically changed (on disk) since the last successful compilation. Any
logically unchanged dependencies have their dependency information copied
over to the new dependency graph.
3) During the `TraitCompiler`'s loop to consider all source files in the
program, if a source file is logically unchanged then its previous
analyses are "adopted" (and their 'register' steps are run). If the file
is logically changed, then it is re-analyzed as usual.
4) Then, incremental build proceeds as before, with the new dependency graph
being used to determine the set of files which require re-emitting.
This analysis reuse avoids template parsing operations in many circumstances
and significantly reduces the time it takes ngtsc to rebuild a large
application.
Future work will increase performance even more, by tackling a variety of
other opportunities to reuse or avoid work.
PR Close #34288
2019-12-05 16:03:17 -08:00
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
|
|
|
|
return this.compiler.listLazyRoutes(entryRoute);
|
perf(ivy): reuse prior analysis work during incremental builds (#34288)
Previously, the compiler performed an incremental build by analyzing and
resolving all classes in the program (even unchanged ones) and then using
the dependency graph information to determine which .js files were stale and
needed to be re-emitted. This algorithm produced "correct" rebuilds, but the
cost of re-analyzing the entire program turned out to be higher than
anticipated, especially for component-heavy compilations.
To achieve performant rebuilds, it is necessary to reuse previous analysis
results if possible. Doing this safely requires knowing when prior work is
viable and when it is stale and needs to be re-done.
The new algorithm implemented by this commit is such:
1) Each incremental build starts with knowledge of the last known good
dependency graph and analysis results from the last successful build,
plus of course information about the set of files changed.
2) The previous dependency graph's information is used to determine the
set of source files which have "logically" changed. A source file is
considered logically changed if it or any of its dependencies have
physically changed (on disk) since the last successful compilation. Any
logically unchanged dependencies have their dependency information copied
over to the new dependency graph.
3) During the `TraitCompiler`'s loop to consider all source files in the
program, if a source file is logically unchanged then its previous
analyses are "adopted" (and their 'register' steps are run). If the file
is logically changed, then it is re-analyzed as usual.
4) Then, incremental build proceeds as before, with the new dependency graph
being used to determine the set of files which require re-emitting.
This analysis reuse avoids template parsing operations in many circumstances
and significantly reduces the time it takes ngtsc to rebuild a large
application.
Future work will increase performance even more, by tackling a variety of
other opportunities to reuse or avoid work.
PR Close #34288
2019-12-05 16:03:17 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
emit(opts?: {
|
2020-04-07 12:43:43 -07:00
|
|
|
emitFlags?: api.EmitFlags|undefined;
|
|
|
|
cancellationToken?: ts.CancellationToken | undefined;
|
2020-01-17 16:00:07 -08:00
|
|
|
customTransformers?: api.CustomTransformers | undefined;
|
|
|
|
emitCallback?: api.TsEmitCallback | undefined;
|
|
|
|
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback | undefined;
|
|
|
|
}|undefined): ts.EmitResult {
|
2020-02-03 10:48:05 -08:00
|
|
|
const {transformers} = this.compiler.prepareEmit();
|
|
|
|
const ignoreFiles = this.compiler.ignoreForEmit;
|
2018-04-06 09:53:10 -07:00
|
|
|
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
|
|
|
|
|
|
|
const writeFile: ts.WriteFileCallback =
|
|
|
|
(fileName: string, data: string, writeByteOrderMark: boolean,
|
2020-04-07 12:43:43 -07:00
|
|
|
onError: ((message: string) => void)|undefined,
|
|
|
|
sourceFiles: ReadonlyArray<ts.SourceFile>|undefined) => {
|
2019-11-21 14:37:53 -08:00
|
|
|
if (sourceFiles !== undefined) {
|
|
|
|
// Record successful writes for any `ts.SourceFile` (that's not a declaration file)
|
|
|
|
// that's an input to this write.
|
|
|
|
for (const writtenSf of sourceFiles) {
|
|
|
|
if (writtenSf.isDeclarationFile) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
this.compiler.incrementalDriver.recordSuccessfulEmit(writtenSf);
|
2019-11-21 14:37:53 -08:00
|
|
|
}
|
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
|
|
|
};
|
|
|
|
|
2019-01-03 12:23:00 +02:00
|
|
|
const customTransforms = opts && opts.customTransformers;
|
2020-01-17 16:00:07 -08:00
|
|
|
const beforeTransforms = transformers.before || [];
|
|
|
|
const afterDeclarationsTransforms = transformers.afterDeclarations;
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
if (customTransforms !== undefined && customTransforms.beforeTs !== undefined) {
|
2019-01-03 12:23:00 +02:00
|
|
|
beforeTransforms.push(...customTransforms.beforeTs);
|
refactor(ivy): obviate the Bazel component of the ivy_switch (#26550)
Originally, the ivy_switch mechanism used Bazel genrules to conditionally
compile one TS file or another depending on whether ngc or ngtsc was the
selected compiler. This was done because we wanted to avoid importing
certain modules (and thus pulling them into the build) if Ivy was on or
off. This mechanism had a major drawback: ivy_switch became a bottleneck
in the import graph, as it both imports from many places in the codebase
and is imported by many modules in the codebase. This frequently resulted
in cyclic imports which caused issues both with TS and Closure compilation.
It turns out ngcc needs both code paths in the bundle to perform the switch
during its operation anyway, so import switching was later abandoned. This
means that there's no real reason why the ivy_switch mechanism needed to
operate at the Bazel level, and for the ivy_switch file to be a bottleneck.
This commit removes the Bazel-level ivy_switch mechanism, and introduces
an additional TypeScript transform in ngtsc (and the pass-through tsc
compiler used for testing JIT) to perform the same operation that ngcc
does, and flip the switch during ngtsc compilation. This allows the
ivy_switch file to be removed, and the individual switches to be located
directly next to their consumers in the codebase, greatly mitigating the
circular import issues and making the mechanism much easier to use.
As part of this commit, the tag for marking switched variables was changed
from __PRE_NGCC__ to __PRE_R3__, since it's no longer just ngcc which
flips these tags. Most variables were renamed from R3_* to SWITCH_* as well,
since they're referenced mostly in render2 code.
Test strategy: existing test coverage is more than sufficient - if this
didn't work correctly it would break the hello world and todo apps.
PR Close #26550
2018-10-17 15:44:44 -07:00
|
|
|
}
|
2019-01-03 12:23:00 +02:00
|
|
|
|
2019-03-18 11:16:38 -07:00
|
|
|
const emitSpan = this.perfRecorder.start('emit');
|
2019-03-06 14:26:56 -08:00
|
|
|
const emitResults: ts.EmitResult[] = [];
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
|
2019-03-06 14:26:56 -08:00
|
|
|
for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
|
2020-01-17 16:00:07 -08:00
|
|
|
if (targetSourceFile.isDeclarationFile || ignoreFiles.has(targetSourceFile)) {
|
2019-03-06 14:26:56 -08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
if (this.compiler.incrementalDriver.safeToSkipEmit(targetSourceFile)) {
|
2019-03-18 12:25:26 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-03-18 11:16:38 -07:00
|
|
|
const fileEmitSpan = this.perfRecorder.start('emitFile', targetSourceFile);
|
2019-03-06 14:26:56 -08:00
|
|
|
emitResults.push(emitCallback({
|
|
|
|
targetSourceFile,
|
|
|
|
program: this.tsProgram,
|
|
|
|
host: this.host,
|
|
|
|
options: this.options,
|
2020-04-07 12:43:43 -07:00
|
|
|
emitOnlyDtsFiles: false,
|
|
|
|
writeFile,
|
2019-03-06 14:26:56 -08:00
|
|
|
customTransformers: {
|
|
|
|
before: beforeTransforms,
|
|
|
|
after: customTransforms && customTransforms.afterTs,
|
|
|
|
afterDeclarations: afterDeclarationsTransforms,
|
2020-01-17 16:00:07 -08:00
|
|
|
} as any,
|
2019-03-06 14:26:56 -08:00
|
|
|
}));
|
2019-03-18 11:16:38 -07:00
|
|
|
this.perfRecorder.stop(fileEmitSpan);
|
|
|
|
}
|
|
|
|
this.perfRecorder.stop(emitSpan);
|
|
|
|
|
|
|
|
if (this.perfTracker !== null && this.options.tracePerformance !== undefined) {
|
|
|
|
this.perfTracker.serializeToFile(this.options.tracePerformance, this.host);
|
2019-03-06 14:26:56 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in code.
|
2019-03-06 14:26:56 -08:00
|
|
|
return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
2018-06-26 15:01:09 -07:00
|
|
|
|
2019-06-19 17:23:59 -07:00
|
|
|
getIndexedComponents(): Map<ts.Declaration, IndexedComponent> {
|
2020-01-17 16:00:07 -08:00
|
|
|
return this.compiler.getIndexedComponents();
|
2018-06-26 15:01:09 -07:00
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
getLibrarySummaries(): Map<string, api.LibrarySummary> {
|
|
|
|
throw new Error('Method not implemented.');
|
2018-06-26 15:01:09 -07:00
|
|
|
}
|
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
getEmittedGeneratedFiles(): Map<string, GeneratedFile> {
|
|
|
|
throw new Error('Method not implemented.');
|
2018-06-26 15:01:09 -07:00
|
|
|
}
|
2019-01-08 11:49:58 -08:00
|
|
|
|
2020-01-17 16:00:07 -08:00
|
|
|
getEmittedSourceFiles(): Map<string, ts.SourceFile> {
|
|
|
|
throw new Error('Method not implemented.');
|
2019-01-08 11:49:58 -08:00
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
2020-04-07 12:43:43 -07:00
|
|
|
const defaultEmitCallback: api.TsEmitCallback = ({
|
|
|
|
program,
|
|
|
|
targetSourceFile,
|
|
|
|
writeFile,
|
|
|
|
cancellationToken,
|
|
|
|
emitOnlyDtsFiles,
|
|
|
|
customTransformers
|
|
|
|
}) =>
|
|
|
|
program.emit(
|
|
|
|
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
2018-04-06 09:53:10 -07:00
|
|
|
|
|
|
|
function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult {
|
|
|
|
const diagnostics: ts.Diagnostic[] = [];
|
|
|
|
let emitSkipped = false;
|
|
|
|
const emittedFiles: string[] = [];
|
|
|
|
for (const er of emitResults) {
|
|
|
|
diagnostics.push(...er.diagnostics);
|
|
|
|
emitSkipped = emitSkipped || er.emitSkipped;
|
|
|
|
emittedFiles.push(...(er.emittedFiles || []));
|
|
|
|
}
|
2019-03-18 11:16:38 -07:00
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
return {diagnostics, emitSkipped, emittedFiles};
|
|
|
|
}
|