Skip to content
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

typeRoots is not finding custom declaration file #22217

Closed
lwilli opened this issue Feb 28, 2018 · 24 comments
Closed

typeRoots is not finding custom declaration file #22217

lwilli opened this issue Feb 28, 2018 · 24 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@lwilli
Copy link

lwilli commented Feb 28, 2018

TypeScript Version:
Version 2.6.2

Search Terms:
declaration file for module exporting function, external module, npm package, third-party module

Code
src/testFile.ts:

import getFieldList = require('graphql-list-fields');

src/@types/graphql-list-fields/index.d.ts:

// Doing just this below (as suggested by the Typescript Handbook does not work 
// and results in the tsc error under **Actual behavior** below
import { GraphQLResolveInfo } from 'graphql';
declare function getFieldList(info: GraphQLResolveInfo): string[];
export = getFieldList;

// The code below is what actually works instead of the above
/*
declare module 'graphql-list-fields' {
    import { GraphQLResolveInfo } from 'graphql';
    function getFieldList(info: GraphQLResolveInfo): string[];
    export = getFieldList;
}
*/

Expected behavior:
Compiling without errors.

Actual behavior:
Error when running tsc:

src/testFile.ts(1,31): error TS7016: Could not find a declaration file for module 'graphql-list-fields'. '/node_modules/graphql-list-fields/index.js' implicitly has an 'any' type.
Try `npm install @types/graphql-list-fields` if it exists or add a new declaration (.d.ts) file containing `declare module 'graphql-list-fields';`

graphql-list-fields is an NPM package that I'm trying to use, which currently has no published type declarations.

After reading through the Typescript Handbook's section on declaration files, I found that I am trying to write a declaration file for a modular library and should use the module-function.d.ts declaration file template. This is how I came up with the code in index.d.ts above, but the compiler still complains about not having the declaration file for the module 'graphql-list-fields'. After trial and error, we found that adding declare module 'graphql-list-fields' around everything worked without compiler errors.

We tried setting "typeRoots": ["./node_modules/@types", "./src/@types"] in tsconfig.json, as mentioned in the tsconfig docs but that still did not work without declare module 'graphql-list-fields'. This seems to be an issue because tsc is not finding the type declaration in the directories specified in typeRoots.

Related Issues:
3019, 8335

@lwilli lwilli changed the title Add documentation for how to write a declaration file for a module exporting a function TypeRoots is not finding custom declaration file Feb 28, 2018
@lwilli lwilli changed the title TypeRoots is not finding custom declaration file typeRoots is not finding custom declaration file Feb 28, 2018
@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2018

