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.

Provide contol over paths of inserted imports in ivy library build

See original GitHub issue

Which @angular/* package(s) are relevant/releated to the feature request?

compiler-cli

Description

Currently the ivy library compiler tries to guess where it has to find modules, directives, components, or pipes when it has to introduce new imports. As far as I can tell, it does this based on whether the target is an APF entry point or not.

This doesn’t work well in our use case, which is a monorepository managed via yarn workspaces. In other words, local packages are installed in node_modules in the repo itself, and will be built into APF npm packages and deployed later on. We don’t have paths in (most of) our tsconfigs, as node’s resolution algorithm is perfectly capable of handling our setup. But, because the files aren’t in APF locally, the compiler inserts deep imports, e.g. ../../node_modules/@scope/pkg/src/my.module rather than @scope/pkg.

This yields errors when using ng-packagr, as can be seen in this example repo: https://github.com/bgotink/angular-repro-20210826. The error is the same as the one seen in #38876.

We’ve got our own (closed source) library pipeline which “corrects” the paths the ivy compiler adds, to run the relative import back into a bare package specifier. This is done via a beforeTs transformer in Program#emit’s customTransformers option. We cannot implement the same behaviour in the .d.ts files though, as there is no afterDeclaration transformer in the angular Program (typescript’s Program has it though). This leads to packages being published with invalid types. I’ve included the output of our internal pipeline in the repository linked above. This transformer actually already existed before we switched to ivy. It was introduced to work around #23917. We extended it when we switched to ivy, without considering the root cause of the new import paths.

Proposed solution

Provide more control over inserted imports in the APIs of @angular/compiler-cli. While the compiler-cli itself doesn’t know the entire context it is used in, and it has to make certain assumptions, the consumer of the compiler-cli package has more understanding of the context and can make more educated decisions.

In our scenario we would want the compiler to always use bare package specifiers for imports that are not in the entry point currently being built.

Alternatives considered

  • Consider non-APF packages inside a node_modules folder as “these will be APF when published” rather than “this is a local private package”. Whether this is a viable option is questionable at best, as there might be repositories where local private packages are linked via yarn workspaces This could be turned on/off via an option in the compiler-cli APIs or via angularCompilerOptions.
  • Provide an afterDeclaration custom transformer option in Program#emit. This would allow us to continue hacking around the inserted imports by replacing relative imports into node_modules with bare package specifiers.

Alternatively, do nothing in angular. We could build our entire monorepository in topographical order (dependencies before dependants) rather than the order in which the projects occur in angular.json, which would make it possible to use tsconfig paths to load the built APF packages for local dependencies. This would work, but…

  • It would require all (transitive) dependencies always be built to build any single package. This conflicts with our test pipeline which, like nx’s affected feature, builds only the packages impacted by the PR to dramatically speed up the pipeline for large projects.
  • It would lead to confusing behaviour if someone forgets to rebuild a dependency, as the tsconfig would lead to the previous version being used. This is especially true as we still want to resolve dependencies to the typescript source in our IDE. Added to that the number of mistakes with our generated @microsoft/api-extractor API reports would skyrocket.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
JoostKcommented, Oct 2, 2021

I looked at the repo a bit and I think that the root cause here is that OneModule is consumed from source, which exposes the implementation of OneModule.forRoot to Angular’s static interpreter (instead of exposing just the .d.ts file). When interpreting function calls based on .d.ts metadata the compiler uses a “foreign function resolver” to extract an expression that it can evaluate:

https://github.com/angular/angular/blob/c721135e370b34c840756bcfb22c8119b4c8c452/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts#L457-L477

Importantly, it makes an effort to switch the evaluation context to start tracking the originating import specifier if the function was imported using an absolute module specifier.

For functions with body, the compiler instead uses the current evaluation context regardless of how the function is imported:

https://github.com/angular/angular/blob/c721135e370b34c840756bcfb22c8119b4c8c452/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts#L480-L497

When applying the same technique as for foreign functions, as follows, your example compiles correctly using @scope/one as module specifier. I added the following logic just before evaluating the function body:

    // If the function occurs in a different file, then assume that the owning module
    // of the call expression should also be used for evaluating the function body.
    if (fn.node.getSourceFile() !== node.expression.getSourceFile() && lhs.bestGuessOwningModule !== null) {
      context = {
        ...context,
        absoluteModuleName: lhs.bestGuessOwningModule.specifier,
        resolutionContext: lhs.bestGuessOwningModule.resolutionContext,
      };
    }

I believe this is the correct thing to do, as the compiler generally assumes that an absolute import path indicates a different entry-point.

0reactions
bgotinkcommented, Apr 7, 2022

We’ve been consistently running into this issue once every few months, and for now our solution has been to replace the forRoot() function with an alternative set-up (e.g. a second root-only module). We are now starting to run into cases where this becomes harder to do.

@JoostK Is it feasible to land the fix you proposed or to add the necessary hooks for us to work around the problem?

EDIT: Right after writing this comment I realised that the emitCallback does give us access to the afterDeclarations hook on the typescript Program, so I have been able to update the same workaround we already had for the .js files to also work for .d.ts files.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Ivy can't compile a project that has a library with internal ...
Ivy is not able to determine the file paths for imports inside of a library in the same CLI project when it is...
Read more >
Documentation (2.1.0) | Apache Ivy™ @version@
Welcome to the official Ivy documentation. What is Ivy? Ivy is a tool for managing (recording, tracking, resolving and reporting) project dependencies.
Read more >
Build Times, Bundle Sizes, and Other Improvements Made by ...
This article covers Ivy, Angular's next-gen compilation pipeline, and the new and exciting improvements that came with the Version 9 ...
Read more >
Managing the dependencies of a seam-gen project with Ivy
Out of the box, seam-gen copies all the dependencies of Seam (and then some) into the lib directory of the generated project.
Read more >
Building Java Libraries Sample - Gradle User Manual
This guide demonstrates how to create a Java library with Gradle using gradle init . You can follow the guide step-by-step to create...
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