question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[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 for isPropertyAccess simply does not work anymore because the syntax kind of the node is something completely unexpected.
  • In getMembersOfClass method, the nodes of symbol.implementation.exports for public static properties are of syntax kind PropertyDeclaration when ā€œnormallyā€ (when things work), they are of kind PropertyAccessExpression
  • In the end, it all breaks down in the StaticInterpreter.accessHelper method because it ends up returning a Reference 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:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:10 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
elca-lancommented, Feb 3, 2020

@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 😃

1reaction
JoostKcommented, Feb 3, 2020

@elca-lan Please find my comments inline:

If that statement from the documentation were strictly true, then things like the forRoot(...) providers like it is used for the RouterModule wouldn’t work, right? forRoot is a function from a ā€œforeignā€ package which is used in the decorator metadata of any Angular application that uses routing…

The forRoot() idiom continues to work due to a change in the ModuleWithProviders type that is returned by such functions, where a generic type parameter has been added that represents the type of the NgModule that is imported. Since this type ends up in the typings, it is no longer ā€œinternalā€.

Also, if that statement were true, that would be extremely limiting and make the whole Angular framework a lot less powerful.

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.

From a practical point of view, if that was not supported, then the compiler should raise an error

  • preferrably with a reasonable error message - if it encounters anything like that and not just the NGCC.

Absolutely, agreed!

It’s not the Ivy compiler that fails with this code. It is the NGCC backwards compatibility compiler. If I switch the libraries to also enableIvy in production builds, then it works.

Other things that work with the libraries back to non-ivy builds are:

  • Referencing an export const VarBusinessSelector = 'lib-business'; defined in the sahred library

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 type lib-business. Therefore, the actual value would have been lost in the typings. I think this can be forced using export const VarBusinessSelector = 'lib-business' as const; as that would keep the literal type.

Your example with static readonly is apparently kept as literal type without the as const cast, I’d have to check how a const variable is transformed into the typings file. Actually, the static readonly is present in the .d.ts file using the original assignment, not just the type declaration.

  • Referencing a function export function FuncBusinessSelector(): string { return 'lib-business'; } defined in the shared library

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 the metadata.json file format when compiling the library. Such metadata is no longer captured,

It would be quite sneaky to declare those features that clearly work as ā€œnot supportedā€ as people generally don’t consult the documentation to check if something is ā€œlegalā€ if it works and makes sense.

Agreed, the compiler should be able to provide useful and actionable diagnostics for situations that are not supported.

As you correctly pointed out, the class containing the constants is of course part of the public API of that library and I don’t see how that would violate locality.

In essence, I think that the statement from the upgrade instructions is wildly inaccurate or at the very least misleading. It would be great if you could provide some clarification with regards to what this actually means since I don’t think that RouterModule.forRoot(ROUTES) is going to change. I would also like to know what definition of ā€œlocalityā€ you are referring to, since it does not seem to match any that I’m aware of.

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:

  1. Fix ngcc to detect .d.ts files and fall back into the TypeScriptReflectionHost host.
  2. Document the exact patterns that are, and that are not supported. The Ivy compatibility guide may need some clarification in this area as well.
  3. Work towards better error messages in the compiler, with actionable suggestions. With Ivy, one of the goals is much improved error messages and this is one area where improvements are to be made.
Read more comments on GitHub >

github_iconTop Results From Across the Web

Angular 10 Libraries and IVY/ngcc Compatibility
The recommendation for Angular 10 still seems to be that such libraries should be compiled with IVY disabled but that the Angular CLI...
Read more >
The Angular linker (goodbye ngcc!) - Ninja Squad
This is ngcc compiling Angular packages and our library into an Ivy compatible format. If you open node_modules/tiny-lib , you'll spot a new...
Read more >
Angular Libs with Ivy: a structured guide to migrating older ...
In this talk, Google Developer Expert (GDE) Martina Kraus presents the exact steps for creating a new Angular Library with Ivy.
Read more >
Angular View Engine was removed - What you need to know
Ivy apps require ngcc to make it work though. Changing to ā€œPartial Ivyā€ makes your library incompatible with View Engine apps.
Read more >
Package Diff: @angular/compiler-cli @ 7.1.4 .. 7.2.12
SymbolTable` will be considered as incompatible ... + * from libraries that are compiled by ngcc. ... export declare class Esm2015ReflectionHost extendsĀ ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found