2020-10-09 19:46:55 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2021-01-21 18:54:40 -05:00
|
|
|
import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, resourceChangeTicket} from '@angular/compiler-cli/src/ngtsc/core';
|
2020-10-09 19:46:55 -04:00
|
|
|
import {NgCompilerOptions} from '@angular/compiler-cli/src/ngtsc/core/api';
|
|
|
|
import {TrackedIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
|
2021-03-15 18:23:03 -04:00
|
|
|
import {ActivePerfRecorder} from '@angular/compiler-cli/src/ngtsc/perf';
|
2020-10-09 19:46:55 -04:00
|
|
|
import {TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
|
|
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
|
|
|
|
2020-10-30 18:16:39 -04:00
|
|
|
import {LanguageServiceAdapter} from './adapters';
|
2020-10-09 14:22:03 -04:00
|
|
|
import {isExternalTemplate} from './utils';
|
2020-10-09 19:46:55 -04:00
|
|
|
|
2020-11-04 15:28:59 -05:00
|
|
|
/**
|
|
|
|
* Manages the `NgCompiler` instance which backs the language service, updating or replacing it as
|
|
|
|
* needed to produce an up-to-date understanding of the current program.
|
|
|
|
*
|
|
|
|
* TODO(alxhub): currently the options used for the compiler are specified at `CompilerFactory`
|
|
|
|
* construction, and are not changable. In a real project, users can update `tsconfig.json`. We need
|
|
|
|
* to properly handle a change in the compiler options, either by having an API to update the
|
|
|
|
* `CompilerFactory` to use new options, or by replacing it entirely.
|
|
|
|
*/
|
2020-10-09 19:46:55 -04:00
|
|
|
export class CompilerFactory {
|
|
|
|
private readonly incrementalStrategy = new TrackedIncrementalBuildStrategy();
|
|
|
|
private compiler: NgCompiler|null = null;
|
|
|
|
private lastKnownProgram: ts.Program|null = null;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
private readonly adapter: LanguageServiceAdapter,
|
|
|
|
private readonly programStrategy: TypeCheckingProgramStrategy,
|
2020-11-04 15:28:59 -05:00
|
|
|
private readonly options: NgCompilerOptions,
|
2020-10-09 19:46:55 -04:00
|
|
|
) {}
|
|
|
|
|
2020-11-04 15:28:59 -05:00
|
|
|
getOrCreate(): NgCompiler {
|
2020-10-09 19:46:55 -04:00
|
|
|
const program = this.programStrategy.getProgram();
|
2021-01-21 18:54:40 -05:00
|
|
|
const modifiedResourceFiles = this.adapter.getModifiedResourceFiles() ?? new Set();
|
|
|
|
|
|
|
|
if (this.compiler !== null && program === this.lastKnownProgram) {
|
|
|
|
if (modifiedResourceFiles.size > 0) {
|
|
|
|
// Only resource files have changed since the last NgCompiler was created.
|
|
|
|
const ticket = resourceChangeTicket(this.compiler, modifiedResourceFiles);
|
|
|
|
this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
2021-03-15 18:23:03 -04:00
|
|
|
} else {
|
|
|
|
// The previous NgCompiler is being reused, but we still want to reset its performance
|
|
|
|
// tracker to capture only the operations that are needed to service the current request.
|
|
|
|
this.compiler.perfRecorder.reset();
|
refactor(compiler-cli): introduce CompilationTicket system for NgCompiler (#40561)
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
2021-01-19 16:10:43 -05:00
|
|
|
}
|
2020-11-04 15:28:59 -05:00
|
|
|
|
2021-01-21 18:54:40 -05:00
|
|
|
return this.compiler;
|
2020-10-09 19:46:55 -04:00
|
|
|
}
|
|
|
|
|
2021-01-21 18:54:40 -05:00
|
|
|
let ticket: CompilationTicket;
|
|
|
|
if (this.compiler === null || this.lastKnownProgram === null) {
|
|
|
|
ticket = freshCompilationTicket(
|
2021-03-15 18:23:03 -04:00
|
|
|
program, this.options, this.incrementalStrategy, this.programStrategy,
|
|
|
|
/* perfRecorder */ null, true, true);
|
2021-01-21 18:54:40 -05:00
|
|
|
} else {
|
|
|
|
ticket = incrementalFromCompilerTicket(
|
|
|
|
this.compiler, program, this.incrementalStrategy, this.programStrategy,
|
2021-03-15 18:23:03 -04:00
|
|
|
modifiedResourceFiles, /* perfRecorder */ null);
|
2020-10-09 19:46:55 -04:00
|
|
|
}
|
2021-01-21 18:54:40 -05:00
|
|
|
this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
|
|
|
this.lastKnownProgram = program;
|
|
|
|
return this.compiler;
|
2020-10-09 19:46:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
registerLastKnownProgram() {
|
|
|
|
this.lastKnownProgram = this.programStrategy.getProgram();
|
|
|
|
}
|
|
|
|
}
|