mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Previously, the incremental flow for NgCompiler was simple: when creating a new NgCompiler instance, the consumer could pass state from a previous compilation, which would cause the new compilation to be performed incrementally. "Local" information about TypeScript files which had not changed would be passed from the old compilation to the new and reused, while "global" information would always be recalculated. However, this flow could be made more efficient in certain cases, such as when no TypeScript files are changed in a new compilation. In this case, _all_ information extracted during the first compilation is reusable. Doing this involves reusing the previous `NgCompiler` instance (the container for such global information) and updating it, instead of creating a new one for the next compilation. This approach works cleanly, but complicates the lifecycle of `NgCompiler`. To prevent consumers from having to deal with the mechanics of reuse vs incremental steps of `NgCompiler`, a new `CompilationTicket` mechanism is added in this commit. Consumers obtain a `CompilationTicket` via one of several code paths depending on the nature of the incoming compilation, and use the `CompilationTicket` to obtain an `NgCompiler` instance. This instance may be a fresh compilation, a new `NgCompiler` for an incremental compilation, or an existing `NgCompiler` that's been updated to optimally process a resource-only change. Consumers can use the new `NgCompiler` without knowledge of its provenance. PR Close #40561
151 lines
5.5 KiB
TypeScript
151 lines
5.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC 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
|
|
*/
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
import {CompilationTicket, freshCompilationTicket, incrementalFromDriverTicket, NgCompiler, NgCompilerHost} from './core';
|
|
import {NgCompilerOptions, UnifiedModulesHost} from './core/api';
|
|
import {NodeJSFileSystem, setFileSystem} from './file_system';
|
|
import {PatchedProgramIncrementalBuildStrategy} from './incremental';
|
|
import {NOOP_PERF_RECORDER} from './perf';
|
|
import {untagAllTsFiles} from './shims';
|
|
import {OptimizeFor} from './typecheck/api';
|
|
import {ReusedProgramStrategy} from './typecheck/src/augmented_program';
|
|
|
|
// 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>;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
createTransformers(): ts.CustomTransformers;
|
|
}
|
|
|
|
/**
|
|
* A plugin for `tsc_wrapped` which allows Angular compilation from a plain `ts_library`.
|
|
*/
|
|
export class NgTscPlugin implements TscPlugin {
|
|
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;
|
|
}
|
|
|
|
constructor(private ngOptions: {}) {
|
|
setFileSystem(new NodeJSFileSystem());
|
|
}
|
|
|
|
wrapHost(
|
|
host: ts.CompilerHost&Partial<UnifiedModulesHost>, inputFiles: readonly string[],
|
|
options: ts.CompilerOptions): PluginCompilerHost {
|
|
// TODO(alxhub): Eventually the `wrapHost()` API will accept the old `ts.Program` (if one is
|
|
// available). When it does, its `ts.SourceFile`s need to be re-tagged to enable proper
|
|
// incremental compilation.
|
|
this.options = {...this.ngOptions, ...options} as NgCompilerOptions;
|
|
this.host = NgCompilerHost.wrap(host, inputFiles, this.options, /* oldProgram */ null);
|
|
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().');
|
|
}
|
|
this.host.postProgramCreationCleanup();
|
|
untagAllTsFiles(program);
|
|
const typeCheckStrategy = new ReusedProgramStrategy(
|
|
program, this.host, this.options, this.host.shimExtensionPrefixes);
|
|
const strategy = new PatchedProgramIncrementalBuildStrategy();
|
|
const oldDriver = oldProgram !== undefined ? strategy.getIncrementalDriver(oldProgram) : null;
|
|
let ticket: CompilationTicket;
|
|
|
|
let modifiedResourceFiles: Set<string>|undefined = undefined;
|
|
if (this.host.getModifiedResourceFiles !== undefined) {
|
|
modifiedResourceFiles = this.host.getModifiedResourceFiles();
|
|
}
|
|
if (modifiedResourceFiles === undefined) {
|
|
modifiedResourceFiles = new Set<string>();
|
|
}
|
|
|
|
if (oldProgram === undefined || oldDriver === null) {
|
|
ticket = freshCompilationTicket(
|
|
program, this.options, strategy, typeCheckStrategy,
|
|
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
|
|
} else {
|
|
strategy.toNextBuildStrategy().getIncrementalDriver(oldProgram);
|
|
ticket = incrementalFromDriverTicket(
|
|
oldProgram, oldDriver, program, this.options, strategy, typeCheckStrategy,
|
|
modifiedResourceFiles, false, false);
|
|
}
|
|
this._compiler = NgCompiler.fromTicket(ticket, this.host, NOOP_PERF_RECORDER);
|
|
return {
|
|
ignoreForDiagnostics: this._compiler.ignoreForDiagnostics,
|
|
ignoreForEmit: this._compiler.ignoreForEmit,
|
|
};
|
|
}
|
|
|
|
getDiagnostics(file?: ts.SourceFile): ts.Diagnostic[] {
|
|
if (file === undefined) {
|
|
return this.compiler.getDiagnostics();
|
|
}
|
|
return this.compiler.getDiagnosticsForFile(file, OptimizeFor.WholeProgram);
|
|
}
|
|
|
|
getOptionDiagnostics(): ts.Diagnostic[] {
|
|
return this.compiler.getOptionDiagnostics();
|
|
}
|
|
|
|
getNextProgram(): ts.Program {
|
|
return this.compiler.getNextProgram();
|
|
}
|
|
|
|
createTransformers(): ts.CustomTransformers {
|
|
return this.compiler.prepareEmit().transformers;
|
|
}
|
|
}
|