Skip to content

Rules for scoped packages with local definitions differs from @types definitions. #30599

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

Closed
MicahZoltu opened this issue Mar 26, 2019 · 12 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@MicahZoltu
Copy link
Contributor

TypeScript Version: 3.4.0-dev.20190326

Search Terms:
scoped package local definition

Code
tsconfig.json

{
	"compilerOptions": {
		"moduleResolution": "node",
		"noEmit": true,
		"typeRoots": [ "./node_modules/@types", "./types" ]
	}
}

source/index.ts

import Foo from '@scope/scoped'
new Foo()

types/scope__scoped/index.d.ts

declare class Foo { }
export default Foo;
  1. npm install typescript@next
  2. npx tsc
  3. --> Notice error:
    source/index.ts:1:17 - error TS2307: Cannot find module '@scope/scoped'.
    1 import Foo from '@scope/scoped'
                      ~~~~~~~~~~~~~~~
    Found 1 error.
    
  4. move types/scope__scoped/index.d.ts to node_modules/@types/scope__scoped/index.d.ts
  5. npx tsc
  6. --> Notice no error.
  7. move node_modules/@types/scope__scoped/index.d.ts to types/@scope/scoped/index.d.ts
  8. --> Notice different error:
    error TS2688: Cannot find type definition file for '@scope'.
    error TS2688: Cannot find type definition file for 'scope__scoped'.
    Found 2 errors.
    

Expected behavior:
A module located in any directory referenced by typeRoots compiler option behaves the same, whether it is in node_modules/@types or some other type root.

Actual behavior:
When the module is in node_modules/@types it can be resolved via the magic rename of scope__scoped to @scope/scoped. When the module is in any other typeRoot directory, it cannot be located.

Playground Link:

Related Issues:
Directly related to #15204 (but that is locked).
Related to #23999 as well.


I am aware that I can resolve this specific issue by wrapping my definition file in a declare module "@scope/scoped" { ... }. This issue is more for tracking the fact that there is a discrepancy that is unexpected from a user's point of view. I was writing this definition in preparation for uploading to DefinitelyTyped, and to get started I copied an existing scoped module from DefinitelyTyped into my project and ran into this problem (which took me quite a while to troubleshoot/isolate since I didn't expect node_modules/@types to be special/magic).

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Mar 26, 2019
@RyanCavanaugh
Copy link
Member

typeRoots does not impact the resolution of modules - it only changes which type definitions are automatically brought into the program (if types is unspecified), or provides additional places to look up type libraries specified from types or reference directives.

There is special logic when resolving modules that node_modules/@types is a fallback location if the normal logic doesn't work. There isn't currently a way to customize that fallback location or add additional places.

You might be able to make something with path mapping happen, but I haven't tried.

@MicahZoltu
Copy link
Contributor Author

Would it be a reasonable feature request to have all typeRoots be eligible as a fallback for missing module resolution?

As a user, my expectation is that node_modules/@types is just a default location for type definitions and anything that works there would work in any typeRoot. Is there something that would break if this expectation held?

I often learn how to create type definitions by looking at DefinitelyTyped, and when something that works there doesn't work in my local project I'm left in a situation where I need to find another source of example to work from.

@MicahZoltu
Copy link
Contributor Author

In the meantime, is there any workaround that would allow me to define a scoped package locally other than via the declare module "..." {} mechanic?

@RyanCavanaugh
Copy link
Member

We can always have more options, but typeRoots already means something and the thing it means is not something to do with module resolution. Making it conflate with other things would increase confusion and complexity.

Path mapping is generally the right way to specify a different file location for a non-relative module name.

@MicahZoltu
Copy link
Contributor Author

MicahZoltu commented Mar 26, 2019

Something that is tickling my brain about your explanation is that if the module is not scope (and thus I can name the folder exactly), the definition works when I copy it into any typeRoots folder.

I think ultimately that is why this feels like a bug, because everything works fine right up until I scope the package, then it all breaks.

Why is it that module resolution works in typeRoots when the name is an exact match, but it doesn't work when the module is scoped (and thus has to go through the name transformation during lookup)?

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@johanlunds
Copy link

johanlunds commented Feb 22, 2020

Just ran into this issue, and yes it does feel like a bug.

The above declared workaround declare module '@scope/scoped' { ... } does not work for me for some reason.

Now I don't know what to do.... Will look into manual type mapping.

@yshelomentsev
Copy link

May be it's folly, but my problem was in package.json. Yarn doesn't add scoped(! only scoped) packages automatically in dependencies block...

In included package:

tsconfig.json:
{
  "compilerOptions": {
    ...
    "declaration": true
    ...
  }
}

package.json:
{
  ...
  "types": "dist/index.d.ts"
  ...
}

@johanlunds
Copy link

johanlunds commented Feb 24, 2020

So this was my solution that worked:

tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "./",

    // add all extra @types declarations here (including non-scoped now)
    "paths": {
      "@abandonware/noble": ["./@types/abandonware__noble/index.d.ts"],
      "json-diff": ["./@types/json-diff/index.d.ts"],
      ...
    },
    ...
}

I tried ./@types/abandonware__noble/index.d.ts and ./@types/abandonware__noble.d.ts without any extra custom tsconfig.json options, but that did not work. ./@types/json-diff/index.d.ts worked without any extra config, but since adding custom compilerOptions I have to add that as well.

Is my solution a workaround, or is this the supposed way to solve adding type declarations for scoped packages?

TS Version: 3.7.4

@johanlunds
Copy link

johanlunds commented Feb 24, 2020

I also tried ./@types/@abandonware/noble/index.d.ts but not working.

@johanlunds
Copy link

Did my solution in #30599 (comment) work @TrueWill?

@TrueWill
Copy link

TrueWill commented May 8, 2020

@johanlunds No, but thank you for posting that and for following up.

I tried:

  • typings/scope__package/index.d.ts (with adjusted typeRoots)
  • "paths": { "@scope/package": ["typings/scope__package/index.d.ts"] }
  • /// <reference types="typings/scope__package/index.d.ts" />
  • Various permutations of relative paths (such as including ./ or not)

All without success. I finally gave up and just require'd the module.

To be clear, I was running into this with ts-node, so it may be specific to that tool. I'll delete my earlier comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

6 participants