2017-11-20 18:21:17 +00:00
|
|
|
|
2017-06-09 21:50:57 +00:00
|
|
|
/**
|
|
|
|
|
* @license
|
2020-05-19 19:08:49 +00:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2017-06-09 21:50:57 +00: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
|
|
|
|
|
*/
|
|
|
|
|
|
2021-06-07 18:58:30 +00:00
|
|
|
import {AotCompiler, AotCompilerOptions, core, createAotCompiler, FormattedMessageChain, GeneratedFile, getMissingNgModuleMetadataErrorData, getParseErrors, isFormattedError, isSyntaxError, MessageBundle, NgAnalyzedFileWithInjectables, NgAnalyzedModules, ParseSourceSpan, PartialModule} from '@angular/compiler';
|
2017-08-02 18:20:07 +00:00
|
|
|
import * as fs from 'fs';
|
2017-06-09 21:50:57 +00:00
|
|
|
import * as path from 'path';
|
2021-09-25 13:10:26 +00:00
|
|
|
import ts from 'typescript';
|
2017-06-09 21:50:57 +00:00
|
|
|
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
import {translateDiagnostics} from '../diagnostics/translate_diagnostics';
|
|
|
|
|
import {createBundleIndexHost, MetadataCollector} from '../metadata';
|
|
|
|
|
import {isAngularCorePackage} from '../ngtsc/core/src/compiler';
|
2018-04-06 16:53:10 +00:00
|
|
|
import {NgtscProgram} from '../ngtsc/program';
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
import {TypeScriptReflectionHost} from '../ngtsc/reflection';
|
2019-10-24 18:24:39 +00:00
|
|
|
import {verifySupportedTypeScriptVersion} from '../typescript_support';
|
2017-06-09 21:50:57 +00:00
|
|
|
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, DiagnosticMessageChain, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitCallback, TsMergeEmitResultsCallback} from './api';
|
2020-04-07 19:43:43 +00:00
|
|
|
import {CodeGenerator, getOriginalReferences, TsCompilerAotCompilerTypeCheckHostAdapter} from './compiler_host';
|
2021-09-30 17:37:11 +00:00
|
|
|
import {getDownlevelDecoratorsTransform} from './downlevel_decorators_transform/index';
|
2021-06-07 18:58:30 +00:00
|
|
|
import {i18nExtract} from './i18n';
|
2020-04-07 19:43:43 +00:00
|
|
|
import {getInlineResourcesTransformFactory, InlineResourcesMetadataTransformer} from './inline_resources';
|
|
|
|
|
import {getExpressionLoweringTransformFactory, LowerMetadataTransform} from './lower_expressions';
|
2018-01-31 01:17:54 +00:00
|
|
|
import {MetadataCache, MetadataTransformer} from './metadata_cache';
|
2017-06-09 21:50:57 +00:00
|
|
|
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
2018-01-31 01:17:54 +00:00
|
|
|
import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
|
2017-11-20 18:21:17 +00:00
|
|
|
import {getAngularClassTransformerFactory} from './r3_transform';
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
import {createMessageDiagnostic, DTS, GENERATED_FILES, isInRootDir, ngToTsDiagnostic, StructureIsReused, TS, tsStructureIsReused} from './util';
|
2017-10-17 23:51:04 +00:00
|
|
|
|
2018-02-16 16:45:21 +00:00
|
|
|
|
2017-09-29 22:02:11 +00:00
|
|
|
/**
|
|
|
|
|
* Maximum number of files that are emitable via calling ts.Program.emit
|
|
|
|
|
* passing individual targetSourceFiles.
|
|
|
|
|
*/
|
|
|
|
|
const MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT = 20;
|
2017-06-09 21:50:57 +00:00
|
|
|
|
2018-02-16 16:45:21 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fields to lower within metadata in render2 mode.
|
|
|
|
|
*/
|
2018-03-30 15:02:30 +00:00
|
|
|
const LOWER_FIELDS = ['useValue', 'useFactory', 'data', 'id', 'loadChildren'];
|
2018-02-16 16:45:21 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fields to lower within metadata in render3 mode.
|
|
|
|
|
*/
|
|
|
|
|
const R3_LOWER_FIELDS = [...LOWER_FIELDS, 'providers', 'imports', 'exports'];
|
|
|
|
|
|
2020-11-05 00:58:29 +00:00
|
|
|
/**
|
|
|
|
|
* Installs a handler for testing purposes to allow inspection of the temporary program.
|
|
|
|
|
*/
|
|
|
|
|
let tempProgramHandlerForTest: ((program: ts.Program) => void)|null = null;
|
|
|
|
|
export function setTempProgramHandlerForTest(handler: (program: ts.Program) => void): void {
|
|
|
|
|
tempProgramHandlerForTest = handler;
|
|
|
|
|
}
|
|
|
|
|
export function resetTempProgramHandlerForTest(): void {
|
|
|
|
|
tempProgramHandlerForTest = null;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-09 21:50:57 +00:00
|
|
|
const emptyModules: NgAnalyzedModules = {
|
|
|
|
|
ngModules: [],
|
|
|
|
|
ngModuleByPipeOrDirective: new Map(),
|
|
|
|
|
files: []
|
|
|
|
|
};
|
|
|
|
|
|
2020-04-07 19:43:43 +00:00
|
|
|
const defaultEmitCallback: TsEmitCallback = ({
|
|
|
|
|
program,
|
|
|
|
|
targetSourceFile,
|
|
|
|
|
writeFile,
|
|
|
|
|
cancellationToken,
|
|
|
|
|
emitOnlyDtsFiles,
|
|
|
|
|
customTransformers
|
|
|
|
|
}) =>
|
|
|
|
|
program.emit(
|
|
|
|
|
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
2017-08-16 22:35:19 +00:00
|
|
|
|
2017-06-09 21:50:57 +00:00
|
|
|
class AngularCompilerProgram implements Program {
|
2017-12-22 17:36:47 +00:00
|
|
|
private rootNames: string[];
|
2018-01-31 01:17:54 +00:00
|
|
|
private metadataCache: MetadataCache;
|
2018-04-06 15:23:40 +00:00
|
|
|
// Metadata cache used exclusively for the flat module index
|
2018-06-18 23:38:33 +00:00
|
|
|
// TODO(issue/24571): remove '!'.
|
2020-04-07 19:43:43 +00:00
|
|
|
private flatModuleMetadataCache!: MetadataCache;
|
2018-01-31 01:17:54 +00:00
|
|
|
private loweringMetadataTransform: LowerMetadataTransform;
|
2017-09-29 22:02:11 +00:00
|
|
|
private oldProgramLibrarySummaries: Map<string, LibrarySummary>|undefined;
|
|
|
|
|
private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined;
|
2017-10-03 16:53:58 +00:00
|
|
|
private oldProgramEmittedSourceFiles: Map<string, ts.SourceFile>|undefined;
|
2017-09-19 18:43:34 +00:00
|
|
|
// Note: This will be cleared out as soon as we create the _tsProgram
|
|
|
|
|
private oldTsProgram: ts.Program|undefined;
|
2017-09-22 01:05:07 +00:00
|
|
|
private emittedLibrarySummaries: LibrarySummary[]|undefined;
|
2017-09-29 22:02:11 +00:00
|
|
|
private emittedGeneratedFiles: GeneratedFile[]|undefined;
|
2017-10-03 16:53:58 +00:00
|
|
|
private emittedSourceFiles: ts.SourceFile[]|undefined;
|
2017-09-19 18:41:47 +00:00
|
|
|
|
2017-06-09 21:50:57 +00:00
|
|
|
// Lazily initialized fields
|
2018-06-18 23:38:33 +00:00
|
|
|
// TODO(issue/24571): remove '!'.
|
2020-04-07 19:43:43 +00:00
|
|
|
private _compiler!: AotCompiler;
|
2018-06-18 23:38:33 +00:00
|
|
|
// TODO(issue/24571): remove '!'.
|
2020-04-07 19:43:43 +00:00
|
|
|
private _hostAdapter!: TsCompilerAotCompilerTypeCheckHostAdapter;
|
2018-06-18 23:38:33 +00:00
|
|
|
// TODO(issue/24571): remove '!'.
|
2020-04-07 19:43:43 +00:00
|
|
|
private _tsProgram!: ts.Program;
|
2017-06-09 21:50:57 +00:00
|
|
|
private _analyzedModules: NgAnalyzedModules|undefined;
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
private _analyzedInjectables: NgAnalyzedFileWithInjectables[]|undefined;
|
2017-09-12 16:40:28 +00:00
|
|
|
private _structuralDiagnostics: Diagnostic[]|undefined;
|
2017-06-09 21:50:57 +00:00
|
|
|
private _programWithStubs: ts.Program|undefined;
|
2017-08-09 20:45:45 +00:00
|
|
|
private _optionsDiagnostics: Diagnostic[] = [];
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
private _transformTsDiagnostics: ts.Diagnostic[] = [];
|
2017-06-09 21:50:57 +00:00
|
|
|
|
|
|
|
|
constructor(
|
2017-12-22 17:36:47 +00:00
|
|
|
rootNames: ReadonlyArray<string>, private options: CompilerOptions,
|
|
|
|
|
private host: CompilerHost, oldProgram?: Program) {
|
|
|
|
|
this.rootNames = [...rootNames];
|
2017-11-20 18:21:17 +00:00
|
|
|
|
2019-10-24 18:24:39 +00:00
|
|
|
if (!options.disableTypeScriptVersionCheck) {
|
|
|
|
|
verifySupportedTypeScriptVersion();
|
|
|
|
|
}
|
2017-11-20 18:21:17 +00:00
|
|
|
|
2017-10-12 23:09:49 +00:00
|
|
|
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
2017-09-19 18:43:34 +00:00
|
|
|
if (oldProgram) {
|
2017-10-12 23:09:49 +00:00
|
|
|
this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
|
|
|
|
|
this.oldProgramEmittedGeneratedFiles = oldProgram.getEmittedGeneratedFiles();
|
|
|
|
|
this.oldProgramEmittedSourceFiles = oldProgram.getEmittedSourceFiles();
|
2017-09-19 18:43:34 +00:00
|
|
|
}
|
2017-09-12 16:40:28 +00:00
|
|
|
|
2017-09-12 22:53:17 +00:00
|
|
|
if (options.flatModuleOutFile) {
|
2017-12-22 17:36:47 +00:00
|
|
|
const {host: bundleHost, indexName, errors} =
|
2018-04-06 15:23:40 +00:00
|
|
|
createBundleIndexHost(options, this.rootNames, host, () => this.flatModuleMetadataCache);
|
2017-08-09 20:45:45 +00:00
|
|
|
if (errors) {
|
2017-08-18 21:03:59 +00:00
|
|
|
this._optionsDiagnostics.push(...errors.map(e => ({
|
|
|
|
|
category: e.category,
|
|
|
|
|
messageText: e.messageText as string,
|
|
|
|
|
source: SOURCE,
|
|
|
|
|
code: DEFAULT_ERROR_CODE
|
|
|
|
|
})));
|
2017-08-09 20:45:45 +00:00
|
|
|
} else {
|
2020-04-07 19:43:43 +00:00
|
|
|
this.rootNames.push(indexName!);
|
2017-09-12 16:40:28 +00:00
|
|
|
this.host = bundleHost;
|
2017-08-09 20:45:45 +00:00
|
|
|
}
|
|
|
|
|
}
|
2018-02-16 16:45:21 +00:00
|
|
|
|
|
|
|
|
this.loweringMetadataTransform =
|
2019-08-20 17:52:31 +00:00
|
|
|
new LowerMetadataTransform(options.enableIvy !== false ? R3_LOWER_FIELDS : LOWER_FIELDS);
|
2018-01-31 01:17:54 +00:00
|
|
|
this.metadataCache = this.createMetadataCache([this.loweringMetadataTransform]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private createMetadataCache(transformers: MetadataTransformer[]) {
|
|
|
|
|
return new MetadataCache(
|
|
|
|
|
new MetadataCollector({quotedNames: true}), !!this.options.strictMetadataEmit,
|
|
|
|
|
transformers);
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2017-09-29 22:02:11 +00:00
|
|
|
getLibrarySummaries(): Map<string, LibrarySummary> {
|
|
|
|
|
const result = new Map<string, LibrarySummary>();
|
|
|
|
|
if (this.oldProgramLibrarySummaries) {
|
|
|
|
|
this.oldProgramLibrarySummaries.forEach((summary, fileName) => result.set(fileName, summary));
|
|
|
|
|
}
|
2017-09-22 01:05:07 +00:00
|
|
|
if (this.emittedLibrarySummaries) {
|
2017-09-29 22:02:11 +00:00
|
|
|
this.emittedLibrarySummaries.forEach(
|
|
|
|
|
(summary, fileName) => result.set(summary.fileName, summary));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getEmittedGeneratedFiles(): Map<string, GeneratedFile> {
|
|
|
|
|
const result = new Map<string, GeneratedFile>();
|
|
|
|
|
if (this.oldProgramEmittedGeneratedFiles) {
|
|
|
|
|
this.oldProgramEmittedGeneratedFiles.forEach(
|
|
|
|
|
(genFile, fileName) => result.set(fileName, genFile));
|
|
|
|
|
}
|
|
|
|
|
if (this.emittedGeneratedFiles) {
|
2017-10-12 23:09:49 +00:00
|
|
|
this.emittedGeneratedFiles.forEach((genFile) => result.set(genFile.genFileUrl, genFile));
|
2017-09-19 18:43:34 +00:00
|
|
|
}
|
2017-09-22 01:05:07 +00:00
|
|
|
return result;
|
2017-09-19 18:43:34 +00:00
|
|
|
}
|
|
|
|
|
|
2017-10-03 16:53:58 +00:00
|
|
|
getEmittedSourceFiles(): Map<string, ts.SourceFile> {
|
|
|
|
|
const result = new Map<string, ts.SourceFile>();
|
|
|
|
|
if (this.oldProgramEmittedSourceFiles) {
|
|
|
|
|
this.oldProgramEmittedSourceFiles.forEach((sf, fileName) => result.set(fileName, sf));
|
|
|
|
|
}
|
|
|
|
|
if (this.emittedSourceFiles) {
|
2017-10-12 23:09:49 +00:00
|
|
|
this.emittedSourceFiles.forEach((sf) => result.set(sf.fileName, sf));
|
2017-10-03 16:53:58 +00:00
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-07 19:43:43 +00:00
|
|
|
getTsProgram(): ts.Program {
|
|
|
|
|
return this.tsProgram;
|
|
|
|
|
}
|
2017-06-09 21:50:57 +00:00
|
|
|
|
|
|
|
|
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken) {
|
|
|
|
|
return this.tsProgram.getOptionsDiagnostics(cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-22 17:36:47 +00:00
|
|
|
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): ReadonlyArray<Diagnostic> {
|
2017-08-09 20:45:45 +00:00
|
|
|
return [...this._optionsDiagnostics, ...getNgOptionDiagnostics(this.options)];
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getTsSyntacticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
2017-12-22 17:36:47 +00:00
|
|
|
ReadonlyArray<ts.Diagnostic> {
|
2017-06-09 21:50:57 +00:00
|
|
|
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-22 17:36:47 +00:00
|
|
|
getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken): ReadonlyArray<Diagnostic> {
|
2017-06-09 21:50:57 +00:00
|
|
|
return this.structuralDiagnostics;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
2017-12-22 17:36:47 +00:00
|
|
|
ReadonlyArray<ts.Diagnostic> {
|
2017-10-26 16:45:01 +00:00
|
|
|
const sourceFiles = sourceFile ? [sourceFile] : this.tsProgram.getSourceFiles();
|
2017-10-05 20:48:40 +00:00
|
|
|
let diags: ts.Diagnostic[] = [];
|
2017-10-26 16:45:01 +00:00
|
|
|
sourceFiles.forEach(sf => {
|
2017-10-05 20:48:40 +00:00
|
|
|
if (!GENERATED_FILES.test(sf.fileName)) {
|
|
|
|
|
diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
|
|
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
});
|
2017-10-05 20:48:40 +00:00
|
|
|
return diags;
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2017-10-12 23:09:49 +00:00
|
|
|
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
|
2017-12-22 17:36:47 +00:00
|
|
|
ReadonlyArray<Diagnostic> {
|
2017-10-05 20:48:40 +00:00
|
|
|
let diags: ts.Diagnostic[] = [];
|
2017-10-12 23:09:49 +00:00
|
|
|
this.tsProgram.getSourceFiles().forEach(sf => {
|
|
|
|
|
if (GENERATED_FILES.test(sf.fileName) && !sf.isDeclarationFile) {
|
|
|
|
|
diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
|
2017-10-05 20:48:40 +00:00
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
});
|
2017-10-20 16:46:41 +00:00
|
|
|
const {ng} = translateDiagnostics(this.hostAdapter, diags);
|
2017-10-05 20:48:40 +00:00
|
|
|
return ng;
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadNgStructureAsync(): Promise<void> {
|
2017-09-12 16:40:28 +00:00
|
|
|
if (this._analyzedModules) {
|
|
|
|
|
throw new Error('Angular structure already loaded');
|
|
|
|
|
}
|
2017-10-26 22:24:54 +00:00
|
|
|
return Promise.resolve()
|
|
|
|
|
.then(() => {
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
const {tmpProgram, sourceFiles, tsFiles, rootNames} = this._createProgramWithBasicStubs();
|
|
|
|
|
return this.compiler.loadFilesAsync(sourceFiles, tsFiles)
|
|
|
|
|
.then(({analyzedModules, analyzedInjectables}) => {
|
|
|
|
|
if (this._analyzedModules) {
|
|
|
|
|
throw new Error('Angular structure loaded both synchronously and asynchronously');
|
|
|
|
|
}
|
|
|
|
|
this._updateProgramWithTypeCheckStubs(
|
|
|
|
|
tmpProgram, analyzedModules, analyzedInjectables, rootNames);
|
|
|
|
|
});
|
2017-10-26 22:24:54 +00:00
|
|
|
})
|
|
|
|
|
.catch(e => this._createProgramOnError(e));
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2017-10-20 16:46:41 +00:00
|
|
|
listLazyRoutes(route?: string): LazyRoute[] {
|
2021-09-25 20:59:24 +00:00
|
|
|
return [];
|
2017-10-20 16:46:41 +00:00
|
|
|
}
|
|
|
|
|
|
2017-11-20 18:21:17 +00:00
|
|
|
emit(parameters: {
|
|
|
|
|
emitFlags?: EmitFlags,
|
|
|
|
|
cancellationToken?: ts.CancellationToken,
|
|
|
|
|
customTransformers?: CustomTransformers,
|
2018-03-20 23:11:20 +00:00
|
|
|
emitCallback?: TsEmitCallback,
|
|
|
|
|
mergeEmitResultsCallback?: TsMergeEmitResultsCallback,
|
2017-11-20 18:21:17 +00:00
|
|
|
} = {}): ts.EmitResult {
|
2019-08-20 17:52:31 +00:00
|
|
|
if (this.options.enableIvy !== false) {
|
2018-04-06 16:53:10 +00:00
|
|
|
throw new Error('Cannot run legacy compiler in ngtsc mode');
|
|
|
|
|
}
|
2019-02-08 11:37:21 +00:00
|
|
|
return this._emitRender2(parameters);
|
2017-11-20 18:21:17 +00:00
|
|
|
}
|
|
|
|
|
|
2020-04-07 19:43:43 +00:00
|
|
|
private _emitRender2({
|
|
|
|
|
emitFlags = EmitFlags.Default,
|
|
|
|
|
cancellationToken,
|
|
|
|
|
customTransformers,
|
|
|
|
|
emitCallback = defaultEmitCallback,
|
|
|
|
|
mergeEmitResultsCallback = mergeEmitResults,
|
|
|
|
|
}: {
|
|
|
|
|
emitFlags?: EmitFlags,
|
|
|
|
|
cancellationToken?: ts.CancellationToken,
|
|
|
|
|
customTransformers?: CustomTransformers,
|
|
|
|
|
emitCallback?: TsEmitCallback,
|
|
|
|
|
mergeEmitResultsCallback?: TsMergeEmitResultsCallback,
|
|
|
|
|
} = {}): ts.EmitResult {
|
2017-09-29 22:02:11 +00:00
|
|
|
const emitStart = Date.now();
|
2017-09-12 22:53:17 +00:00
|
|
|
if (emitFlags & EmitFlags.I18nBundle) {
|
|
|
|
|
const locale = this.options.i18nOutLocale || null;
|
|
|
|
|
const file = this.options.i18nOutFile || null;
|
|
|
|
|
const format = this.options.i18nOutFormat || null;
|
|
|
|
|
const bundle = this.compiler.emitMessageBundle(this.analyzedModules, locale);
|
|
|
|
|
i18nExtract(format, file, this.host, this.options, bundle);
|
|
|
|
|
}
|
2017-09-19 18:41:47 +00:00
|
|
|
if ((emitFlags & (EmitFlags.JS | EmitFlags.DTS | EmitFlags.Metadata | EmitFlags.Codegen)) ===
|
2017-09-12 16:40:28 +00:00
|
|
|
0) {
|
2017-10-12 23:09:49 +00:00
|
|
|
return {emitSkipped: true, diagnostics: [], emittedFiles: []};
|
2017-09-12 16:40:28 +00:00
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
let {genFiles, genDiags} = this.generateFilesForEmit(emitFlags);
|
|
|
|
|
if (genDiags.length) {
|
|
|
|
|
return {
|
|
|
|
|
diagnostics: genDiags,
|
|
|
|
|
emitSkipped: true,
|
|
|
|
|
emittedFiles: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
this.emittedGeneratedFiles = genFiles;
|
2017-09-22 01:05:07 +00:00
|
|
|
const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = [];
|
2017-10-12 23:09:49 +00:00
|
|
|
const genFileByFileName = new Map<string, GeneratedFile>();
|
|
|
|
|
genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
|
|
|
|
|
this.emittedLibrarySummaries = [];
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
this._transformTsDiagnostics = [];
|
2017-10-12 23:09:49 +00:00
|
|
|
const emittedSourceFiles = [] as ts.SourceFile[];
|
2017-09-22 01:05:07 +00:00
|
|
|
const writeTsFile: ts.WriteFileCallback =
|
|
|
|
|
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
|
|
|
|
|
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
|
|
|
|
|
let genFile: GeneratedFile|undefined;
|
|
|
|
|
if (sourceFile) {
|
|
|
|
|
outSrcMapping.push({outFileName: outFileName, sourceFile});
|
2017-10-12 23:09:49 +00:00
|
|
|
genFile = genFileByFileName.get(sourceFile.fileName);
|
|
|
|
|
if (!sourceFile.isDeclarationFile && !GENERATED_FILES.test(sourceFile.fileName)) {
|
|
|
|
|
// Note: sourceFile is the transformed sourcefile, not the original one!
|
2018-02-08 16:59:25 +00:00
|
|
|
const originalFile = this.tsProgram.getSourceFile(sourceFile.fileName);
|
|
|
|
|
if (originalFile) {
|
|
|
|
|
emittedSourceFiles.push(originalFile);
|
|
|
|
|
}
|
2017-10-03 16:53:58 +00:00
|
|
|
}
|
2017-09-22 01:05:07 +00:00
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
|
2017-09-22 01:05:07 +00:00
|
|
|
};
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
|
|
|
|
|
const modules = this._analyzedInjectables &&
|
|
|
|
|
this.compiler.emitAllPartialModules2(this._analyzedInjectables);
|
|
|
|
|
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
const tsCustomTransformers =
|
|
|
|
|
this.calculateTransforms(genFileByFileName, modules, customTransformers);
|
2017-09-29 22:02:11 +00:00
|
|
|
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
|
2017-09-20 16:54:47 +00:00
|
|
|
// Restore the original references before we emit so TypeScript doesn't emit
|
|
|
|
|
// a reference to the .d.ts file.
|
2017-12-22 17:36:47 +00:00
|
|
|
const augmentedReferences = new Map<ts.SourceFile, ReadonlyArray<ts.FileReference>>();
|
2017-10-12 23:09:49 +00:00
|
|
|
for (const sourceFile of this.tsProgram.getSourceFiles()) {
|
2017-09-20 16:54:47 +00:00
|
|
|
const originalReferences = getOriginalReferences(sourceFile);
|
|
|
|
|
if (originalReferences) {
|
|
|
|
|
augmentedReferences.set(sourceFile, sourceFile.referencedFiles);
|
|
|
|
|
sourceFile.referencedFiles = originalReferences;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
const genTsFiles: GeneratedFile[] = [];
|
|
|
|
|
const genJsonFiles: GeneratedFile[] = [];
|
|
|
|
|
genFiles.forEach(gf => {
|
|
|
|
|
if (gf.stmts) {
|
|
|
|
|
genTsFiles.push(gf);
|
|
|
|
|
}
|
|
|
|
|
if (gf.source) {
|
|
|
|
|
genJsonFiles.push(gf);
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-09-20 16:54:47 +00:00
|
|
|
let emitResult: ts.EmitResult;
|
2017-10-12 23:09:49 +00:00
|
|
|
let emittedUserTsCount: number;
|
2017-09-20 16:54:47 +00:00
|
|
|
try {
|
2017-10-12 23:09:49 +00:00
|
|
|
const sourceFilesToEmit = this.getSourceFilesForEmit();
|
|
|
|
|
if (sourceFilesToEmit &&
|
|
|
|
|
(sourceFilesToEmit.length + genTsFiles.length) < MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT) {
|
|
|
|
|
const fileNamesToEmit =
|
|
|
|
|
[...sourceFilesToEmit.map(sf => sf.fileName), ...genTsFiles.map(gf => gf.genFileUrl)];
|
2018-03-20 23:11:20 +00:00
|
|
|
emitResult = mergeEmitResultsCallback(
|
2017-10-12 23:09:49 +00:00
|
|
|
fileNamesToEmit.map((fileName) => emitResult = emitCallback({
|
|
|
|
|
program: this.tsProgram,
|
|
|
|
|
host: this.host,
|
|
|
|
|
options: this.options,
|
2020-04-07 19:43:43 +00:00
|
|
|
writeFile: writeTsFile,
|
|
|
|
|
emitOnlyDtsFiles,
|
2018-01-31 01:17:54 +00:00
|
|
|
customTransformers: tsCustomTransformers,
|
2017-10-12 23:09:49 +00:00
|
|
|
targetSourceFile: this.tsProgram.getSourceFile(fileName),
|
|
|
|
|
})));
|
|
|
|
|
emittedUserTsCount = sourceFilesToEmit.length;
|
|
|
|
|
} else {
|
|
|
|
|
emitResult = emitCallback({
|
|
|
|
|
program: this.tsProgram,
|
|
|
|
|
host: this.host,
|
|
|
|
|
options: this.options,
|
2020-04-07 19:43:43 +00:00
|
|
|
writeFile: writeTsFile,
|
|
|
|
|
emitOnlyDtsFiles,
|
2018-01-31 01:17:54 +00:00
|
|
|
customTransformers: tsCustomTransformers
|
2017-10-12 23:09:49 +00:00
|
|
|
});
|
|
|
|
|
emittedUserTsCount = this.tsProgram.getSourceFiles().length - genTsFiles.length;
|
|
|
|
|
}
|
2017-09-20 16:54:47 +00:00
|
|
|
} finally {
|
|
|
|
|
// Restore the references back to the augmented value to ensure that the
|
|
|
|
|
// checks that TypeScript makes for project structure reuse will succeed.
|
|
|
|
|
for (const [sourceFile, references] of Array.from(augmentedReferences)) {
|
2017-12-22 17:36:47 +00:00
|
|
|
// TODO(chuckj): Remove any cast after updating build to 2.6
|
|
|
|
|
(sourceFile as any).referencedFiles = references;
|
2017-09-20 16:54:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
this.emittedSourceFiles = emittedSourceFiles;
|
2017-11-02 21:49:38 +00:00
|
|
|
|
|
|
|
|
// Match behavior of tsc: only produce emit diagnostics if it would block
|
|
|
|
|
// emit. If noEmitOnError is false, the emit will happen in spite of any
|
|
|
|
|
// errors, so we should not report them.
|
2019-02-02 09:02:16 +00:00
|
|
|
if (emitResult && this.options.noEmitOnError === true) {
|
2017-11-02 21:49:38 +00:00
|
|
|
// translate the diagnostics in the emitResult as well.
|
|
|
|
|
const translatedEmitDiags = translateDiagnostics(this.hostAdapter, emitResult.diagnostics);
|
|
|
|
|
emitResult.diagnostics = translatedEmitDiags.ts.concat(
|
|
|
|
|
this.structuralDiagnostics.concat(translatedEmitDiags.ng).map(ngToTsDiagnostic));
|
|
|
|
|
}
|
2017-09-19 18:41:47 +00:00
|
|
|
|
2019-02-02 09:02:16 +00:00
|
|
|
if (emitResult && !outSrcMapping.length) {
|
2017-09-12 16:40:28 +00:00
|
|
|
// if no files were emitted by TypeScript, also don't emit .json files
|
2017-12-22 17:36:47 +00:00
|
|
|
emitResult.diagnostics =
|
|
|
|
|
emitResult.diagnostics.concat([createMessageDiagnostic(`Emitted no files.`)]);
|
2017-09-12 16:40:28 +00:00
|
|
|
return emitResult;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-28 18:42:58 +00:00
|
|
|
let sampleSrcFileName: string|undefined;
|
|
|
|
|
let sampleOutFileName: string|undefined;
|
|
|
|
|
if (outSrcMapping.length) {
|
|
|
|
|
sampleSrcFileName = outSrcMapping[0].sourceFile.fileName;
|
|
|
|
|
sampleOutFileName = outSrcMapping[0].outFileName;
|
|
|
|
|
}
|
|
|
|
|
const srcToOutPath =
|
|
|
|
|
createSrcToOutPathMapper(this.options.outDir, sampleSrcFileName, sampleOutFileName);
|
2017-09-19 18:41:47 +00:00
|
|
|
if (emitFlags & EmitFlags.Codegen) {
|
2017-10-12 23:09:49 +00:00
|
|
|
genJsonFiles.forEach(gf => {
|
|
|
|
|
const outFileName = srcToOutPath(gf.genFileUrl);
|
2020-04-07 19:43:43 +00:00
|
|
|
this.writeFile(outFileName, gf.source!, false, undefined, gf);
|
2017-10-12 23:09:49 +00:00
|
|
|
});
|
2017-09-12 16:40:28 +00:00
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
let metadataJsonCount = 0;
|
2017-09-12 16:40:28 +00:00
|
|
|
if (emitFlags & EmitFlags.Metadata) {
|
2017-10-12 23:09:49 +00:00
|
|
|
this.tsProgram.getSourceFiles().forEach(sf => {
|
|
|
|
|
if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) {
|
|
|
|
|
metadataJsonCount++;
|
|
|
|
|
const metadata = this.metadataCache.getMetadata(sf);
|
2017-12-16 01:30:41 +00:00
|
|
|
if (metadata) {
|
|
|
|
|
const metadataText = JSON.stringify([metadata]);
|
|
|
|
|
const outFileName = srcToOutPath(sf.fileName.replace(/\.tsx?$/, '.metadata.json'));
|
|
|
|
|
this.writeFile(outFileName, metadataText, false, undefined, undefined, [sf]);
|
|
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
}
|
|
|
|
|
});
|
2017-09-12 22:53:17 +00:00
|
|
|
}
|
2017-09-29 22:02:11 +00:00
|
|
|
const emitEnd = Date.now();
|
2019-02-02 09:02:16 +00:00
|
|
|
if (emitResult && this.options.diagnostics) {
|
2017-12-22 17:36:47 +00:00
|
|
|
emitResult.diagnostics = emitResult.diagnostics.concat([createMessageDiagnostic([
|
2017-09-29 22:02:11 +00:00
|
|
|
`Emitted in ${emitEnd - emitStart}ms`,
|
2017-10-12 23:09:49 +00:00
|
|
|
`- ${emittedUserTsCount} user ts files`,
|
|
|
|
|
`- ${genTsFiles.length} generated ts files`,
|
|
|
|
|
`- ${genJsonFiles.length + metadataJsonCount} generated json files`,
|
2017-12-22 17:36:47 +00:00
|
|
|
].join('\n'))]);
|
2017-09-29 22:02:11 +00:00
|
|
|
}
|
2018-04-02 20:05:08 +00:00
|
|
|
|
2017-09-12 16:40:28 +00:00
|
|
|
return emitResult;
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Private members
|
2017-09-12 16:40:28 +00:00
|
|
|
private get compiler(): AotCompiler {
|
|
|
|
|
if (!this._compiler) {
|
2017-10-20 16:46:41 +00:00
|
|
|
this._createCompiler();
|
2017-09-12 16:40:28 +00:00
|
|
|
}
|
2020-04-07 19:43:43 +00:00
|
|
|
return this._compiler!;
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2017-10-20 16:46:41 +00:00
|
|
|
private get hostAdapter(): TsCompilerAotCompilerTypeCheckHostAdapter {
|
|
|
|
|
if (!this._hostAdapter) {
|
|
|
|
|
this._createCompiler();
|
|
|
|
|
}
|
2020-04-07 19:43:43 +00:00
|
|
|
return this._hostAdapter!;
|
2017-10-20 16:46:41 +00:00
|
|
|
}
|
|
|
|
|
|
2017-09-12 16:40:28 +00:00
|
|
|
private get analyzedModules(): NgAnalyzedModules {
|
|
|
|
|
if (!this._analyzedModules) {
|
|
|
|
|
this.initSync();
|
|
|
|
|
}
|
2020-04-07 19:43:43 +00:00
|
|
|
return this._analyzedModules!;
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2017-12-22 17:36:47 +00:00
|
|
|
private get structuralDiagnostics(): ReadonlyArray<Diagnostic> {
|
2017-11-15 01:49:47 +00:00
|
|
|
let diagnostics = this._structuralDiagnostics;
|
|
|
|
|
if (!diagnostics) {
|
2017-09-12 16:40:28 +00:00
|
|
|
this.initSync();
|
2017-11-15 01:49:47 +00:00
|
|
|
diagnostics = (this._structuralDiagnostics = this._structuralDiagnostics || []);
|
2017-09-12 16:40:28 +00:00
|
|
|
}
|
2017-11-15 01:49:47 +00:00
|
|
|
return diagnostics;
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2017-09-12 16:40:28 +00:00
|
|
|
private get tsProgram(): ts.Program {
|
|
|
|
|
if (!this._tsProgram) {
|
|
|
|
|
this.initSync();
|
|
|
|
|
}
|
2020-04-07 19:43:43 +00:00
|
|
|
return this._tsProgram!;
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
/** Whether the program is compiling the Angular core package. */
|
|
|
|
|
private get isCompilingAngularCore(): boolean {
|
|
|
|
|
if (this._isCompilingAngularCore !== null) {
|
|
|
|
|
return this._isCompilingAngularCore;
|
2018-02-16 16:45:21 +00:00
|
|
|
}
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
return this._isCompilingAngularCore = isAngularCorePackage(this.tsProgram);
|
2018-02-16 16:45:21 +00:00
|
|
|
}
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
private _isCompilingAngularCore: boolean|null = null;
|
2018-02-16 16:45:21 +00:00
|
|
|
|
2017-10-12 23:09:49 +00:00
|
|
|
private calculateTransforms(
|
2017-11-20 18:21:17 +00:00
|
|
|
genFiles: Map<string, GeneratedFile>|undefined, partialModules: PartialModule[]|undefined,
|
2017-10-12 23:09:49 +00:00
|
|
|
customTransformers?: CustomTransformers): ts.CustomTransformers {
|
2018-03-06 22:53:01 +00:00
|
|
|
const beforeTs: Array<ts.TransformerFactory<ts.SourceFile>> = [];
|
|
|
|
|
const metadataTransforms: MetadataTransformer[] = [];
|
2018-04-06 15:23:40 +00:00
|
|
|
const flatModuleMetadataTransforms: MetadataTransformer[] = [];
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
const annotateForClosureCompiler = this.options.annotateForClosureCompiler || false;
|
|
|
|
|
|
2018-03-06 22:53:01 +00:00
|
|
|
if (this.options.enableResourceInlining) {
|
|
|
|
|
beforeTs.push(getInlineResourcesTransformFactory(this.tsProgram, this.hostAdapter));
|
2018-04-06 15:23:40 +00:00
|
|
|
const transformer = new InlineResourcesMetadataTransformer(this.hostAdapter);
|
|
|
|
|
metadataTransforms.push(transformer);
|
|
|
|
|
flatModuleMetadataTransforms.push(transformer);
|
2018-03-06 22:53:01 +00:00
|
|
|
}
|
2018-02-16 16:45:21 +00:00
|
|
|
|
2017-07-13 21:25:17 +00:00
|
|
|
if (!this.options.disableExpressionLowering) {
|
2018-01-31 01:17:54 +00:00
|
|
|
beforeTs.push(
|
|
|
|
|
getExpressionLoweringTransformFactory(this.loweringMetadataTransform, this.tsProgram));
|
2018-03-06 22:53:01 +00:00
|
|
|
metadataTransforms.push(this.loweringMetadataTransform);
|
2017-07-13 21:25:17 +00:00
|
|
|
}
|
2017-11-20 18:21:17 +00:00
|
|
|
if (genFiles) {
|
2020-03-07 16:14:25 +00:00
|
|
|
beforeTs.push(getAngularEmitterTransformFactory(
|
|
|
|
|
genFiles, this.getTsProgram(), annotateForClosureCompiler));
|
2017-11-20 18:21:17 +00:00
|
|
|
}
|
|
|
|
|
if (partialModules) {
|
2020-03-07 16:14:25 +00:00
|
|
|
beforeTs.push(getAngularClassTransformerFactory(partialModules, annotateForClosureCompiler));
|
2018-01-31 01:17:54 +00:00
|
|
|
|
|
|
|
|
// If we have partial modules, the cached metadata might be incorrect as it doesn't reflect
|
|
|
|
|
// the partial module transforms.
|
2018-04-06 15:23:40 +00:00
|
|
|
const transformer = new PartialModuleMetadataTransformer(partialModules);
|
|
|
|
|
metadataTransforms.push(transformer);
|
|
|
|
|
flatModuleMetadataTransforms.push(transformer);
|
2017-11-20 18:21:17 +00:00
|
|
|
}
|
2018-02-16 16:45:21 +00:00
|
|
|
|
2017-08-16 22:35:19 +00:00
|
|
|
if (customTransformers && customTransformers.beforeTs) {
|
|
|
|
|
beforeTs.push(...customTransformers.beforeTs);
|
|
|
|
|
}
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
|
|
|
|
|
// If decorators should be converted to static fields (enabled by default), we set up
|
|
|
|
|
// the decorator downlevel transform. Note that we set it up as last transform as that
|
|
|
|
|
// allows custom transformers to strip Angular decorators without having to deal with
|
|
|
|
|
// identifying static properties. e.g. it's more difficult handling `<..>.decorators`
|
|
|
|
|
// or `<..>.ctorParameters` compared to the `ts.Decorator` AST nodes.
|
|
|
|
|
if (this.options.annotationsAs !== 'decorators') {
|
|
|
|
|
const typeChecker = this.getTsProgram().getTypeChecker();
|
|
|
|
|
const reflectionHost = new TypeScriptReflectionHost(typeChecker);
|
|
|
|
|
// Similarly to how we handled tsickle decorator downleveling in the past, we just
|
|
|
|
|
// ignore diagnostics that have been collected by the transformer. These are
|
|
|
|
|
// non-significant failures that shouldn't prevent apps from compiling.
|
|
|
|
|
beforeTs.push(getDownlevelDecoratorsTransform(
|
2020-06-11 22:22:00 +00:00
|
|
|
typeChecker, reflectionHost, [], this.isCompilingAngularCore, annotateForClosureCompiler,
|
|
|
|
|
/* skipClassDecorators */ false));
|
fix(compiler-cli): downlevel angular decorators to static properties (#37382)
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline.
This had the negative potential of breaking ES2015 output and SSR due
to a limitation in TypeScript.
TypeScript by default preserves type information for decorated constructor
parameters when `emitDecoratorMetadata` is enabled. For example,
consider this snippet below:
```
@Directive()
export class MyDirective {
constructor(button: MyButton) {}
}
export class MyButton {}
```
TypeScript would generate metadata for the `MyDirective` class it has
a decorator applied. This metadata would be needed in JIT mode, or
for libraries that provide `MyDirective` through NPM. The metadata would
look as followed:
```
let MyDirective = class MyDir {}
MyDirective = __decorate([
Directive(),
__metadata("design:paramtypes", [MyButton]),
], MyDirective);
let MyButton = class MyButton {}
```
Notice that TypeScript generated calls to `__decorate` and
`__metadata`. These calls are needed so that the Angular compiler
is able to determine whether `MyDirective` is actually an directive,
and what types are needed for dependency injection.
The limitation surfaces in this concrete example because `MyButton`
is declared after the `__metadata(..)` call, while `__metadata`
actually directly references `MyButton`. This is illegal though because
`MyButton` has not been declared at this point. This is due to the
so-called temporal dead zone in JavaScript. Errors like followed will
be reported at runtime when such file/code evaluates:
```
Uncaught ReferenceError: Cannot access 'MyButton' before initialization
```
As noted, this is a TypeScript limitation because ideally TypeScript
shouldn't evaluate `__metadata`/reference `MyButton` immediately.
Instead, it should defer the reference until `MyButton` is actually
declared. This limitation will not be fixed by the TypeScript team
though because it's a limitation as per current design and they will
only revisit this once the tc39 decorator proposal is finalized
(currently stage-2 at time of writing).
Given this wontfix on the TypeScript side, and our heavy reliance on
this metadata in libraries (and for JIT mode), we intend to fix this
from within the Angular compiler by downleveling decorators to static
properties that don't need to evaluate directly. For example:
```
MyDirective.ctorParameters = () => [MyButton];
```
With this snippet above, `MyButton` is not referenced directly. Only
lazily when the Angular runtime needs it. This mitigates the temporal
dead zone issue caused by a limitation in TypeScript's decorator
metadata output. See: https://github.com/microsoft/TypeScript/issues/27519.
In the past (as noted; before version 7), the Angular compiler by
default used tsickle that already performed this transformation. We
moved the transformation to the CLI for JIT and `ng-packager`, but now
we realize that we can move this all to a single place in the compiler
so that standalone ngc consumers can benefit too, and that we can
disable tsickle in our Bazel `ngc-wrapped` pipeline (that currently
still relies on tsickle to perform this decorator processing).
This transformation also has another positive side-effect of making
Angular application/library code more compatible with server-side
rendering. In principle, TypeScript would also preserve type information
for decorated class members (similar to how it did that for constructor
parameters) at runtime. This becomes an issue when your application
relies on native DOM globals for decorated class member types. e.g.
```
@Input() panelElement: HTMLElement;
```
Your application code would then reference `HTMLElement` directly
whenever the source file is loaded in NodeJS for SSR. `HTMLElement`
does not exist on the server though, so that will become an invalid
reference. One could work around this by providing global mocks for
these DOM symbols, but that doesn't match up with other places where
dependency injection is used for mocking DOM/browser specific symbols.
More context in this issue: #30586. The TL;DR here is that the Angular
compiler does not care about types for these class members, so it won't
ever reference `HTMLElement` at runtime.
Fixes #30106. Fixes #30586. Fixes #30141.
Resolves FW-2196. Resolves FW-2199.
PR Close #37382
2020-06-05 14:26:23 +00:00
|
|
|
}
|
|
|
|
|
|
2018-03-06 22:53:01 +00:00
|
|
|
if (metadataTransforms.length > 0) {
|
|
|
|
|
this.metadataCache = this.createMetadataCache(metadataTransforms);
|
|
|
|
|
}
|
2018-04-06 15:23:40 +00:00
|
|
|
if (flatModuleMetadataTransforms.length > 0) {
|
|
|
|
|
this.flatModuleMetadataCache = this.createMetadataCache(flatModuleMetadataTransforms);
|
|
|
|
|
}
|
2017-08-16 22:35:19 +00:00
|
|
|
const afterTs = customTransformers ? customTransformers.afterTs : undefined;
|
|
|
|
|
return {before: beforeTs, after: afterTs};
|
2017-07-13 21:25:17 +00:00
|
|
|
}
|
|
|
|
|
|
2017-09-12 16:40:28 +00:00
|
|
|
private initSync() {
|
|
|
|
|
if (this._analyzedModules) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
const {tmpProgram, sourceFiles, tsFiles, rootNames} = this._createProgramWithBasicStubs();
|
|
|
|
|
const {analyzedModules, analyzedInjectables} =
|
|
|
|
|
this.compiler.loadFilesSync(sourceFiles, tsFiles);
|
|
|
|
|
this._updateProgramWithTypeCheckStubs(
|
|
|
|
|
tmpProgram, analyzedModules, analyzedInjectables, rootNames);
|
2017-09-12 16:40:28 +00:00
|
|
|
} catch (e) {
|
2017-10-26 22:24:54 +00:00
|
|
|
this._createProgramOnError(e);
|
2017-09-12 16:40:28 +00:00
|
|
|
}
|
2017-10-20 16:46:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _createCompiler() {
|
|
|
|
|
const codegen: CodeGenerator = {
|
|
|
|
|
generateFile: (genFileName, baseFileName) =>
|
2020-04-07 19:43:43 +00:00
|
|
|
this._compiler.emitBasicStub(genFileName, baseFileName),
|
2017-10-20 16:46:41 +00:00
|
|
|
findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this._hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter(
|
|
|
|
|
this.rootNames, this.options, this.host, this.metadataCache, codegen,
|
|
|
|
|
this.oldProgramLibrarySummaries);
|
|
|
|
|
const aotOptions = getAotCompilerOptions(this.options);
|
2017-11-15 01:49:47 +00:00
|
|
|
const errorCollector = (this.options.collectAllErrors || this.options.fullTemplateTypeCheck) ?
|
|
|
|
|
(err: any) => this._addStructuralDiagnostics(err) :
|
|
|
|
|
undefined;
|
2017-10-20 16:46:41 +00:00
|
|
|
this._compiler = createAotCompiler(this._hostAdapter, aotOptions, errorCollector).compiler;
|
2017-09-12 16:40:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _createProgramWithBasicStubs(): {
|
|
|
|
|
tmpProgram: ts.Program,
|
2017-09-20 23:27:29 +00:00
|
|
|
rootNames: string[],
|
2017-09-22 01:05:07 +00:00
|
|
|
sourceFiles: string[],
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
tsFiles: string[],
|
2017-09-12 16:40:28 +00:00
|
|
|
} {
|
|
|
|
|
if (this._analyzedModules) {
|
2018-01-31 01:17:54 +00:00
|
|
|
throw new Error(`Internal Error: already initialized!`);
|
2017-09-12 16:40:28 +00:00
|
|
|
}
|
2017-09-19 18:43:34 +00:00
|
|
|
// Note: This is important to not produce a memory leak!
|
|
|
|
|
const oldTsProgram = this.oldTsProgram;
|
|
|
|
|
this.oldTsProgram = undefined;
|
2017-09-22 01:05:07 +00:00
|
|
|
|
|
|
|
|
const codegen: CodeGenerator = {
|
|
|
|
|
generateFile: (genFileName, baseFileName) =>
|
2020-04-07 19:43:43 +00:00
|
|
|
this.compiler.emitBasicStub(genFileName, baseFileName),
|
2017-10-20 16:46:41 +00:00
|
|
|
findGeneratedFileNames: (fileName) => this.compiler.findGeneratedFileNames(fileName),
|
2017-09-12 16:40:28 +00:00
|
|
|
};
|
2017-09-22 01:05:07 +00:00
|
|
|
|
2017-09-12 16:40:28 +00:00
|
|
|
|
2017-10-30 23:03:22 +00:00
|
|
|
let rootNames = [...this.rootNames];
|
2017-10-20 16:46:41 +00:00
|
|
|
if (this.options.generateCodeForLibraries !== false) {
|
2017-10-26 22:24:54 +00:00
|
|
|
// if we should generateCodeForLibraries, never include
|
2017-10-20 16:46:41 +00:00
|
|
|
// generated files in the program as otherwise we will
|
2018-01-31 01:17:54 +00:00
|
|
|
// overwrite them and typescript will report the error
|
2017-10-20 16:46:41 +00:00
|
|
|
// TS5055: Cannot write file ... because it would overwrite input file.
|
2017-10-30 23:03:22 +00:00
|
|
|
rootNames = rootNames.filter(fn => !GENERATED_FILES.test(fn));
|
2017-10-20 16:46:41 +00:00
|
|
|
}
|
2017-09-20 23:27:29 +00:00
|
|
|
if (this.options.noResolve) {
|
2017-10-12 23:09:49 +00:00
|
|
|
this.rootNames.forEach(rootName => {
|
2017-10-20 16:46:41 +00:00
|
|
|
if (this.hostAdapter.shouldGenerateFilesFor(rootName)) {
|
|
|
|
|
rootNames.push(...this.compiler.findGeneratedFileNames(rootName));
|
2017-09-22 01:05:07 +00:00
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
});
|
2017-09-20 23:27:29 +00:00
|
|
|
}
|
|
|
|
|
|
2017-10-20 16:46:41 +00:00
|
|
|
const tmpProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, oldTsProgram);
|
2020-11-05 00:58:29 +00:00
|
|
|
if (tempProgramHandlerForTest !== null) {
|
|
|
|
|
tempProgramHandlerForTest(tmpProgram);
|
|
|
|
|
}
|
2017-09-22 01:05:07 +00:00
|
|
|
const sourceFiles: string[] = [];
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
const tsFiles: string[] = [];
|
2017-10-12 23:09:49 +00:00
|
|
|
tmpProgram.getSourceFiles().forEach(sf => {
|
2017-10-20 16:46:41 +00:00
|
|
|
if (this.hostAdapter.isSourceFile(sf.fileName)) {
|
2017-09-22 01:05:07 +00:00
|
|
|
sourceFiles.push(sf.fileName);
|
|
|
|
|
}
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
if (TS.test(sf.fileName) && !DTS.test(sf.fileName)) {
|
|
|
|
|
tsFiles.push(sf.fileName);
|
|
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
});
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
return {tmpProgram, sourceFiles, tsFiles, rootNames};
|
2017-09-12 16:40:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _updateProgramWithTypeCheckStubs(
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules,
|
|
|
|
|
analyzedInjectables: NgAnalyzedFileWithInjectables[], rootNames: string[]) {
|
2017-10-26 22:24:54 +00:00
|
|
|
this._analyzedModules = analyzedModules;
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 18:33:48 +00:00
|
|
|
this._analyzedInjectables = analyzedInjectables;
|
2017-10-26 22:24:54 +00:00
|
|
|
tmpProgram.getSourceFiles().forEach(sf => {
|
|
|
|
|
if (sf.fileName.endsWith('.ngfactory.ts')) {
|
|
|
|
|
const {generate, baseFileName} = this.hostAdapter.shouldGenerateFile(sf.fileName);
|
|
|
|
|
if (generate) {
|
2018-01-31 01:17:54 +00:00
|
|
|
// Note: ! is ok as hostAdapter.shouldGenerateFile will always return a baseFileName
|
2017-10-26 22:24:54 +00:00
|
|
|
// for .ngfactory.ts files.
|
2020-04-07 19:43:43 +00:00
|
|
|
const genFile = this.compiler.emitTypeCheckStub(sf.fileName, baseFileName!);
|
2017-10-26 22:24:54 +00:00
|
|
|
if (genFile) {
|
|
|
|
|
this.hostAdapter.updateGeneratedFile(genFile);
|
2017-09-22 01:05:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
2017-10-26 22:24:54 +00:00
|
|
|
}
|
|
|
|
|
});
|
2017-10-20 16:46:41 +00:00
|
|
|
this._tsProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, tmpProgram);
|
2017-09-12 16:40:28 +00:00
|
|
|
// Note: the new ts program should be completely reusable by TypeScript as:
|
|
|
|
|
// - we cache all the files in the hostAdapter
|
|
|
|
|
// - new new stubs use the exactly same imports/exports as the old once (we assert that in
|
|
|
|
|
// hostAdapter.updateGeneratedFile).
|
2021-04-01 08:35:27 +00:00
|
|
|
if (tsStructureIsReused(this._tsProgram) !== StructureIsReused.Completely) {
|
2017-09-12 16:40:28 +00:00
|
|
|
throw new Error(`Internal Error: The structure of the program changed during codegen.`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-26 22:24:54 +00:00
|
|
|
private _createProgramOnError(e: any) {
|
|
|
|
|
// Still fill the analyzedModules and the tsProgram
|
|
|
|
|
// so that we don't cause other errors for users who e.g. want to emit the ngProgram.
|
|
|
|
|
this._analyzedModules = emptyModules;
|
|
|
|
|
this.oldTsProgram = undefined;
|
|
|
|
|
this._hostAdapter.isSourceFile = () => false;
|
|
|
|
|
this._tsProgram = ts.createProgram(this.rootNames, this.options, this.hostAdapter);
|
2017-06-09 21:50:57 +00:00
|
|
|
if (isSyntaxError(e)) {
|
2017-11-15 01:49:47 +00:00
|
|
|
this._addStructuralDiagnostics(e);
|
2017-10-26 22:24:54 +00:00
|
|
|
return;
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 01:49:47 +00:00
|
|
|
private _addStructuralDiagnostics(error: Error) {
|
|
|
|
|
const diagnostics = this._structuralDiagnostics || (this._structuralDiagnostics = []);
|
|
|
|
|
if (isSyntaxError(error)) {
|
2021-04-09 15:31:08 +00:00
|
|
|
diagnostics.push(...syntaxErrorToDiagnostics(error, this.tsProgram));
|
2017-11-15 01:49:47 +00:00
|
|
|
} else {
|
|
|
|
|
diagnostics.push({
|
|
|
|
|
messageText: error.toString(),
|
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
|
source: SOURCE,
|
|
|
|
|
code: DEFAULT_ERROR_CODE
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-12 23:09:49 +00:00
|
|
|
// Note: this returns a ts.Diagnostic so that we
|
|
|
|
|
// can return errors in a ts.EmitResult
|
|
|
|
|
private generateFilesForEmit(emitFlags: EmitFlags):
|
|
|
|
|
{genFiles: GeneratedFile[], genDiags: ts.Diagnostic[]} {
|
|
|
|
|
try {
|
|
|
|
|
if (!(emitFlags & EmitFlags.Codegen)) {
|
|
|
|
|
return {genFiles: [], genDiags: []};
|
|
|
|
|
}
|
2017-10-17 23:51:04 +00:00
|
|
|
// TODO(tbosch): allow generating files that are not in the rootDir
|
|
|
|
|
// See https://github.com/angular/angular/issues/19337
|
|
|
|
|
let genFiles = this.compiler.emitAllImpls(this.analyzedModules)
|
|
|
|
|
.filter(genFile => isInRootDir(genFile.genFileUrl, this.options));
|
2017-10-12 23:09:49 +00:00
|
|
|
if (this.oldProgramEmittedGeneratedFiles) {
|
|
|
|
|
const oldProgramEmittedGeneratedFiles = this.oldProgramEmittedGeneratedFiles;
|
|
|
|
|
genFiles = genFiles.filter(genFile => {
|
|
|
|
|
const oldGenFile = oldProgramEmittedGeneratedFiles.get(genFile.genFileUrl);
|
|
|
|
|
return !oldGenFile || !genFile.isEquivalent(oldGenFile);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return {genFiles, genDiags: []};
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// TODO(tbosch): check whether we can actually have syntax errors here,
|
|
|
|
|
// as we already parsed the metadata and templates before to create the type check block.
|
|
|
|
|
if (isSyntaxError(e)) {
|
|
|
|
|
const genDiags: ts.Diagnostic[] = [{
|
|
|
|
|
file: undefined,
|
|
|
|
|
start: undefined,
|
|
|
|
|
length: undefined,
|
|
|
|
|
messageText: e.message,
|
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
|
source: SOURCE,
|
|
|
|
|
code: DEFAULT_ERROR_CODE
|
|
|
|
|
}];
|
|
|
|
|
return {genFiles: [], genDiags};
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns undefined if all files should be emitted.
|
|
|
|
|
*/
|
|
|
|
|
private getSourceFilesForEmit(): ts.SourceFile[]|undefined {
|
|
|
|
|
// TODO(tbosch): if one of the files contains a `const enum`
|
|
|
|
|
// always emit all files -> return undefined!
|
2020-04-07 19:43:43 +00:00
|
|
|
let sourceFilesToEmit = this.tsProgram.getSourceFiles().filter(sf => {
|
|
|
|
|
return !sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName);
|
|
|
|
|
});
|
2017-10-12 23:09:49 +00:00
|
|
|
if (this.oldProgramEmittedSourceFiles) {
|
2018-03-20 23:11:20 +00:00
|
|
|
sourceFilesToEmit = sourceFilesToEmit.filter(sf => {
|
2020-04-07 19:43:43 +00:00
|
|
|
const oldFile = this.oldProgramEmittedSourceFiles!.get(sf.fileName);
|
2018-03-20 23:11:20 +00:00
|
|
|
return sf !== oldFile;
|
2017-10-12 23:09:49 +00:00
|
|
|
});
|
2017-10-03 16:53:58 +00:00
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
return sourceFilesToEmit;
|
2017-10-03 16:53:58 +00:00
|
|
|
}
|
|
|
|
|
|
2017-09-22 01:05:07 +00:00
|
|
|
private writeFile(
|
|
|
|
|
outFileName: string, outData: string, writeByteOrderMark: boolean,
|
2017-10-27 00:23:30 +00:00
|
|
|
onError?: (message: string) => void, genFile?: GeneratedFile,
|
|
|
|
|
sourceFiles?: ReadonlyArray<ts.SourceFile>) {
|
2017-09-22 01:05:07 +00:00
|
|
|
// collect emittedLibrarySummaries
|
|
|
|
|
let baseFile: ts.SourceFile|undefined;
|
|
|
|
|
if (genFile) {
|
2017-10-12 23:09:49 +00:00
|
|
|
baseFile = this.tsProgram.getSourceFile(genFile.srcFileUrl);
|
2017-09-22 01:05:07 +00:00
|
|
|
if (baseFile) {
|
|
|
|
|
if (!this.emittedLibrarySummaries) {
|
|
|
|
|
this.emittedLibrarySummaries = [];
|
|
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
if (genFile.genFileUrl.endsWith('.ngsummary.json') && baseFile.fileName.endsWith('.d.ts')) {
|
2017-09-22 01:05:07 +00:00
|
|
|
this.emittedLibrarySummaries.push({
|
|
|
|
|
fileName: baseFile.fileName,
|
|
|
|
|
text: baseFile.text,
|
|
|
|
|
sourceFile: baseFile,
|
|
|
|
|
});
|
2017-10-12 23:09:49 +00:00
|
|
|
this.emittedLibrarySummaries.push({fileName: genFile.genFileUrl, text: outData});
|
2017-09-28 18:44:05 +00:00
|
|
|
if (!this.options.declaration) {
|
|
|
|
|
// If we don't emit declarations, still record an empty .ngfactory.d.ts file,
|
2018-01-31 01:17:54 +00:00
|
|
|
// as we might need it later on for resolving module names from summaries.
|
2017-10-12 23:09:49 +00:00
|
|
|
const ngFactoryDts =
|
|
|
|
|
genFile.genFileUrl.substring(0, genFile.genFileUrl.length - 15) + '.ngfactory.d.ts';
|
2017-09-28 18:44:05 +00:00
|
|
|
this.emittedLibrarySummaries.push({fileName: ngFactoryDts, text: ''});
|
|
|
|
|
}
|
2017-09-22 01:05:07 +00:00
|
|
|
} else if (outFileName.endsWith('.d.ts') && baseFile.fileName.endsWith('.d.ts')) {
|
2017-10-12 23:09:49 +00:00
|
|
|
const dtsSourceFilePath = genFile.genFileUrl.replace(/\.ts$/, '.d.ts');
|
2017-09-22 01:05:07 +00:00
|
|
|
// Note: Don't use sourceFiles here as the created .d.ts has a path in the outDir,
|
|
|
|
|
// but we need one that is next to the .ts file
|
|
|
|
|
this.emittedLibrarySummaries.push({fileName: dtsSourceFilePath, text: outData});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Filter out generated files for which we didn't generate code.
|
2018-01-31 01:17:54 +00:00
|
|
|
// This can happen as the stub calculation is not completely exact.
|
2017-09-22 01:05:07 +00:00
|
|
|
// Note: sourceFile refers to the .ngfactory.ts / .ngsummary.ts file
|
2017-12-07 16:52:16 +00:00
|
|
|
// node_emitter_transform already set the file contents to be empty,
|
|
|
|
|
// so this code only needs to skip the file if !allowEmptyCodegenFiles.
|
2017-10-12 23:09:49 +00:00
|
|
|
const isGenerated = GENERATED_FILES.test(outFileName);
|
2017-12-07 16:52:16 +00:00
|
|
|
if (isGenerated && !this.options.allowEmptyCodegenFiles &&
|
|
|
|
|
(!genFile || !genFile.stmts || genFile.stmts.length === 0)) {
|
|
|
|
|
return;
|
2017-09-22 01:05:07 +00:00
|
|
|
}
|
|
|
|
|
if (baseFile) {
|
|
|
|
|
sourceFiles = sourceFiles ? [...sourceFiles, baseFile] : [baseFile];
|
|
|
|
|
}
|
2017-10-27 00:23:30 +00:00
|
|
|
// TODO: remove any when TS 2.4 support is removed.
|
|
|
|
|
this.host.writeFile(outFileName, outData, writeByteOrderMark, onError, sourceFiles as any);
|
2017-09-22 01:05:07 +00:00
|
|
|
}
|
2017-08-14 18:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
2018-03-13 03:20:32 +00:00
|
|
|
|
2017-12-22 17:36:47 +00:00
|
|
|
export function createProgram({rootNames, options, host, oldProgram}: {
|
|
|
|
|
rootNames: ReadonlyArray<string>,
|
|
|
|
|
options: CompilerOptions,
|
2020-04-07 19:43:43 +00:00
|
|
|
host: CompilerHost,
|
|
|
|
|
oldProgram?: Program
|
2017-12-22 17:36:47 +00:00
|
|
|
}): Program {
|
2019-08-20 17:52:31 +00:00
|
|
|
if (options.enableIvy !== false) {
|
2020-01-18 00:00:07 +00:00
|
|
|
return new NgtscProgram(rootNames, options, host, oldProgram as NgtscProgram | undefined);
|
2019-08-20 17:54:02 +00:00
|
|
|
} else {
|
|
|
|
|
return new AngularCompilerProgram(rootNames, options, host, oldProgram);
|
2018-04-06 16:53:10 +00:00
|
|
|
}
|
2017-10-12 23:09:49 +00:00
|
|
|
}
|
2017-06-09 21:50:57 +00:00
|
|
|
|
2017-08-02 18:20:07 +00:00
|
|
|
// Compute the AotCompiler options
|
|
|
|
|
function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
|
2017-08-16 16:00:03 +00:00
|
|
|
let missingTranslation = core.MissingTranslationStrategy.Warning;
|
2017-08-02 18:20:07 +00:00
|
|
|
|
|
|
|
|
switch (options.i18nInMissingTranslations) {
|
|
|
|
|
case 'ignore':
|
2017-08-16 16:00:03 +00:00
|
|
|
missingTranslation = core.MissingTranslationStrategy.Ignore;
|
2017-08-02 18:20:07 +00:00
|
|
|
break;
|
|
|
|
|
case 'error':
|
2017-08-16 16:00:03 +00:00
|
|
|
missingTranslation = core.MissingTranslationStrategy.Error;
|
2017-08-02 18:20:07 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let translations: string = '';
|
|
|
|
|
|
|
|
|
|
if (options.i18nInFile) {
|
2017-08-31 21:11:29 +00:00
|
|
|
if (!options.i18nInLocale) {
|
2017-08-02 18:20:07 +00:00
|
|
|
throw new Error(`The translation file (${options.i18nInFile}) locale must be provided.`);
|
|
|
|
|
}
|
|
|
|
|
translations = fs.readFileSync(options.i18nInFile, 'utf8');
|
|
|
|
|
} else {
|
|
|
|
|
// No translations are provided, ignore any errors
|
|
|
|
|
// We still go through i18n to remove i18n attributes
|
2017-08-16 16:00:03 +00:00
|
|
|
missingTranslation = core.MissingTranslationStrategy.Ignore;
|
2017-08-02 18:20:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
locale: options.i18nInLocale,
|
2018-11-16 17:57:23 +00:00
|
|
|
i18nFormat: options.i18nInFormat || options.i18nOutFormat,
|
2020-04-07 19:43:43 +00:00
|
|
|
i18nUseExternalIds: options.i18nUseExternalIds,
|
|
|
|
|
translations,
|
|
|
|
|
missingTranslation,
|
2017-09-29 21:55:44 +00:00
|
|
|
enableSummariesForJit: options.enableSummariesForJit,
|
2017-07-28 13:58:28 +00:00
|
|
|
preserveWhitespaces: options.preserveWhitespaces,
|
2017-09-11 22:18:19 +00:00
|
|
|
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
|
2017-09-22 01:05:07 +00:00
|
|
|
allowEmptyCodegenFiles: options.allowEmptyCodegenFiles,
|
2018-02-16 16:45:21 +00:00
|
|
|
enableIvy: options.enableIvy,
|
2019-02-12 22:29:28 +00:00
|
|
|
createExternalSymbolFactoryReexports: options.createExternalSymbolFactoryReexports,
|
2017-08-02 18:20:07 +00:00
|
|
|
};
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2017-12-22 17:36:47 +00:00
|
|
|
function getNgOptionDiagnostics(options: CompilerOptions): ReadonlyArray<Diagnostic> {
|
2017-06-09 21:50:57 +00:00
|
|
|
if (options.annotationsAs) {
|
|
|
|
|
switch (options.annotationsAs) {
|
|
|
|
|
case 'decorators':
|
|
|
|
|
case 'static fields':
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return [{
|
2017-08-18 21:03:59 +00:00
|
|
|
messageText:
|
2017-06-09 21:50:57 +00:00
|
|
|
'Angular compiler options "annotationsAs" only supports "static fields" and "decorators"',
|
2017-08-18 21:03:59 +00:00
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
|
source: SOURCE,
|
|
|
|
|
code: DEFAULT_ERROR_CODE
|
2017-06-09 21:50:57 +00:00
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-24 21:44:25 +00:00
|
|
|
function normalizeSeparators(path: string): string {
|
|
|
|
|
return path.replace(/\\/g, '/');
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-28 18:42:58 +00:00
|
|
|
/**
|
|
|
|
|
* Returns a function that can adjust a path from source path to out path,
|
|
|
|
|
* based on an existing mapping from source to out path.
|
|
|
|
|
*
|
|
|
|
|
* TODO(tbosch): talk to the TypeScript team to expose their logic for calculating the `rootDir`
|
|
|
|
|
* if none was specified.
|
|
|
|
|
*
|
2018-11-12 16:56:51 +00:00
|
|
|
* Note: This function works on normalized paths from typescript but should always return
|
|
|
|
|
* POSIX normalized paths for output paths.
|
2017-09-28 18:42:58 +00:00
|
|
|
*/
|
|
|
|
|
export function createSrcToOutPathMapper(
|
2020-04-07 19:43:43 +00:00
|
|
|
outDir: string|undefined, sampleSrcFileName: string|undefined,
|
|
|
|
|
sampleOutFileName: string|undefined, host: {
|
2017-10-06 22:12:32 +00:00
|
|
|
dirname: typeof path.dirname,
|
|
|
|
|
resolve: typeof path.resolve,
|
|
|
|
|
relative: typeof path.relative
|
|
|
|
|
} = path): (srcFileName: string) => string {
|
2017-09-28 18:42:58 +00:00
|
|
|
if (outDir) {
|
2017-10-24 21:44:25 +00:00
|
|
|
let path: {} = {}; // Ensure we error if we use `path` instead of `host`.
|
2017-09-28 18:42:58 +00:00
|
|
|
if (sampleSrcFileName == null || sampleOutFileName == null) {
|
|
|
|
|
throw new Error(`Can't calculate the rootDir without a sample srcFileName / outFileName. `);
|
|
|
|
|
}
|
2017-10-24 21:44:25 +00:00
|
|
|
const srcFileDir = normalizeSeparators(host.dirname(sampleSrcFileName));
|
|
|
|
|
const outFileDir = normalizeSeparators(host.dirname(sampleOutFileName));
|
2017-09-28 18:42:58 +00:00
|
|
|
if (srcFileDir === outFileDir) {
|
|
|
|
|
return (srcFileName) => srcFileName;
|
|
|
|
|
}
|
2017-10-17 23:51:04 +00:00
|
|
|
// calculate the common suffix, stopping
|
|
|
|
|
// at `outDir`.
|
2017-10-06 22:12:32 +00:00
|
|
|
const srcDirParts = srcFileDir.split('/');
|
2017-10-24 21:44:25 +00:00
|
|
|
const outDirParts = normalizeSeparators(host.relative(outDir, outFileDir)).split('/');
|
2017-09-28 18:42:58 +00:00
|
|
|
let i = 0;
|
|
|
|
|
while (i < Math.min(srcDirParts.length, outDirParts.length) &&
|
|
|
|
|
srcDirParts[srcDirParts.length - 1 - i] === outDirParts[outDirParts.length - 1 - i])
|
|
|
|
|
i++;
|
2017-10-06 22:12:32 +00:00
|
|
|
const rootDir = srcDirParts.slice(0, srcDirParts.length - i).join('/');
|
2018-11-12 16:56:51 +00:00
|
|
|
return (srcFileName) => {
|
|
|
|
|
// Note: Before we return the mapped output path, we need to normalize the path delimiters
|
|
|
|
|
// because the output path is usually passed to TypeScript which sometimes only expects
|
|
|
|
|
// posix normalized paths (e.g. if a custom compiler host is used)
|
|
|
|
|
return normalizeSeparators(host.resolve(outDir, host.relative(rootDir, srcFileName)));
|
|
|
|
|
};
|
2017-09-28 18:42:58 +00:00
|
|
|
} else {
|
2018-11-12 16:56:51 +00:00
|
|
|
// Note: Before we return the output path, we need to normalize the path delimiters because
|
|
|
|
|
// the output path is usually passed to TypeScript which only passes around posix
|
|
|
|
|
// normalized paths (e.g. if a custom compiler host is used)
|
|
|
|
|
return (srcFileName) => normalizeSeparators(srcFileName);
|
2017-06-09 21:50:57 +00:00
|
|
|
}
|
2017-07-28 13:58:28 +00:00
|
|
|
}
|
2017-09-12 22:53:17 +00:00
|
|
|
|
2017-10-12 23:09:49 +00: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;
|
2018-03-20 23:11:20 +00:00
|
|
|
emittedFiles.push(...(er.emittedFiles || []));
|
2017-10-12 23:09:49 +00:00
|
|
|
}
|
|
|
|
|
return {diagnostics, emitSkipped, emittedFiles};
|
|
|
|
|
}
|
2017-11-15 01:49:47 +00:00
|
|
|
|
|
|
|
|
function diagnosticSourceOfSpan(span: ParseSourceSpan): ts.SourceFile {
|
|
|
|
|
// For diagnostics, TypeScript only uses the fileName and text properties.
|
|
|
|
|
// The redundant '()' are here is to avoid having clang-format breaking the line incorrectly.
|
2020-04-07 19:43:43 +00:00
|
|
|
return ({fileName: span.start.file.url, text: span.start.file.content} as any);
|
2017-11-15 01:49:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function diagnosticSourceOfFileName(fileName: string, program: ts.Program): ts.SourceFile {
|
|
|
|
|
const sourceFile = program.getSourceFile(fileName);
|
|
|
|
|
if (sourceFile) return sourceFile;
|
|
|
|
|
|
|
|
|
|
// If we are reporting diagnostics for a source file that is not in the project then we need
|
|
|
|
|
// to fake a source file so the diagnostic formatting routines can emit the file name.
|
|
|
|
|
// The redundant '()' are here is to avoid having clang-format breaking the line incorrectly.
|
2020-04-07 19:43:43 +00:00
|
|
|
return ({fileName, text: ''} as any);
|
2017-11-15 01:49:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function diagnosticChainFromFormattedDiagnosticChain(chain: FormattedMessageChain):
|
|
|
|
|
DiagnosticMessageChain {
|
|
|
|
|
return {
|
|
|
|
|
messageText: chain.message,
|
2019-10-01 23:44:50 +00:00
|
|
|
next: chain.next && chain.next.map(diagnosticChainFromFormattedDiagnosticChain),
|
2017-11-15 01:49:47 +00:00
|
|
|
position: chain.position
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-09 15:31:08 +00:00
|
|
|
function syntaxErrorToDiagnostics(error: Error, program: ts.Program): Diagnostic[] {
|
2017-11-15 01:49:47 +00:00
|
|
|
const parserErrors = getParseErrors(error);
|
|
|
|
|
if (parserErrors && parserErrors.length) {
|
|
|
|
|
return parserErrors.map<Diagnostic>(e => ({
|
|
|
|
|
messageText: e.contextualMessage(),
|
|
|
|
|
file: diagnosticSourceOfSpan(e.span),
|
|
|
|
|
start: e.span.start.offset,
|
|
|
|
|
length: e.span.end.offset - e.span.start.offset,
|
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
|
source: SOURCE,
|
|
|
|
|
code: DEFAULT_ERROR_CODE
|
|
|
|
|
}));
|
2017-12-06 23:27:23 +00:00
|
|
|
} else if (isFormattedError(error)) {
|
|
|
|
|
return [{
|
|
|
|
|
messageText: error.message,
|
|
|
|
|
chain: error.chain && diagnosticChainFromFormattedDiagnosticChain(error.chain),
|
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
|
source: SOURCE,
|
|
|
|
|
code: DEFAULT_ERROR_CODE,
|
|
|
|
|
position: error.position
|
|
|
|
|
}];
|
|
|
|
|
}
|
2021-04-09 15:31:08 +00:00
|
|
|
|
|
|
|
|
const ngModuleErrorData = getMissingNgModuleMetadataErrorData(error);
|
|
|
|
|
if (ngModuleErrorData !== null) {
|
|
|
|
|
// This error represents the import or export of an `NgModule` that didn't have valid metadata.
|
|
|
|
|
// This _might_ happen because the NgModule in question is an Ivy-compiled library, and we want
|
|
|
|
|
// to show a more useful error if that's the case.
|
|
|
|
|
const ngModuleClass =
|
|
|
|
|
getDtsClass(program, ngModuleErrorData.fileName, ngModuleErrorData.className);
|
|
|
|
|
if (ngModuleClass !== null && isIvyNgModule(ngModuleClass)) {
|
|
|
|
|
return [{
|
|
|
|
|
messageText: `The NgModule '${ngModuleErrorData.className}' in '${
|
|
|
|
|
ngModuleErrorData
|
|
|
|
|
.fileName}' is imported by this compilation, but appears to be part of a library compiled for Angular Ivy. This may occur because:
|
|
|
|
|
|
|
|
|
|
1) the library was processed with 'ngcc'. Removing and reinstalling node_modules may fix this problem.
|
|
|
|
|
|
|
|
|
|
2) the library was published for Angular Ivy and v12+ applications only. Check its peer dependencies carefully and ensure that you're using a compatible version of Angular.
|
|
|
|
|
|
|
|
|
|
See https://angular.io/errors/NG6999 for more information.
|
|
|
|
|
`,
|
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
|
code: DEFAULT_ERROR_CODE,
|
|
|
|
|
source: SOURCE,
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-06 23:27:23 +00:00
|
|
|
// Produce a Diagnostic anyway since we know for sure `error` is a SyntaxError
|
|
|
|
|
return [{
|
|
|
|
|
messageText: error.message,
|
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
|
code: DEFAULT_ERROR_CODE,
|
|
|
|
|
source: SOURCE,
|
|
|
|
|
}];
|
|
|
|
|
}
|
2021-04-09 15:31:08 +00:00
|
|
|
|
|
|
|
|
function getDtsClass(program: ts.Program, fileName: string, className: string): ts.ClassDeclaration|
|
|
|
|
|
null {
|
|
|
|
|
const sf = program.getSourceFile(fileName);
|
|
|
|
|
if (sf === undefined || !sf.isDeclarationFile) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
for (const stmt of sf.statements) {
|
|
|
|
|
if (!ts.isClassDeclaration(stmt)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (stmt.name === undefined || stmt.name.text !== className) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return stmt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No classes found that matched the given name.
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isIvyNgModule(clazz: ts.ClassDeclaration): boolean {
|
|
|
|
|
for (const member of clazz.members) {
|
|
|
|
|
if (!ts.isPropertyDeclaration(member)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (ts.isIdentifier(member.name) && member.name.text === 'ɵmod') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No Ivy 'ɵmod' property found.
|
|
|
|
|
return false;
|
2021-06-04 14:57:07 +00:00
|
|
|
}
|