[Ivy] Incompatibility of Esm2015ReflectionHost with libraries compiled with NGCC
See original GitHub issueš bug report
Affected Package
The issue is caused by @angular/compiler-cli
Is this a regression?
Sort of. It is a regression if you happen to upgrade from a < 3.7 to a >= 3.7 version of TypeScript
It did work with Angular 6 and it also worked with previous Angular 9 rcās (Iām pretty sure that rc.7 still worked).
Description
This was very tricky to track down. The problem expressed in the form that the compiler complains when running ngcc on some of the project libraries that āselector/template has to be a stringā. This happened for cases where we use a constant from another package as the selector or template for a @Component annotation.
A lot of debugging of the build process and some guesswork resulted in the following findings:
- In essence, the Esm2015ReflectionHost is not capable of resolving expressions of the form
MyClass.MyPublicStaticStringProperty
⦠in some situations. - In the cases where it fails, the
reflectMember
check forisPropertyAccess
simply does not work anymore because the syntax kind of the node is something completely unexpected. - In
getMembersOfClass
method, the nodes ofsymbol.implementation.exports
for public static properties are of syntax kindPropertyDeclaration
when ānormallyā (when things work), they are of kindPropertyAccessExpression
- In the end, it all breaks down in the
StaticInterpreter.accessHelper
method because it ends up returning aReference
instead of the resolved string constant. This then triggers various extremely unhelpful downstream errors.
š¬ Minimal Reproduction
I donāt have a minimal reproduction right now since Iām not sure how I can ābuildā this situation from scratch, but as soon as I find one, I will add it here. See my next issue comment for reproduction.
An āabstractionā of the code I am working with contains a component in a library in the āpackagesā directory
@Component({
selector: SharedConstants.SecondSelector,
template: `<div>...</div>`,
})
export class SharedDerivedComponent extends BaseComponent {
}
This is referencing a constant from another library in the āpackagesā directory
export declare class SharedConstants {
static readonly SecondSelector = "lib-shared-derived";
}
š„ Exception or Error
Some of the messages that result from this are:
NG1010: selector must be a string
NG1010: template must be a string
NG1006: Two incompatible decorators on class
š Your Environment
Angular Version:
Angular CLI: 9.0.0-rc.11
Node: 10.16.0
OS: win32 x64
Angular: 9.0.0-rc.11
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, localize, platform-browser
... platform-browser-dynamic, router
Ivy Workspace: Yes
Package Version
------------------------------------------------------------
@angular-devkit/architect 0.900.0-rc.11
@angular-devkit/build-angular 0.900.0-rc.11
@angular-devkit/build-ng-packagr 0.900.0-rc.11
@angular-devkit/build-optimizer 0.900.0-rc.11
@angular-devkit/build-webpack 0.900.0-rc.11
@angular-devkit/core 9.0.0-rc.11
@angular-devkit/schematics 9.0.0-rc.11
@ngtools/webpack 9.0.0-rc.11
@schematics/angular 9.0.0-rc.11
@schematics/update 0.900.0-rc.11
ng-packagr 9.0.0-rc.7
rxjs 6.5.4
typescript 3.7.5
webpack 4.8.3
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:10 (5 by maintainers)
@JoostK thanks a lot for the very detailed explanation and action items. With all the backwards compatibility going on, it is sometimes hard to tell what the actual design principles behind Ivy are, but this cleared things up big time for me.
It seems from your description that one of the goals of Ivy is to get away from the parsing of source files and instead mostly rely on typing information. This is something I can totally get on board with and it also gives me a concrete idea of what to look out for to avoid issues in the future.
I will also check the
ModuleWithProviders
to make sure my code base is compatible with the latest patterns in providers.I guess itās time to look into the contribution process for Angular in more detail š
@elca-lan Please find my comments inline:
The
forRoot()
idiom continues to work due to a change in theModuleWithProviders
type that is returned by such functions, where a generic type parameter has been added that represents the type of theNgModule
that is imported. Since this type ends up in the typings, it is no longer āinternalā.In your case, the TypeScript compiler did include the value assignment in the typings as itās assigned into a readonly property. That makes the value available to inspect without looking into the actual source code, which is something that can therefore be supported (and ngtsc in fact does, as you found out). From that perspective, it is indeed a bug that ngcc does not support this use case.
Absolutely, agreed!
I am not certain about how TypeScript would represent a const in the typings, but I suspect it would be widened to
string
instead of the literal typelib-business
. Therefore, the actual value would have been lost in the typings. I think this can be forced usingexport const VarBusinessSelector = 'lib-business' as const;
as that would keep the literal type.Your example withActually, thestatic readonly
is apparently kept as literal type without theas const
cast, Iād have to check how aconst
variable is transformed into the typings file.static readonly
is present in the .d.ts file using the original assignment, not just the type declaration.This will no longer work, as the
"lib-business"
value will not be available in the typings. In ViewEngine, this did work as the value side of a library was captured in themetadata.json
file format when compiling the library. Such metadata is no longer captured,Agreed, the compiler should be able to provide useful and actionable diagnostics for situations that are not supported.
In essence, values that are only available inside the source code but not the typings are āinternalā and not available to the ngtsc compiler. My usage of the term locality may not be very accurate, as it has various associations (where my usage does not correspond with how locality is typically referred to)
I think thereās three action items here:
TypeScriptReflectionHost
host.