TypeScript: TypeScript 3.9.7 doesn't auto import from dependancies of dependancies (3.6.3 does)
I have a “parent” app which is a base library that we use in other apps. We recently moved all the dependancies into the parent package.json, and removed them all from the children apps keeping only the parent. Making sure we had the right versions was cumbersome, and NPM would install all the dependancies of the parent app anyway so things work.
The problem we noticed is that we can only auto import from the parent (which is in the child app’s package.json), and not things like Angular or RxJS.
TypeScript Version: 3.9.7 (also 3.8.3, and 4.0.0-dev.20200803 with a VS Code plugin)
Search Terms: auto import package.json dependancies types
Code
I have created an example repo here: https://github.com/cjdreiss/ts-import-error
It contains the “parent” app, and a “child” app with instructions in the readme.
Parent package.json
"dependencies": {
"@angular/animations": "~10.0.6",
"@angular/common": "~10.0.6",
"@angular/compiler": "~10.0.6",
"@angular/core": "~10.0.6",
"@angular/forms": "~10.0.6",
"@angular/platform-browser": "~10.0.6",
"@angular/platform-browser-dynamic": "~10.0.6",
"@angular/router": "~10.0.6",
"rxjs": "~6.5.5"
}
Child package.json
"dependencies": {
"@cjdreiss/ts-import-error-parent" : ">0.0.0"
}
Component trying to import things from rxjs
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { ExampleComponent } from '@cjdreiss/ts-import-error-parent';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'child';
test: Observable<any>;
// test2: BehaviorSubject<any>; // won't auto import even though there is an rxjs import already...
parent: ExampleComponent; // auto imports because @cjdreiss/ts-import-error-parent is in package.json
}
Expected behavior:
Auto imports from things like rxjs
and @angular/xxx
should work
Actual behavior: Only auto imports from dependancies listed in package.json work (my library as an example). If we change the TypeScript version VS Code is using to 3.6.3 it can import it.
Non working import in 3.9.7
Working import after switching the TS version to 3.6.3
Related Issues: I believe this issue might be related (although it says its in Milestone 4.0.0), and there are other related issues in that https://github.com/microsoft/TypeScript/issues/37812
This seems to demonstrate the same issue: https://github.com/microsoft/TypeScript/issues/37187 which was closed as a duplicate of https://github.com/microsoft/TypeScript/issues/36042
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 9
- Comments: 24 (6 by maintainers)
This is very much the intentional behavior; dependencies of your dependencies are not part of the dependency contract and it’s fundamentally incorrect for us to encourage violating that assumption. You are always free to write the imports manually.
@RyanCavanaugh
TypeScript used to work with this use-case (having a shared framework). It no longer does. You guys inadvertently broke an important use case we and others were using in production. A use case that is important for enabling enterprise-level web development (which was one of the original goals of TypeScript…). Just because you philosophically disagree with it doesn’t change any of those facts.
Couldn’t the TypeScript compiler at least provide a compiler flag to opt-in to the old behavior?
“Writing imports manually” isn’t a solution when you have dozens of developers with varying degrees of expertise all utilizing the same common framework.
Typescript traverses up the directories and scans package.json files it finds. https://github.com/microsoft/TypeScript/blob/04205ca32c2b6e60a6c38cbf47961319566a8ea4/src/server/project.ts#L1903
This means if I create a dummy package.json inside
src/
I can get auto-imports to work without messing with the project’s own package.json../package.json
./src/package.json
The script that generates the dummy package.json:
This comment is intended to summarize approaches to the problem identified in this ticket.
First, I’ll share that I too am running this problem. I have package A whose package.json lists peerDependencies B and C. I want consumers of package A to be able to npm install package A and have B and C installed automatically, this works great. However, I want them to also be able to use VSCode to autocomplete anything from A, B, or C. This does not work, only A will autocomplete. This thread and other threads explain very well that this is the intended behavior. (Note that in my use case these are private packages, but I think these principles apply the same.) And, to put a real-world use case to the problem I want to bundle a suite of NestJS and related packages together as a starting point for building applications, conceptually similar to how Drupal has distros. A “bundle” package would allow distributing a single package that does the hard work of specifying the right version compatibility of all packages in the bundle.
I see 5 umbrella approaches to this problem. In the examples below I’ll refer to these fake package names:
@vendor1/package1
@vendor2/package1
@vendor3/package1
1 - Don’t import dependencies of dependencies
In this scenario, you change the assumptions. Instead of expecting consumers of package A to be able to autocomplete from B and C, you say that consumers of package A can only autocomplete package A. It would be the responsibility of A to expose any functionality that B or C provide. I think this perspective was succinctly phrased in @RyanCavanaugh’s comment.
CON: A “bundle” package isn’t valid in this scenario. If a consumer needs functionality from B, then package A must expose that functionality, e.g. using adapter pattern.
I don’t have an example of how this would work; it would be highly specific to the code in A, B, and C.
2 - Directly list dependencies
Have consumers of package A list B and C as direct dependencies, e.g. using
peerDependencies
. See @abdusco’s comment. To avoid version conflicts between your project’s specific versions of B and C with package A’s versions of B and C, consider using loose specificity in your project, e.g."@vendor1/package1": "*"
.CON: Bubbling up dependencies B and C requires manual upkeep or custom tooling, and may break lockfiles in CI tooling (see @abdusco’s comment).
Expand to see example
The package.json for your project:
The package.json for package A:
3 - Add a dummy package.json solely for autocomplete
Have consumers of package A list B and C as direct dependencies in a dummy package.json that is within the ts include path, e.g.
src/package.json
. See @abdusco’s comment.CON: Bubbling up dependencies B and C requires manual upkeep or custom tooling, and the fact you have multiple package.json files may be confusing.
Expand to see example
Your project’s directory structure:
The package.json in the root of your project:
The dummy package.json in
src/
:IMPORTANT! If your project’ structure is more complicated, e.g. a monorepo with NestJS or NX, you will need a dummy package.json in each directory like so:
4 - Re-export using type definitions
Have consumers of package A use a type definition that re-exports types from sub-dependencies B and C. See @abdusco’s comment.
CON: Bubbling up dependencies B and C requires manual upkeep or custom tooling.
Expand to see example
The tsconfig.json for your project:
The type definition for B at
typings/vendor2/index.d.ts
:The type definition for C at
typings/vendor3/index.d.ts
:5 - Package inheritance
Have consumers of package A use package inheritance. A StackOverflow thread points out that npm does not support package inheritance and is unlikely to do so. However, there are a few alternatives that could be explored to see if they are valid for your situation:
CON: NPM does not support inheritance, so custom tooling is required or switching dependency managers.
For examples, look at the relevant tools listed above.
I hope this is a helpful and accurate summary; there are a lot of related tickets that I have spent considerable time wading through and I think this is the proper spot to comment. I also hope this saves others time and inspires identification of a simpler solution. If I have said anything in error please provide correction and I will update.
Edit 2022-07-08 15:47 ET: Expanded from 3 to 5 approaches.
Looks like TS team should talk with other well-known teams, like framework devs to find out a way, that will still provide import suggestions (at least for frameworks classes and objects). Like an option or separate feature.
Just disabling such feature, is not a correct way. Huge step back.
@abdusco we didn’t find a work around. We went back to using full, “normal”, package.json files in our consuming apps and built a CLI upgrade tool to help manage the package.json files in our apps during upgrades.
You’re solution seems pretty clever though, if we get tired of the “normal” way of doing it in the future, maybe I’ll try this but for now we’re going to stick with the usual way of having a proper package.json file.
Is it possible for the TS team to add some option to specify which dependencies it should use to resolve auto-imports for? That way you could opt in to the old behavior for a specific dependency.
Anyone who builds a “base class library” type situation used to have this scenario work and it’s now broken. That seems like a major regression to me…