"typeRoots" is meant for global code. i.e. something that is declarated in the global namespace, and you want to include it. this is why your declare module 'graphql-list-fields' {.. works, since it just declares a module with that name in the global namespace.

For modules, they have thier own scope, all you need is path mappig..

somethign like:

{
    "compilerOptions": {
        "target": "es5",
        "baseUrl": "./",
        "paths": {
            "*" : ["src/@t`ypes/*"]
        }
    }
}`

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Mar 2, 2018
@lwilli
Copy link
Author

lwilli commented Mar 2, 2018

Thanks for clearing that up @mhegazy. I think it would be helpful if this was explained more in the TypeScript docs, possibly under the declaration files section.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2018

TypeRoots is really a support we have added for back compat with typings and to allow migration, so we tried to keep it out of the docs as much as possible to avoid confusion.

Not sure if this fits in the declaration file section. we do have a note about it in https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping

I can add a note in the declaration file section about module resolution section as a whole.

give that section a read and let us know if you still find docs in it lacking

@lwilli
Copy link
Author

lwilli commented Mar 2, 2018

Oh I see. Yeah, that is the page that I needed to begin with. I think it would be nice to have at least a brief mention of path mapping and maybe a link to that doc somewhere under https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html.

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@lwilli
Copy link
Author

lwilli commented Mar 22, 2018

I still think it would be helpful to have the docs updated as stated in my previous comment @typescript-bot

@ericdrobinson
Copy link

ericdrobinson commented Jul 24, 2018

TypeRoots is really a support we have added for back compat with typings and to allow migration, so we tried to keep it out of the docs as much as possible to avoid confusion.

@mhegazy Having a supported feature with little-to-no documentation is what drives confusion. Please properly document features like this but be liberal with your warning language. For example:

Note: This feature is provided for backwards compatibility with typings to support migration. See the release notes [here] for more information.

This would be vastly preferable to having to scan GitHub Issues, StackExchange, etc. for information.

@kaiwa
Copy link

kaiwa commented Sep 1, 2019

Thx a fucking ton @mhegazy ! 🎉 That path entry for @types was finally the missing piece which made my non-standard project setup work. I spent days on this.

@YEriin
Copy link

YEriin commented Sep 16, 2019

If you don't mind slow down your application starting process little, you can just add TS_NODE_FILES=true to your package.json's start script and all custom declaration file will be found.

@KrzysztofMadejski
Copy link

I'm now getting:

The following changes are being made to your tsconfig.json file:
  - compilerOptions.paths must not be set (aliased imports are not supported)

and this option is being deleted.

@kaiwa
Copy link

kaiwa commented Dec 1, 2019

@KrzysztofMadejski that's done by CRA, not TS itself.

@Roaders
Copy link

Roaders commented May 8, 2020

This thread says that typeroots is really for backwards compatibility. I am trying to use it to author types for definitively typed. How else would I properly test these types without typeroots? I have generated types using dts-gen and added types to my type roots and still no luck :-(

@abhijithvijayan
Copy link

abhijithvijayan commented Sep 10, 2020

@Roaders did you find the solution to use dts-gen generated types?

Edit: Solved with #22217 (comment)

@intellix
Copy link

intellix commented Sep 13, 2020

One thing I don't understand here is that:

tsconfig.base.json:

"typeRoots": ["node_modules/@types", "types"],

And a directory structure:

/node_modules/@types/something/index.d.ts
/types/something-else/index.d.ts

It won't get picked up at all in my nrwl/nx repository. I can try a million things but if I move that something-else folder into node_modules/@types it'll magically work and get picked up, which suggests the typeRoots does absolutely nothing as node_modules/@types is magically handled.

Marking typeRoots as deprecated and saying it works in a weird/quirky way would have at least stopped me trying to use it.

The only way I've managed to add local types each time I've needed to, is by spending hours fighting with my tsconfig.base.json and stumbling upon the mhegazy comment above

@andyperlitch
Copy link

I kept getting bitten by this and forgetting about having to configure the paths option as well, so I created a simple repository with the proper settings. Hope it helps someone.

@pikeas
Copy link

pikeas commented Dec 29, 2020

I'm being bit by this as well. I couldn't get custom types for a 3rd party project picked up no matter what I tried. It was working for a while by copying the folder into node_modules/@types, but tsc or another process validates that directory and deletes unexpected files.

{
    "paths": {
          "something": ["types/something"]
    },
    // typeRoots does not need to be set
}

This seems to work but it was extremely unintuitive and under-documented. It didn't occur to me to try paths until I found this thread after many hours of failures. I thought paths was for "A series of entries which re-map imports to lookup locations relative to the baseUrl" (https://www.typescriptlang.org/tsconfig#paths), and unrelated to resolving type declarations.

@gaurav5430
Copy link

gaurav5430 commented Mar 14, 2021

I have a library written in javascript, which exposes type declarations to external consumers. These type declarations are generated automatically using the typescript compiler in a types folder. In the consumer, it works fine, but I would like to use the same declarations within the library as well for intellisense/autocomplete within the IDE

I initially thought that typeRoots would be the way to go, but as discussed above, it seems to be only applicable for global modules / types.

as @mhegazy suggested, I tried using paths to accomplish this.
I can't make it work with relative paths, though it works fine with aliased absolute paths.

The below does not work

// src/packageA
...some code
// src/@types/packageA
...generated type definitions
// usage within the library - does not work
import packageA from '../../packageA'
// tsconfig
paths: {
  "*" : ["@types/*", "*"]
}

but the below code works:

// src/packageA
...some code
// src/@types/packageA
...generated type definitions
// usage within the library - works, but this is not what I want
import packageA from 'packageA'
// tsconfig
paths: {
  "packageA" : ["@types/packageA/*", "*"] //notice the aliasing here
}

This also works:

// src/packageA
...some code
// src/@types/packageA
...generated type definitions
// usage within the library - works, but this is not what I want
import packageA from 'absolute/path/from/base/url/'
// tsconfig
paths: {
  "*" : ["@types/*", "*"]
}

Note that both of the working versions require some change in my webpack module resolution as well to work correctly, but at least typescript seems to be resolving them correctly.

Am i missing something here, or is it generally not possible to make paths work with relative paths ?

@abdessamadely
Copy link

In my case, the issue was that I've been using include and exclude. The issue is that I was trying to access the global types from a file that wasn't included.

so I just remove the include and kept only the exclude, and now works as expected

@NetOpWibby
Copy link

@gaurav5430's comment got me on the right track!

My original folder layout:

@types/
  index.d.ts
src/
  index.ts
package.json
tsconfig.json

My tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "./",
    "declaration": true,
    "esModuleInterop": true,
    "module": "commonjs",
    "outDir": "dist",
    "paths": {
      "*": [
        "@types/*"
      ]
    },
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types",
      "@types"
    ],
  },
  "include": [
    "src/**/*"
  ]
}

I was still getting errors and I could not understand why. Turns out, @types/index.d.ts should actually be named after the module that doesn't have types. So, for me I had to change: @types/index.d.ts to @types/csv-to-js-parser.d.ts.

(Contents of that file being declare module "csv-to-js-parser";)

FINALLY

Also, this issue is super annoying.

mcmire added a commit to MetaMask/metamask-module-template that referenced this issue Mar 24, 2022
If a project lacks types for one or more dependencies, it is customary
to fill in those types via ambient modules in the form of `.d.ts` files.
Unfortunately `.d.ts` files aren't 100% supported in this template.

* Because `.d.ts` don't export anything, ESLint will complain.
* A `.d.ts` file can contain multiple ambient modules. However,
  TypeScript may not automatically pick up the types in this file
  (unless that file is covered by the `include` setting, but even then
  it's not guaranteed it'll be seen before the file that needs to use
  the types contained in the `d.ts` file is seen).

To address these issues:

* We instruct ESLint to treat `.d.ts` files as scripts rather than
  modules.
* We establish a `types/` directory where our ambient modules will be
  stored, and then update `tsconfig.json` to automatically consult this
  directory when resolving types for imported modules. (Note that we do
  *not* use the `typeRoots` option for this [as the `tsconfig.json`
  documentation might indicate][1]; this is [not intended to be used for
  this purpose][2]. Rather, we use `paths`.)
* We update `tsconfig.json` to widen its purview for development so that
  test files have access to all of the types that non-test files do.
* We add a special `tsconfig.build.json` file that is only used by `yarn
  build` which continues to ensure that only the files we want to
  publish (i.e. files in `src/`) are emitted to `dist/`.

[1]: https://www.typescriptlang.org/tsconfig#typeRoots
[2]: microsoft/TypeScript#22217
mcmire added a commit to MetaMask/metamask-module-template that referenced this issue Mar 25, 2022
If a project lacks types for one or more dependencies, it is customary
to fill in those types via [ambient modules][1] in the form of `.d.ts`
(type definition) files. Unfortunately this template cannot be used to
properly provide ambient modules.

* First, because type definition files don't export anything, and ESLint
  is configured to read `*.ts` files as modules by default, ESLint will
  complain that a `.d.ts` file could be parsed as a script instead.
  That's an easy problem to solve.

* Second, TypeScript's treatment of type definition files is not
  always intuitive, and we haven't fully understood how to use them. We
  usually work with type definition files in the context of packages, so
  the mechanics of how they work are kept out of sight. In that case,
  [TypeScript will use the `types` field in `package.json`][2] to know
  how to find the type definition files for a package.

  But what about backfilling types for existing packages (i.e.
  declaration of ambient modules)? We've been assuming that as long as a
  `.d.ts` file is matched by the `include` setting in `tsconfig.json`,
  that TypeScript will be able to see and use all of the types in that
  file. For instance, if you're importing a module `foo` in one `.ts`
  file, and you've backfilled the types for `foo` in another `.d.ts`
  file in the same directory, and both files are covered by `include`,
  then TypeScript should be able to bind types to the objects you're
  importing from `foo`, right? Not always. In fact, if you have a test
  file that is *not* covered by `include` that also imports the module
  `foo`, then TypeScript seems to ignore your type definition file and
  does not associate any types with `foo`.

* Lastly, this problem illustrates that writing TypeScript and building
  TypeScript are two separate processes managed by two different tools
  (`tsserver` vs. `tsc`, respectively) which follow different rules and
  have different behavior. We need to acknowledge this in our
  configuration.

So, here's what this commit does to address these issues:

* We instruct ESLint to treat `*.d.ts` files as scripts rather than
  modules.
* We establish a `types/` directory where our ambient modules will be
  stored, and then update `tsconfig.json` to automatically consult this
  directory when resolving types for imported modules. (Note that we do
  *not* use the `typeRoots` option for this [as the `tsconfig.json`
  documentation might indicate][3]; this is [not intended to be used for
  this purpose][4]. Rather, we use [`paths`][5].)
* We update `tsconfig.json` to widen its purview for development so that
  test files have access to all of the types that non-test files do.
* We add a special `tsconfig.build.json` file that is only used by `yarn
  build` which continues to ensure that only the files we want to
  publish (i.e. files in `src/`) are emitted to `dist/`.

[1]: https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules
[2]: https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html#editing-the-packagejson
[3]: https://www.typescriptlang.org/tsconfig#typeRoots
[4]: microsoft/TypeScript#22217
[5]: https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
@Casimodo72
Copy link

Casimodo72 commented Apr 12, 2022

Has anyone succeeded in making this work with a target set to ES6?
For me it works with a target set to ES5 or ES3 but not ES6 (or any other target).
I was wondering why the suggested strategy here didn't work for me and then tried out switching my target to from ES6 to ES5. But I don't want ES5.

@erikunha
Copy link

@gaurav5430
images (79)

@ALFmachine
Copy link

ALFmachine commented Jul 26, 2022

for anyone finding this issue i was able to resolve this in my code base by adding custom types to the tsconfig.base.json after declaring them in the typeRoots

tsconfig.base.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "rootDir": ".",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es2015",
    "module": "esnext",
    "lib": ["es2017", "dom"],
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "baseUrl": ".",
    "paths": {
      "@prisma-client": ["packages/libs/prisma-client/src/index.ts"],
      "@ui/meeting": ["packages/libs/ui/meeting/src/index.ts"],
      "@ui/shared": ["packages/libs/ui/shared/src/index.ts"],
      "@ui/util": ["packages/libs/ui/util/src/index.ts"]
    },
    "typeRoots": ["types", "node_modules/@types"],
    "types": ["@emotion-types", "@window-types"]
  },
  "exclude": ["node_modules", "tmp"],
  "files": [],
  "include": []
}

type dir
image

SO post - second answer

@bbortt
Copy link

bbortt commented Nov 17, 2022

for anyone finding this issue i was able to resolve this in my code base by adding custom types to the tsconfig.base.json after declaring them in the typeRoots

@ALFmachine big ups for the types after typeRoots.. can't believe the order matters :o

@fendouxiaohaier
Copy link

I have a library written in javascript, which exposes type declarations to external consumers. These type declarations are generated automatically using the typescript compiler in a types folder. In the consumer, it works fine, but I would like to use the same declarations within the library as well for intellisense/autocomplete within the IDE

I initially thought that typeRoots would be the way to go, but as discussed above, it seems to be only applicable for global modules / types.

as @mhegazy suggested, I tried using paths to accomplish this. I can't make it work with relative paths, though it works fine with aliased absolute paths.

The below does not work

// src/packageA
...some code
// src/@types/packageA
...generated type definitions
// usage within the library - does not work
import packageA from '../../packageA'
// tsconfig
paths: {
  "*" : ["@types/*", "*"]
}

but the below code works:

// src/packageA
...some code
// src/@types/packageA
...generated type definitions
// usage within the library - works, but this is not what I want
import packageA from 'packageA'
// tsconfig
paths: {
  "packageA" : ["@types/packageA/*", "*"] //notice the aliasing here
}

This also works:

// src/packageA
...some code
// src/@types/packageA
...generated type definitions
// usage within the library - works, but this is not what I want
import packageA from 'absolute/path/from/base/url/'
// tsconfig
paths: {
  "*" : ["@types/*", "*"]
}

Note that both of the working versions require some change in my webpack module resolution as well to work correctly, but at least typescript seems to be resolving them correctly.

Am i missing something here, or is it generally not possible to make paths work with relative paths ?

how to resolve?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests