-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Design:type metadata for cyclic dependencies throw at runtime #27519
Comments
A possible solution I see is to modify the code setting design-type metadata to use a resolver/getter function instead of passing the type constructor directly, and access design-type metadata a bit later, after all classes were defined. I guess such requirement makes my proposed solution rather impractical. function property() {
return (target, key) => {
// Typically, users won't call process.nextTick but change the decorator to only store
// design-type getter function. The actual type class will be read by other code at time
// that may be still in the same tick, but later enough to have all classes already defined
process.nextTick(() => {
const t = Reflect.getMetadata('design:type', target, key) || (() => {});
console.log('property %s %s of type', target.constructor.name, key, t().name);
});
};
}
class Product {
}
__decorate([
property(),
__metadata("design:type", () => Category)
], Product.prototype, "category", void 0);
class Category {
}
__decorate([
property(),
__metadata("design:type", () => Array)
], Category.prototype, "products", void 0); |
@RyanCavanaugh Thank you for taking look at this issue. The description of Design Limitation label says: Constraints of the existing architecture prevent this from being fixed. I understand it may not be possible to make my code snippet shown above to work, but cannot we at least improve the compiler to detect the problem at compile time please? Is such improvement prevented by limitations of the existing architecture too? |
TypeScript compile could produce code that used forwardRef, similar how Angular suggest: https://angular.io/api/core/forwardRef __metadata("design:type", __forwardRef(() => Category)) It also applies to design:paramtypes. |
I was looking at this again today and @alxhub brought up an interesting point: TS will emit invalid ES2015 code for valid TS regardless of circular dependencies. Consider this code:
Using TS 3.4.5 and NodeJS v10.10.0, you can compile and run it via The TS compilation will succeed but execution will fail:
Yet there would be a compilation error if Perhaps there should be a compilation error for Types being used before being declared, at least if |
I think this issue has been discussed and is indeed a design limitation - pinging @rbuckton for context. |
Personally, I can live with with the limitation, i.e. that TypeScript cannot emit design-type metadata when the property is using a type before it's declared. However, I'd like the problem to be detected and reported by the TypeScript compiler, the compiler should fail the build with a descriptive error message that will make it easy for the developer to understand what they did wrong and how to fix the problem. Compare it with the current situation, where compiler happily produces JavaScript code and the problem is discovered only at runtime, usually with a hard crash on uncaught ReferenceError. I find such behavior rather impractical - what is the compiler good for when it cannot detect incorrect code? The problem can be also difficult to diagnose and troubleshoot, especially for people not aware of this limitation. The message "ReferenceError: Lock is not defined" with a stack trace pointing somewhere deep into code added by the compiler provide very little hints on how to fix the problem. |
Maybe you could add a warning that broken code was produced and remove it? After all, it is a metadata issue and the code itself is working. I came here from Angular, where metadata are only kept in dev mode, in production it works fine, but when I try to fire it up locally with JIT compiler — I get an error. |
@DanielRosenwasser do you think there is a way to move forward to introduce a forwardRef based implementation? Based on the above info it doesn't look like it would suffer from the issue. Of course it still wont work for constructor-based DI, but for lazy property based injection or for describing other relationships between classes it would work. IMO this one might be worth the extra effort as its causing rippling effects in the new emergent TS ecosystem (angular, inversify/nestjs etc). (In Inversify for instance, a type-unsafe token based alternative is recommended instead of forwardRef) |
I'll try to keep it in mind, but I really don't think I can reliably give an answer in the near future until I can focus on decorators again. Recently my standards focus has been on optional chaining, nullish coalescing, and class fields. |
@spion Any change we might make would be a breaking change. We considered switching to a new metadata format some time ago, as there is no way to handle forward references in a non-breaking way with the current emit. Something like this: class Foo {
// @__metadata("design:paramtypes", [Number, String])
// @__metadata("design:returntype", Bar) // error
@__metadata("design:typeinfo", {
paramTypes: () => [Number, String], // deferred via arrow, no error
returnType: () => Bar, // deferred via arrow, no error
})
method(x, y) {
return new Bar(x, y);
}
}
class Bar { ... } We held off on any significant changes to this behavior as there are other issues limiting its utility:
With the decorators proposal still in flux, we have not prioritized any changes to decorator emit (including metadata) until the proposal progresses further along the standards track. |
Got it - I guess the new decorator proposal seems like a good time to revisit. FWIW when we want run-time interface representation we currently use "classterfaces" i.e. interfaces defined as a class class IMyInterface {
prop!: number;
method!: (arg: number) => number
} They seem to work just fine as a substitute for interfaces: class AddFive implements IMyInterface {
prop = 5
method(arg: number) { return this.prop + arg; }
} and its easy to decorate them with plenty of additional info and/or use them as type-safe DI tokens. |
Makes sense 👍 In which case: Can we please improve the compiler to detect the situation when it's going to emit invalid code that will later crash at runtime? Would that be considering a breaking change? Since the emitted code is going to crash at runtime anyways, if the compiler decide to fail the build instead of emitting a code that will crash, then I would expect that such change should not break any valid/working applications out there. Thoughts? |
@bajtos the problem is that any decorator causes an emit of metadata, but not all of them read it. This will cause non-related decorators to also crash. |
In our project we are using quite extensively the reflection API - it is core component of the serialization/deserialization in out codebase. We are currently experimenting with the folowing solution/workaround: // ########## in Serialized.ts
const createProxyTypeConstructor = (getType: () => GenericConstructor<unknown>) => {
const proxyTypeConstructor = class ProxyTypeConstructor {
public constructor(...args: any) {
return new (getType())(...args);
}
};
Promise.resolve().then(() => {
Object.setPrototypeOf(proxyTypeConstructor, getType());
});
return proxyTypeConstructor;
};
export class Serialized<SerializedType> {
public static readonly metadata = {
type: (callback: () => GenericConstructor<unknown>) => (target: any, propertyKey: string) => {
Reflect.metadata('design:type', createProxyTypeConstructor(callback))(target, propertyKey);
}
};
// serialization implementation and static utility members irrelevant to the reflection API
} // ######## in topology.ts
export class Zone extends SerializedWithDirtyTracking<Breach.API.Topology.Zone> {
// ...irrelevant members
@Serialized.metadata.type(() => Topology)
@observable
public parent: InstanceType<typeof Topology>;
// ...irrelevant members
}
// ...snip...
export class Topology extends Entity {
@observable public zones = observable<{ [id: string]: Zone }>({});
} This approach is mostly backward compatible:
This haven't been still tested thoroughly. I am open to any criticism/corrections/suggestions I can open a PR to implement this behavior in tsc, but this will have to go as an alternative method of metadata decoration as it is not 100% compatible with the current implementation. |
In angular#37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and components packages (@angular/*). This change however revealed a bug in the ngc that caused __decorate and __metadata calls to still be emitted in the JS code even though we don't depend on them. Additionally it was these calls that caused code in @angular/material packages to fail at runtime due to circular dependency in the emitted decorator code documeted as microsoft/TypeScript#27519. This change partially rolls back angular#37221 by reenabling the decorator to static fields (static properties) downleveling. This is just a temporary workaround while we are also fixing root cause in `ngc` - tracked as FW-2199. Resolves FW-2198. Related to FW-2196
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output and SSR: (1): TypeScript has a limitation where `forwardRef` breaks in es2015 due to `forwardRef` calls being invoked immediately as part of decorator metadata. See: angular/angular-cli#14424 and angular#30106. (2): TypeScript preserves type information for class members, and references the type values immediately (as with `forwardRef` above). This means that using native DOM globals as property types could break SSR. This is because loading such JS file requires these DOM globals to exist in NodeJS globally too (and there is no support for DI mocking). See: angular#30586. This is especially relevant for libraries that do not want to use tsickle but ship SSR-compatible code. The root cause for (1) is a TypeScript limitation as mentioned. This is the related upstream ticket: microsoft/TypeScript#27519. Downleveling decorators to static properties fixes the issues, as outlined in (1) and (2), because we can defer metadata evaluation to avoid direct evaluation on file load. Additionally, we have more control and can discard unnnecessary metadata information, like class member types that are not needed by Angular at all see (2). One might wonder why this hasn't been an issue in the past since we disabled this as part of version 7. These issues didn't surface at a large scale because we added a custom transformer to CLI projects and to `ng-packagr`. Those transformers downleveled constructor parameters to fix (1) and (2). Also `emitMetadataDecorator` has been disabled by default in CLI projects. For bazel release output this didn't surface either because tsickle still ran by default in prodmode output. This was never an ideal solution though, and we'd also like to not run tsickle by default in the Bazel prodmode output. It was not ideal because we just applied workarounds at Angular compiler derivatives. Ideally, TypeScript would just emit proper metadata that isn't evaluated at top-level, but given they marked it as limitation and the decorator proposal is still stage 2, this won't happen any time soon (if at all). The ideal solution is that we downlevel decorators (as previously done with tsickle by default) as part of the Angular compiler (a level higher; and one below the actual TypeScript compiler limitation). This fixes the issues with the common `forwardRef` pattern (1), and also fixes (2). It also allows us to reduce workarounds in the compiler derivatives (i.e. CLI and ng-packagr), and we can disable tsickle in Angular bazel (as already done with this commit). Fixes angular#30106. Fixes angular#30586. Fixes angular#30141. Resolves FW-2196. Resolves FW-2199.
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output and SSR: (1): TypeScript has a limitation where `forwardRef` breaks in es2015 due to `forwardRef` calls being invoked immediately as part of decorator metadata. See: angular/angular-cli#14424 and angular#30106. (2): TypeScript preserves type information for class members, and references the type values immediately (as with `forwardRef` above). This means that using native DOM globals as property types could break SSR. This is because loading such JS file requires these DOM globals to exist in NodeJS globally too (and there is no support for DI mocking). See: angular#30586. This is especially relevant for libraries that do not want to use tsickle but ship SSR-compatible code. The root cause for (1) is a TypeScript limitation as mentioned. This is the related upstream ticket: microsoft/TypeScript#27519. Downleveling decorators to static properties fixes the issues, as outlined in (1) and (2), because we can defer metadata evaluation to avoid direct evaluation on file load. Additionally, we have more control and can discard unnnecessary metadata information, like class member types that are not needed by Angular at all see (2). One might wonder why this hasn't been an issue in the past since we disabled this as part of version 7. These issues didn't surface at a large scale because we added a custom transformer to CLI projects and to `ng-packagr`. Those transformers downleveled constructor parameters to fix (1) and (2). Also `emitMetadataDecorator` has been disabled by default in CLI projects. For bazel release output this didn't surface either because tsickle still ran by default in prodmode output. This was never an ideal solution though, and we'd also like to not run tsickle by default in the Bazel prodmode output. It was not ideal because we just applied workarounds at Angular compiler derivatives. Ideally, TypeScript would just emit proper metadata that isn't evaluated at top-level, but given they marked it as limitation and the decorator proposal is still stage 2, this won't happen any time soon (if at all). The ideal solution is that we downlevel decorators (as previously done with tsickle by default) as part of the Angular compiler (a level higher; and one below the actual TypeScript compiler limitation). This fixes the issues with the common `forwardRef` pattern (1), and also fixes (2). It also allows us to reduce workarounds in the compiler derivatives (i.e. CLI and ng-packagr), and we can disable tsickle in Angular bazel (as already done with this commit). Fixes angular#30106. Fixes angular#30586. Fixes angular#30141. Resolves FW-2196. Resolves FW-2199.
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output and SSR: (1): TypeScript has a limitation where `forwardRef` breaks in es2015 due to `forwardRef` calls being invoked immediately as part of decorator metadata. See: angular/angular-cli#14424 and angular#30106. (2): TypeScript preserves type information for class members, and references the type values immediately (as with `forwardRef` above). This means that using native DOM globals as property types could break SSR. This is because loading such JS file requires these DOM globals to exist in NodeJS globally too (and there is no support for DI mocking). See: angular#30586. This is especially relevant for libraries that do not want to use tsickle but ship SSR-compatible code. The root cause for (1) is a TypeScript limitation as mentioned. This is the related upstream ticket: microsoft/TypeScript#27519. Downleveling decorators to static properties fixes the issues, as outlined in (1) and (2), because we can defer metadata evaluation to avoid direct evaluation on file load. Additionally, we have more control and can discard unnnecessary metadata information, like class member types that are not needed by Angular at all see (2). One might wonder why this hasn't been an issue in the past since we disabled this as part of version 7. These issues didn't surface at a large scale because we added a custom transformer to CLI projects and to `ng-packagr`. Those transformers downleveled constructor parameters/ore removed decorators at all to fix (1) and (2). Also `emitMetadataDecorator` has been disabled by default in CLI projects. For bazel release output this didn't surface either because tsickle still ran by default in prodmode output. This was never an ideal solution though, and we'd also like to not run tsickle by default in the Bazel prodmode output. It was not ideal because we just applied workarounds at Angular compiler derivatives. Ideally, TypeScript would just emit proper metadata that isn't evaluated at top-level, but given they marked it as limitation and the decorator proposal is still stage 2, this won't happen any time soon (if at all). The ideal solution is that we downlevel decorators (as previously done with tsickle by default) as part of the Angular compiler (a level higher; and one below the actual TypeScript compiler limitation). This fixes the issues with the common `forwardRef` pattern (1), and also fixes (2). It also allows us to reduce code duplication in the compiler derivatives (e.g. ng-packagr), fixes the left-behind standalone ngc comsumers, and we can disable tsickle in Angular bazel (as already done with this commit). Fixes angular#30106. Fixes angular#30586. Fixes angular#30141. Resolves FW-2196. Resolves FW-2199.
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output and SSR: (1): TypeScript has a limitation where `forwardRef` breaks in es2015 due to `forwardRef` calls being invoked immediately as part of decorator metadata. See: angular/angular-cli#14424 and angular#30106. (2): TypeScript preserves type information for class members, and references the type values immediately (as with `forwardRef` above). This means that using native DOM globals as property types could break SSR. This is because loading such JS file requires these DOM globals to exist in NodeJS globally too (and there is no support for DI mocking). See: angular#30586. This is especially relevant for libraries that do not want to use tsickle but ship SSR-compatible code. The root cause for (1) is a TypeScript limitation as mentioned. This is the related upstream ticket: microsoft/TypeScript#27519. Downleveling decorators to static properties fixes the issues, as outlined in (1) and (2), because we can defer metadata evaluation to avoid direct evaluation on file load. Additionally, we have more control and can discard unnnecessary metadata information, like class member types that are not needed by Angular at all see (2). One might wonder why this hasn't been an issue in the past since we disabled this as part of version 7. These issues didn't surface at a large scale because we added a custom transformer to CLI projects and to `ng-packagr`. Those transformers downleveled constructor parameters/ore removed decorators at all to fix (1) and (2). Also `emitMetadataDecorator` has been disabled by default in CLI projects. For bazel release output this didn't surface either because tsickle still ran by default in prodmode output. This was never an ideal solution though, and we'd also like to not run tsickle by default in the Bazel prodmode output. It was not ideal because we just applied workarounds at Angular compiler derivatives. Ideally, TypeScript would just emit proper metadata that isn't evaluated at top-level, but given they marked it as limitation and the decorator proposal is still stage 2, this won't happen any time soon (if at all). The ideal solution is that we downlevel decorators (as previously done with tsickle by default) as part of the Angular compiler (a level higher; and one below the actual TypeScript compiler limitation). This fixes the issues with the common `forwardRef` pattern (1), and also fixes (2). It also allows us to reduce code duplication in the compiler derivatives (e.g. ng-packagr), fixes the left-behind standalone ngc comsumers, and we can disable tsickle in Angular bazel (as already done with this commit). Fixes angular#30106. Fixes angular#30586. Fixes angular#30141. Resolves FW-2196. Resolves FW-2199.
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output and SSR: (1): TypeScript has a limitation where `forwardRef` breaks in es2015 due to `forwardRef` calls being invoked immediately as part of decorator metadata. See: angular/angular-cli#14424 and angular#30106. (2): TypeScript preserves type information for class members, and references the type values immediately (as with `forwardRef` above). This means that using native DOM globals as property types could break SSR. This is because loading such JS file requires these DOM globals to exist in NodeJS globally too (and there is no support for DI mocking). See: angular#30586. This is especially relevant for libraries that do not want to use tsickle but ship SSR-compatible code. The root cause for (1) is a TypeScript limitation as mentioned. This is the related upstream ticket: microsoft/TypeScript#27519. Downleveling decorators to static properties fixes the issues, as outlined in (1) and (2), because we can defer metadata evaluation to avoid direct evaluation on file load. Additionally, we have more control and can discard unnnecessary metadata information, like class member types that are not needed by Angular at all see (2). One might wonder why this hasn't been an issue in the past since we disabled this as part of version 7. These issues didn't surface at a large scale because we added a custom transformer to CLI projects and to `ng-packagr`. Those transformers downleveled constructor parameters/ore removed decorators at all to fix (1) and (2). Also `emitMetadataDecorator` has been disabled by default in CLI projects. For bazel release output this didn't surface either because tsickle still ran by default in prodmode output. This was never an ideal solution though, and we'd also like to not run tsickle by default in the Bazel prodmode output. It was not ideal because we just applied workarounds at Angular compiler derivatives. Ideally, TypeScript would just emit proper metadata that isn't evaluated at top-level, but given they marked it as limitation and the decorator proposal is still stage 2, this won't happen any time soon (if at all). The ideal solution is that we downlevel decorators (as previously done with tsickle by default) as part of the Angular compiler (a level higher; and one below the actual TypeScript compiler limitation). This fixes the issues with the common `forwardRef` pattern (1), and also fixes (2). It also allows us to reduce code duplication in the compiler derivatives (e.g. ng-packagr), fixes the left-behind standalone ngc comsumers, and we can disable tsickle in Angular bazel (as already done with this commit). Fixes angular#30106. Fixes angular#30586. Fixes angular#30141. Resolves FW-2196. Resolves FW-2199.
In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output and SSR: (1): TypeScript has a limitation where `forwardRef` breaks in es2015 due to `forwardRef` calls being invoked immediately as part of decorator metadata. See: angular/angular-cli#14424 and angular#30106. (2): TypeScript preserves type information for class members, and references the type values immediately (as with `forwardRef` above). This means that using native DOM globals as property types could break SSR. This is because loading such JS file requires these DOM globals to exist in NodeJS globally too (and there is no support for DI mocking). See: angular#30586. This is especially relevant for libraries that do not want to use tsickle but ship SSR-compatible code. The root cause for (1) is a TypeScript limitation as mentioned. This is the related upstream ticket: microsoft/TypeScript#27519. Downleveling decorators to static properties fixes the issues, as outlined in (1) and (2), because we can defer metadata evaluation to avoid direct evaluation on file load. Additionally, we have more control and can discard unnnecessary metadata information, like class member types that are not needed by Angular at all see (2). One might wonder why this hasn't been an issue in the past since we disabled this as part of version 7. These issues didn't surface at a large scale because we added a custom transformer to CLI projects and to `ng-packagr`. Those transformers downleveled constructor parameters/ore removed decorators at all to fix (1) and (2). Also `emitMetadataDecorator` has been disabled by default in CLI projects. For bazel release output this didn't surface either because tsickle still ran by default in prodmode output. This was never an ideal solution though, and we'd also like to not run tsickle by default in the Bazel prodmode output. It was not ideal because we just applied workarounds at Angular compiler derivatives. Ideally, TypeScript would just emit proper metadata that isn't evaluated at top-level, but given they marked it as limitation and the decorator proposal is still stage 2, this won't happen any time soon (if at all). The ideal solution is that we downlevel decorators (as previously done with tsickle by default) as part of the Angular compiler (a level higher; and one below the actual TypeScript compiler limitation). This fixes the issues with the common `forwardRef` pattern (1), and also fixes (2). It also allows us to reduce code duplication in the compiler derivatives (e.g. ng-packagr), fixes the left-behind standalone ngc comsumers, and we can disable tsickle in Angular bazel (as already done with this commit). Fixes angular#30106. Fixes angular#30586. Fixes angular#30141. Resolves FW-2196. Resolves FW-2199.
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.
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.
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.
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.
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.
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.
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.
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.
…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: 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: #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
…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: 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: #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
…ngular#37317) In angular#37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and components packages (@angular/*). This change however revealed a bug in the ngc that caused __decorate and __metadata calls to still be emitted in the JS code even though we don't depend on them. Additionally it was these calls that caused code in @angular/material packages to fail at runtime due to circular dependency in the emitted decorator code documeted as microsoft/TypeScript#27519. This change partially rolls back angular#37221 by reenabling the decorator to static fields (static properties) downleveling. This is just a temporary workaround while we are also fixing root cause in `ngc` - tracked as FW-2199. Resolves FW-2198. Related to FW-2196 PR Close angular#37317
…ngular#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: 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. PR Close angular#37382
…ngular#37317) In angular#37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and components packages (@angular/*). This change however revealed a bug in the ngc that caused __decorate and __metadata calls to still be emitted in the JS code even though we don't depend on them. Additionally it was these calls that caused code in @angular/material packages to fail at runtime due to circular dependency in the emitted decorator code documeted as microsoft/TypeScript#27519. This change partially rolls back angular#37221 by reenabling the decorator to static fields (static properties) downleveling. This is just a temporary workaround while we are also fixing root cause in `ngc` - tracked as FW-2199. Resolves FW-2198. Related to FW-2196 PR Close angular#37317
…ngular#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: 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. PR Close angular#37382
@rbuckton Any update on this? I don't really understand the reason why typescript emit "Type" instead of "() => Type" since the latter resolves some cyclic dependencies problems. To me it sounds like a tiny change in an experimental feature anyway, and this tiny change would improve a lot of TS projects, and of course typescript itself. Debugging circular import problem, when one is not aware of theses subtleties, is very hard. Note: I spent some time yesterday making a minimal reproducible code and describing the issue in clear term here: #41201. |
Decorators are an experimental feature in TypeScript, which is known to have issues. Changes in Parcel means that it can no longer build the app, so their use is blocking updating Parcel to a stable release. This change removes the ORM decorators in favour of defining entities programmatically. I'm hoping that the tests and migration-creating script are enough to have caught any mistakes, but given that areas of the app remain untested, it's hard to be sure. Also, this change has to disable Parcel's scope hoisting, as it renames the entity classes. The rename wasn't a problem when using decorators as the generated code saw the class name left unchanged. Now, however, the ORM breaks if they are changed. Refs #399, parcel-bundler/parcel#7293 (comment), microsoft/TypeScript#27519
TypeScript Version: 3.2.0-dev.20181003
Search Terms: design cyclic
Code
index.ts
tsconfig.json
package.json
Expected behavior:
Ideally, the code compiles and
node index.js
produces the following console output:If this is not possible, then the compiler should detect cyclic dependencies and fail the compilation with a helpful error.
Actual behavior:
The code compiles. When the compiled code is executed, it fails at runtime.
Here is the relevant snippet from the transpiled output:
Playground Link:
Playground does not support☹️
emitDecoratorMetadata
andexperimentalDecorators
optionsRelated Issues:
None found.
The text was updated successfully, but these errors were encountered: