forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(compiler-cli): downlevel angular decorators to static properties
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: microsoft/TypeScript#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: angular#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 angular#30106. Fixes angular#30586. Fixes angular#30141. Resolves FW-2196. Resolves FW-2199.
- Loading branch information
1 parent
3569fdf
commit 82ca78b
Showing
12 changed files
with
1,413 additions
and
420 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.