2019-01-29 11:33:37 -08:00
|
|
|
/**
|
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-01-29 11:33:37 -08:00
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
|
|
2020-01-17 16:00:44 -08:00
|
|
|
import {NgCompiler, NgCompilerHost} from './core';
|
|
|
|
|
import {NgCompilerOptions, UnifiedModulesHost} from './core/api';
|
|
|
|
|
import {NodeJSFileSystem, setFileSystem} from './file_system';
|
|
|
|
|
import {NOOP_PERF_RECORDER} 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/src/augmented_program';
|
2019-01-29 11:33:37 -08:00
|
|
|
|
2020-01-17 16:00:44 -08:00
|
|
|
// The following is needed to fix a the chicken-and-egg issue where the sync (into g3) script will
|
|
|
|
|
// refuse to accept this file unless the following string appears:
|
|
|
|
|
// import * as plugin from '@bazel/typescript/internal/tsc_wrapped/plugin_api';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A `ts.CompilerHost` which also returns a list of input files, out of which the `ts.Program`
|
|
|
|
|
* should be created.
|
|
|
|
|
*
|
|
|
|
|
* Currently mirrored from @bazel/typescript/internal/tsc_wrapped/plugin_api (with the naming of
|
|
|
|
|
* `fileNameToModuleName` corrected).
|
|
|
|
|
*/
|
|
|
|
|
interface PluginCompilerHost extends ts.CompilerHost, Partial<UnifiedModulesHost> {
|
|
|
|
|
readonly inputFiles: ReadonlyArray<string>;
|
2019-01-29 11:33:37 -08:00
|
|
|
}
|
|
|
|
|
|
2020-01-17 16:00:44 -08:00
|
|
|
/**
|
|
|
|
|
* Mirrors the plugin interface from tsc_wrapped which is currently under active development. To
|
|
|
|
|
* enable progress to be made in parallel, the upstream interface isn't implemented directly.
|
|
|
|
|
* Instead, `TscPlugin` here is structurally assignable to what tsc_wrapped expects.
|
|
|
|
|
*/
|
|
|
|
|
interface TscPlugin {
|
|
|
|
|
readonly name: string;
|
|
|
|
|
|
|
|
|
|
wrapHost(
|
|
|
|
|
host: ts.CompilerHost&Partial<UnifiedModulesHost>, inputFiles: ReadonlyArray<string>,
|
|
|
|
|
options: ts.CompilerOptions): PluginCompilerHost;
|
|
|
|
|
|
|
|
|
|
setupCompilation(program: ts.Program, oldProgram?: ts.Program): {
|
|
|
|
|
ignoreForDiagnostics: Set<ts.SourceFile>,
|
|
|
|
|
ignoreForEmit: Set<ts.SourceFile>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
getDiagnostics(file?: ts.SourceFile): ts.Diagnostic[];
|
|
|
|
|
|
|
|
|
|
getOptionDiagnostics(): ts.Diagnostic[];
|
|
|
|
|
|
|
|
|
|
getNextProgram(): ts.Program;
|
|
|
|
|
|
2020-02-14 11:21:50 -08:00
|
|
|
createTransformers(): ts.CustomTransformers;
|
2020-01-17 16:00:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A plugin for `tsc_wrapped` which allows Angular compilation from a plain `ts_library`.
|
|
|
|
|
*/
|
2019-01-29 11:33:37 -08:00
|
|
|
export class NgTscPlugin implements TscPlugin {
|
2020-01-17 16:00:44 -08:00
|
|
|
name = 'ngtsc';
|
|
|
|
|
|
|
|
|
|
private options: NgCompilerOptions|null = null;
|
|
|
|
|
private host: NgCompilerHost|null = null;
|
|
|
|
|
private _compiler: NgCompiler|null = null;
|
|
|
|
|
|
|
|
|
|
get compiler(): NgCompiler {
|
|
|
|
|
if (this._compiler === null) {
|
|
|
|
|
throw new Error('Lifecycle error: setupCompilation() must be called first.');
|
|
|
|
|
}
|
|
|
|
|
return this._compiler;
|
2019-01-30 08:30:53 -08:00
|
|
|
}
|
|
|
|
|
|
2020-04-07 12:43:43 -07:00
|
|
|
constructor(private ngOptions: {}) {
|
|
|
|
|
setFileSystem(new NodeJSFileSystem());
|
|
|
|
|
}
|
2020-01-17 16:00:44 -08:00
|
|
|
|
|
|
|
|
wrapHost(
|
|
|
|
|
host: ts.CompilerHost&UnifiedModulesHost, inputFiles: readonly string[],
|
|
|
|
|
options: ts.CompilerOptions): PluginCompilerHost {
|
2020-04-07 12:43:43 -07:00
|
|
|
this.options = {...this.ngOptions, ...options} as NgCompilerOptions;
|
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(host, inputFiles, this.options, /* oldProgram */ null);
|
2020-01-17 16:00:44 -08:00
|
|
|
return this.host;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupCompilation(program: ts.Program, oldProgram?: ts.Program): {
|
|
|
|
|
ignoreForDiagnostics: Set<ts.SourceFile>,
|
|
|
|
|
ignoreForEmit: Set<ts.SourceFile>,
|
|
|
|
|
} {
|
|
|
|
|
if (this.host === null || this.options === null) {
|
|
|
|
|
throw new Error('Lifecycle error: setupCompilation() before wrapHost().');
|
|
|
|
|
}
|
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 typeCheckStrategy = new ReusedProgramStrategy(
|
|
|
|
|
program, this.host, this.options, this.host.shimExtensionPrefixes);
|
|
|
|
|
this._compiler = new NgCompiler(
|
|
|
|
|
this.host, this.options, program, typeCheckStrategy, oldProgram, NOOP_PERF_RECORDER);
|
2020-01-17 16:00:44 -08:00
|
|
|
return {
|
|
|
|
|
ignoreForDiagnostics: this._compiler.ignoreForDiagnostics,
|
|
|
|
|
ignoreForEmit: this._compiler.ignoreForEmit,
|
2019-01-29 11:33:37 -08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 16:00:44 -08:00
|
|
|
getDiagnostics(file?: ts.SourceFile): ts.Diagnostic[] {
|
|
|
|
|
return this.compiler.getDiagnostics(file);
|
2019-01-29 11:33:37 -08:00
|
|
|
}
|
2020-01-17 16:00:44 -08:00
|
|
|
|
2020-04-07 12:43:43 -07:00
|
|
|
getOptionDiagnostics(): ts.Diagnostic[] {
|
|
|
|
|
return this.compiler.getOptionDiagnostics();
|
|
|
|
|
}
|
2020-01-17 16:00:44 -08:00
|
|
|
|
2020-04-07 12:43:43 -07:00
|
|
|
getNextProgram(): ts.Program {
|
|
|
|
|
return this.compiler.getNextProgram();
|
|
|
|
|
}
|
2020-01-17 16:00:44 -08:00
|
|
|
|
2020-04-07 12:43:43 -07:00
|
|
|
createTransformers(): ts.CustomTransformers {
|
|
|
|
|
return this.compiler.prepareEmit().transformers;
|
|
|
|
|
}
|
2019-01-29 11:33:37 -08:00
|
|
|
}
|