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';
|
2021-04-06 17:59:20 -04:00
|
|
|
import {AbsoluteFsPath, resolve} from '@angular/compiler-cli/src/ngtsc/file_system';
|
2020-10-09 19:46:55 -04:00
|
|
|
import {TrackedIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
|
fix(compiler-cli): ensure the compiler tracks `ts.Program`s correctly (#41291)
`NgCompiler` previously had a notion of the "next" `ts.Program`, which
served two purposes:
* it allowed a client using the `ts.createProgram` API to query for the
latest program produced by the previous `NgCompiler`, as a starting
point for building the _next_ program that incorporated any new user
changes.
* it allowed the old `NgCompiler` to be queried for the `ts.Program` on
which all prior state is based, which is needed to compute the delta
from the new program to ultimately determine how much of the prior
state can be reused.
This system contained a flaw: it relied on the `NgCompiler` knowing when
the `ts.Program` would be changed. This works fine for changes that
originate in `NgCompiler` APIs, but a client of the `TemplateTypeChecker`
may use that API in ways that create new `ts.Program`s without the
`NgCompiler`'s knowledge. This caused the `NgCompiler`'s concept of the
"next" program to get out of sync, causing incorrectness in future
incremental analysis.
This refactoring cleans up the compiler's `ts.Program` management in
several ways:
* `TypeCheckingProgramStrategy`, the API which controls `ts.Program`
updating, is renamed to the `ProgramDriver` and extracted to a separate
ngtsc package.
* It loses its responsibility of determining component shim filenames. That
functionality now lives exclusively in the template type-checking package.
* The "next" `ts.Program` concept is renamed to the "current" program, as
the "next" name was misleading in several ways.
* `NgCompiler` now wraps the `ProgramDriver` used in the
`TemplateTypeChecker` to know when a new `ts.Program` is created,
regardless of which API drove the creation, which actually fixes the bug.
PR Close #41291
2021-03-19 20:06:10 -04:00
|
|
|
import {ProgramDriver} from '@angular/compiler-cli/src/ngtsc/program_driver';
|
2020-10-09 19:46:55 -04:00
|
|
|
|
2020-10-30 18:16:39 -04:00
|
|
|
import {LanguageServiceAdapter} from './adapters';
|
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;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
private readonly adapter: LanguageServiceAdapter,
|
fix(compiler-cli): ensure the compiler tracks `ts.Program`s correctly (#41291)
`NgCompiler` previously had a notion of the "next" `ts.Program`, which
served two purposes:
* it allowed a client using the `ts.createProgram` API to query for the
latest program produced by the previous `NgCompiler`, as a starting
point for building the _next_ program that incorporated any new user
changes.
* it allowed the old `NgCompiler` to be queried for the `ts.Program` on
which all prior state is based, which is needed to compute the delta
from the new program to ultimately determine how much of the prior
state can be reused.
This system contained a flaw: it relied on the `NgCompiler` knowing when
the `ts.Program` would be changed. This works fine for changes that
originate in `NgCompiler` APIs, but a client of the `TemplateTypeChecker`
may use that API in ways that create new `ts.Program`s without the
`NgCompiler`'s knowledge. This caused the `NgCompiler`'s concept of the
"next" program to get out of sync, causing incorrectness in future
incremental analysis.
This refactoring cleans up the compiler's `ts.Program` management in
several ways:
* `TypeCheckingProgramStrategy`, the API which controls `ts.Program`
updating, is renamed to the `ProgramDriver` and extracted to a separate
ngtsc package.
* It loses its responsibility of determining component shim filenames. That
functionality now lives exclusively in the template type-checking package.
* The "next" `ts.Program` concept is renamed to the "current" program, as
the "next" name was misleading in several ways.
* `NgCompiler` now wraps the `ProgramDriver` used in the
`TemplateTypeChecker` to know when a new `ts.Program` is created,
regardless of which API drove the creation, which actually fixes the bug.
PR Close #41291
2021-03-19 20:06:10 -04:00
|
|
|
private readonly programStrategy: ProgramDriver,
|
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-04-06 17:59:20 -04:00
|
|
|
const modifiedResourceFiles = new Set<AbsoluteFsPath>();
|
|
|
|
for (const fileName of this.adapter.getModifiedResourceFiles() ?? []) {
|
|
|
|
modifiedResourceFiles.add(resolve(fileName));
|
|
|
|
}
|
2021-01-21 18:54:40 -05:00
|
|
|
|
2021-04-08 16:14:45 -04:00
|
|
|
if (this.compiler !== null && program === this.compiler.getCurrentProgram()) {
|
2021-01-21 18:54:40 -05:00
|
|
|
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;
|
2021-04-08 16:14:45 -04:00
|
|
|
if (this.compiler === null) {
|
2021-01-21 18:54:40 -05:00
|
|
|
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);
|
|
|
|
return this.compiler;
|
2020-10-09 19:46:55 -04:00
|
|
|
}
|
|
|
|
}
|