Typescript: Suggestion: add excludeTypeRoots to tsconfig

Created on 20 Sep 2017  ·  67Comments  ·  Source: microsoft/TypeScript



TypeScript Version: 2.4.0 / nightly (2.5.0-dev.201xxxxx)
2.5.2

It's a old issue that haven't resolve yet.

17242 #17042

Resubmit this for old ones are closed.

Case

  1. Used many declaration in node_modules/@types
  2. @types/node bring some compile error (it is a frontend project, but @types/node is a dependency of other package)
  3. So it need to disable @types/node, while other

Suggestion

  1. Support excludeTypeRoots in tsconfig.json.
  2. excludeTypeRoots have higher prioiry than typeRoots
In Discussion Suggestion

Most helpful comment

Actually many definition have bugs.
It impossible to make every developer submit a PR and wait author to update them.
So it is very necessary to have a way to disable those definition with bugs.

Actually many definition from JavaScript project would have this problem.
Indeed so many... You can find many issue in DefinitelyTyped everyday. such as:

Of course I can write my own definition in typings, but the problem is sometimes it would be conflict with the original one. That's why I need a option to exclude them.

All 67 comments

So far we have not got this request for many users. with types specifying what to include and typeRoots to specify the location to look for types it seems possible to address most scenarios. adding yet another include/exclude pattern for types is not attractive.

This issue is mainly happened in those full-stack library. (Both support Node and browser).

Some of this library depends on @types/node, such as Protobuf.js.

And this would lead to some error.
etc. return type of setTimeout is different when there is @types/node.

My recomendation here is to set types in your tsconfig.json all the time. I have personally found it to be more predictable this way while working on multi-project repos.

@mhegazy
It can't resolve the problem, you just try this:

npm install @types/protobufjs

Code

let shouldBeNumber = setTimeout(() => { }, 0);
shouldBeNumber = 123;

If @types/node is used, this will get a compile error, otherwise it will compile successfully.

Setting types to ["protobufjs"] (withoutnode) is useless.
Because node is a dependency of @types/protobufjs, that's why I say it need a way to exclude.

I miss understood the situation then. this seems like a definition issue. include/exclude starts the process. but once there is an import or /// <reference types=".." /> the compiler will suck in the additional definition files. that is the same way include / exclude work today for files as well.

The definition file should not have a dependency on node if it does not need it.

The definition file should not have a dependency on node if it does not need it.

But actually, there is already many frontend packages depend on @types/node, such as @types/react-dom @types/protobufjs and so on...

But actually, there is already many frontend packages depend on

These are definition bugs that need to be fixed i would say.

Actually many definition have bugs.
It impossible to make every developer submit a PR and wait author to update them.
So it is very necessary to have a way to disable those definition with bugs.

Actually many definition from JavaScript project would have this problem.
Indeed so many... You can find many issue in DefinitelyTyped everyday. such as:

Of course I can write my own definition in typings, but the problem is sometimes it would be conflict with the original one. That's why I need a option to exclude them.

Still have this problem, anyone have good way to resolve ?
So many library have this problem, even @types/react-dom

Just to up this, it is giving me headaches.

Have the same problem with [email protected]. It depends on dnd-core, which depends on @types/node, which globally declares the require function, which causes a duplicate require declaration error.

I've tried using the paths property to redirect references to @types/node to an empty file, but it appears to have no effect:

"paths": {
  "../node_modules/@types/node": ["Frame/NPMOverrides/node.d.ts"],
  "node_modules/@types/node": ["Frame/NPMOverrides/node.d.ts"],
  "@types/node": ["Frame/NPMOverrides/node.d.ts"],
  "node": ["Frame/NPMOverrides/node.d.ts"],
}

EDIT: Have worked around the issue for now by setting compilerOptions.skipLibCheck to true in my tsconfig.ts file. Not ideal, but at least the compile error doesn't show up now.

It can be managed using the postinstall workaround mentioned on another thread. Recommend using shx package for multiplatform support.

"postinstall": "shx rm -rf node_modules/@types/node && echo 'workaround for libs importing @types/node on browser environment'"

EDIT:
from: https://github.com/microsoft/TypeScript/issues/18588#issuecomment-487561397
a better workaround than running rm -rf on postinstall: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/21310#issuecomment-434329726

This should be supported.

My recomendation here is to set types in your tsconfig.json all the time

This recommendation isn't ideal for my use case, which is: I have a base tsconfig.json that defines types, and I want to extend it (using extends).

If I specify types in the extension config, it will override the types from the extended config (perhaps it could merge?). This unfortunately means I'm left with no option but to duplicate the list in both TS configs, which is a bit of a maintenance burden.

Unless I have overlooked something, this would also be handy in lerna projects. I have some packages that don't require fancy libs and some that do. However, since the dependencies are hoisted, typescript scans the definitions from all the packages and determines that even the simple ones need esnext.asynciterable for example.

I can either prevent certain @types packages from being hoisted, or I can explicitly list the types in the simple packages tsconfig.json - but a blacklist option would be preferred.

It can be managed using the postinstall workaround mentioned on another thread. Recommend using shx package for multiplatform support.

"postinstall": "shx rm -rf node_modules/@types/node && echo 'workaround for libs importing @types/node on browser environment'"

You know something is wonky when a rimraf is needed as a workaround...

It seems like the powers that be on the typescript side think that this is a matter for package maintainers to deal with, but the reality is messier -- there are different packages that are alternatives for other ones (like react vs. preact for example) where people are trying to use these in the same project, and the solution for dealing with types is not always straightforward:
https://github.com/developit/preact/issues/1036

None of the solutions proposed, such as defining types, or remapping using baseUrl/paths actually work. The rimraf is literally the only thing I've found that does work. Do we really want rm rf commands sitting around in our configuration files, waiting for a misplaced asterisk or slash to come along?

Please please please implement this.

By far this is one of the biggest annoyances in Typescript. Expecting that every single dependency's types are absolutely perfectly built before being able to build your code correctly - we all know this is impossible, developers are not perfect, _configuration for TypeScript libraries can be difficult_, and I'm struggling with this very thing with Google engineers who have completely messed up their types for libraries I'm using.

The fact that I can't easily just tell Typescript to ignore those types for now and spit out my code is so infuriating. At the core of, its still JavaScript - and these libraries have been built already - I can make use of them now, but TypeScript is being a real productivity blocker here.

These are definition bugs that need to be fixed i would say.

Is the current expectation to really go and fix every single library you depend on's types? What do you do while you wait for your pull requests to be accepted? There are certain situations where "make a pull request" is a viable answer - this is definitely not one of them - because its actually completely unrelated to the issue at hand.

Yeah, I'm now facing this problem too. I'd love an exclude for types.

Unfortunately a recent change to angular-cli has added a type dependency in for "node", which breaks all my references to settimeout and setinterval (as their type is supposed to be number, and now is NodeJS.Timeout) :(

I'm seeing the same problem in my project and defining types in tsconfig doesn't seem to help. I have to file issues to library that includes @types/node to remove them. e.g. https://github.com/TooTallNate/node-data-uri-to-buffer/issues/8

Would definitely be helpful if there's way to just exclude @types/node.

Having typeRoots keep the order for overloads would be also helpful. This way we can overload the buggy ones at least.

Since this is tagged "awaiting more feedback":

We're in the same boat. Web libs with dependencies on server typings are polluting our types with incorrect node types.

The end result is @types/node is ending up in a ton of browser targeting projects.

Are people asking for excludeTypeRoots or excludeTypes ? It sounds like a lot more the latter than the former?

I can only speak for myself, but I am after an excludeTypes :)
(I agree, it looks like most people seem to want that)

ie. many of us need to exclude @types/node

Is skipLibCheck a dominating strategy for people in this situation? Or is the inclusion of node itself the problem?

I haven't used that before, but it sounds like it would prevent ALL types from all node_modules packages, as opposed to just @types ?

(ie. you couldn't use Angular, which has .d.ts files for all its libraries)

-EDIT-
(or does it just prevent errors inside node_modules packages that would kill the compile? this is not my problem - my problem is that node types are overriding browser types, like setTimeout should return a number, but with node, returns NodeJS.Timer)

@RyanCavanaugh I believe in our case it would be excludeTypes. Here are two situations we've hit recently, and I think it summarizes what most people are seeing:

  1. Create React App adds @types/node and Styled Components adds @types/react-native... these two collide so that a fresh CRA install w/ Styled Components is unable to build (if skipLibCheck is off).
    Ref: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33015

  2. Many browser-targeting libraries add @types/node. react-dom is a visible example, though recently fixed. Some common DOM window functions like setTimeout infer to the incorrect node typing for setTimeout, which creates a TS compilation error – unless you write window.setTimeout. All CRA apps have this issue as far as I know - any library that works across node and the browser, but targets the browser, will likely have this.
    Ref: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/21310

There may be existing config solutions to these problems. I am far from an expert in this area - just summarizing what I've found.

There may be better proposals to fix these: allowing the config to specify the precedence of types to put DOM types over node types would solve bullet 2 most accurately for CRA apps. Further, it might be the responsibility of the type definitions to be more correct in the first place – but it's happening in multiple libraries, so it seems the maintainers need some help. There's not one huge issue, but I've found a long tail of issue threads running into this same core difficulty of unwanted types from dependencies.

For those using yarn, see point 1) of https://github.com/DefinitelyTyped/DefinitelyTyped/issues/21310#issuecomment-434329726 for a better workaround than running rm -rf on postinstall.

@types/node are especially problematic. It's difficult to avoid this package being included in a front-end project (React or React Native).

@types/node has many global definitions (console, setTimeout etc.) which are not compatible with front-end definitions in @types/react-native.

From my experience, TypeScript will choose one set of definitions or the other at random. So it's a coin toss whether the project compiles or not.

Consider @types/node:

declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timeout;
declare namespace setTimeout {
    function __promisify__(ms: number): Promise<void>;
    function __promisify__<T>(ms: number, value: T): Promise<T>;
}
declare function clearTimeout(timeoutId: NodeJS.Timeout): void;
declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timeout;
declare function clearInterval(intervalId: NodeJS.Timeout): void;
declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): NodeJS.Immediate;
declare namespace setImmediate {
    function __promisify__(): Promise<void>;
    function __promisify__<T>(value: T): Promise<T>;
}
declare function clearImmediate(immediateId: NodeJS.Immediate): void;

While @types/react-native:

declare function clearInterval(handle: number): void;
declare function clearTimeout(handle: number): void;
declare function setInterval(handler: (...args: any[]) => void, timeout: number): number;
declare function setInterval(handler: any, timeout?: any, ...args: any[]): number;
declare function setTimeout(handler: (...args: any[]) => void, timeout: number): number;
declare function setTimeout(handler: any, timeout?: any, ...args: any[]): number;
declare function clearImmediate(handle: number): void;
declare function setImmediate(handler: (...args: any[]) => void): number;

Both are global definitions.

Not sure what's the solution... So far I used patch-package to comment out one set of global definitions.

I think TypeScript can automatically resolve this problem in many cases by checking _requiredBy field of package.json of installed @types packages. When the compiler has known, as a result of this checking, that an @types package is not a dependency listed in package.json (note that we can know it without seeing package.json file of the main project), this package should be excluded from the targets of auto loading. Can you adopt this logic?

I believe I'm running into a similar issue with conflicting type definitions in downstream dependencies. In my case, koa-bodyparser and koa-joi-router both augment the koa module definition with a body parameter, but one defines the parameter as required and one does not. I've submitted a PR to DefinitelyTyped, but it would be great if there was a feature that allowed me to resolve these kinds of typing conflicts. Problems like this contribute to my impression that using Typescript results in spending most of my time solving typing issues rather than writing code.

I'd suggest the .gitignore / .npmignore way, allowing mixing includes/excludes in one list, and priority them by order.

I don't think it is a good idea, but is there chance to distinguish function overloads by return type?

As I mentioned at #31148, unused @types files installed by node modules pollutes and breaks the main project. It is caused by del package at that time. Now this problem came to be spread by another major package npm-check-updates. I warn the destruction of ecosystem is expanding. TypeScript must resolve the problem quickly. See a solution https://github.com/microsoft/TypeScript/issues/18588#issuecomment-492616933.

When a package has erroneous TypeScript type definitions, I use the following workaround to completely disable the package's own *.d.ts:

noop.d.ts:

declare module "PACKAGE_NAME";

...and:

tsconfig.json:

    "paths": {
      "PACKAGE_NAME": [
        "noop.d.ts"
      ]
    },

Same problem here, using the skipLibCheck workaround :-/

Some stuff I've compiled about this problem when I was mad at TS again:


1) some common 3rd party type definition problems (applies to both typeRoots and packages that declare their own types):

  • A type definition module can override global types, or can introduce new global types.
  • A type definition module can change types of other packages (via module augmentation).
  • Via interface merging, type definitions can have conflicts with each other.

2) typeRoot problems:

  • @types pkgs can depend on other @types packages, thus they must be hoisted into the type root (i.e. @types).
  • Hoisting can introduce unwanted @types packages via dependencies of dependencies.

3) types from a pkg's package.json. These problems are mostly related to OS packages that have lackluster typings declared in their package.json.

  • A pkg can have broken types for a given version.
  • A pkg can have wrong types.
  • It is very hard to prevent tsc from using types in a package.json. There are workarounds, but not easily accessible.

4) versioning:

  • @types pkgs are not tied to original pkg's (i.e. js implementation) version, so you can never know what types version will work with which implementation version.

5) Disabling typeRoots and doing manual type whitelisting via typessolves only 1 problem: Avoiding unwanted @types packages.

This issue has been open for over 2 years now, has no one gone and submitted a PR to React to fix their bad types in all that time? That certainly feels like the right solution to that specific problem.

That being said, I do think this issue deserves a solution for other reasons:

{
  "devDependencies": {
    "@types/node": "...",
    "ts-node": "...",
    "typescript": "...",
}

```ts
// source/index.ts
import * from 'path' // should fail, this library is meant to be used in node & browser!

```ts
// scripts/build.ts
import * from 'path' // should succeed, this is a build script that executes in nodejs

I have separate tsconfig.json files for the library and the build scripts, but in order to utilize types I need to include every type in the library, which is basically just duplicating the contents of my dependencies... all so I can ignore a single @types/node.

What I would really like is for node to be a first class citizen like dom and es2018 so I can simply _not_ include it in my library tsconfig.json and include it in my build script tsconfig.json.

The core of the issue is that I have one package.json and multiple tsconfig.json files. The TypeScript projects all _share_ a single node_modules folder so I don't have to download everything twice.

_While the OP of this issue mentions “typeRoots”, I think most people (perhaps even OP?) end up thinking of the types property of compilerOptions. In any case, that is what I will be discussing; apologies if this in any way detracts from the OP. With that out of the way…_

Having maintained the react-native typings for a few years now, I’ve seen various issues raised that all boil down to the same thing. Either people have a react-native project with a dependency that pulls in @types/node or they have a react dom project with a dependency that pulls in @types/react-native. I can’t find a currently valid example of the former, because people have since spent time on making the globals of @types/react-native and @types/node mergeable, but here’s one of the latter: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33015

All suggested solutions come down to not letting tsc use the @types/* package that’s irrelevant to the env the user is targeting. What ends up being the problem to most users is the user-experience of doing so; which is to allow-list all the types the user _does_ want, by specifying them using the types property of the compilerOptions. Rather, people would like to deny-list the few they don’t want. (Currently people end up doing this by e.g. adding those packages to a .yarnclean file, which automatically removes matching entries after installing packages.)

Now, I’m not necessarily a proponent of having more options–in general–however, precedent has been set by also having include and exclude options for files. It feels natural then for this pattern to extend to other options that have at least 1 way to be explicit, besides a greedy implicit default. I.e. currently there’s 2 types options:

  1. Include _all_ packages found in node_modules/@types by default
  2. Include _only_ the ones explicitly specified with the types property of compilerOptions

I propose to add a third one:

  1. Include _all_ packages found in node_modules/@types, but _exclude_ those explicitly specified with a excludeTypes property

I have created a repo that both demonstrates the problem and comes with a simple tsc patch to add a excludeTypes property to compilerOptions: https://github.com/alloy/tsc-exclude-types-repro

_cc @orta who I’ve been pairing with_

@alloy Can you summarize here why it is not possible to fix the typings you linked? Most of the issues I see in this thread are just people who are trying to hack around broken type definitions, but IMO the real solution for those is to fix the type definitions rather than hack around their brokenness. If there are situations that are fundamentally unfixable, that would be good to describe in this issue.

@MicahZoltu it boils down to being productive. I said it earlier in this issue. Yes, it would be great if every single library had its types in order at all times- but it is abundantly clear that this is a pipe dream.

There have been countless hours wasted for me personally on this, when someone deploys a new version or adds types to their previously untyped library and they completely mess things up in one way or another, be it their types, or them including other types which are inadvertently not compatible with something else in my stack.

The core of this issue is productivity and not wasting another second on something so ridiculous. The fact of the matter is, the code does work, at the end of the day its just JavaScript, but TypeScript has come in and decided that if any types are slightly wrong, you just won't be able to compile today. Because they said so. Because they want to be overly strict, when they could just give us the option to say, for this specific library - just treat it as any!

Yes, libraries have issues and they will get fixed. I will still go and open up an issue about the types and try and help fix those issues - but flip, when I just need to push a new feature out the door and TypeScript gives me this crap, it really drives me up the wall, as I'm sure it does everyone else in this thread and the countless others who suffer silently.

Its frankly ridiculous to argue "Just fix the types..." at this point. We have reiterated the problem clearly now, over and over again. Please be more developer (and my sanity) friendly and see the problem here please.

@MicahZoltu Good point 👍

Theoretically it is likely possible to fix these issues, but it requires people to:

  • re-architect how they provide optional features–e.g. styled-components/native could be a separate package
  • make typings that could clash mergeable

All of these rely on multiple people fully understanding the problem and checking all possible permutations of how people might load these dependencies. In short, it’s a very human task and thus inevitably will fail [again].

Because there’s no great builtin escape hatch for end-users to control what typings do _not_ get loaded, this ends up being a maintenance burden for DT maintainers [such as myself] _and_ results in frustrated [TS] end-users.

my solution / workaround to “nullify” incorrect imported typings is to add a path definition to tsconfig.json such as:

"paths": {
  "offending-package": [
    "typings/noop.d.ts"
  ],

...with noop.d.ts containing:

declare module “offending-package”;

To be clear, I am an advocate of this feature, but for the reasons specified in https://github.com/microsoft/TypeScript/issues/18588#issuecomment-598037818 and I think that the fact that it will enable developers to drop bad transitive dependencies is a minor loss for the community.

The downside of enabling developers to drop transitive type dependencies is that it will give people an "easy path" to solving their "right now" problems that doesn't help the ecosystem move forward in a healthy way. While some people will take the time to submit a PR to the incorrectly typed library despite there being an easy workaround, the vast majority of people will just apply the workaround and move on. Some subset of that vast majority would have submitted a PR if that were the only option, while another subset would have gone and used a different library (one with proper types).

My fear is that by giving developers an easy path to "fix my immediate problem" that doesn't align with "fix the problem for everyone going forward" we likely will just end up with more broken code.

@MicahZoltu I hear you, but I don’t think ‘forcing’ people realistically leads to a culture of more fruitful collaboration. I think a pragmatic approach of having to add an entry to a denylist means we can have both:

  • an easy escape hatch for those that wouldn’t contribute a fix anyways [and are only a cognitive drag on collaborators]
  • while still indicate to those that have the mindset to _do_ want to contribute a fix that there is an issue that can be improved

The downside of enabling developers to drop transitive type dependencies is that it will give people an "easy path" to solving their "right now" problems that doesn't help the ecosystem move forward in a healthy way.

I kinda disagree. The ecosystem will still move forward. People use TypeScript because they enjoy the types and autocomplete that comes when using other people's libraries. If they need to switch that off for the sake of productivity in a single moment, it does not mean that they will ignore the fact that this library isn't typed anymore- especially if its something they use often. Have you seen how many issues pop up on regular JavaScript libraries asking people to add types / rewrite in TypeScript? Its clear that people do care, even when types don't exist from the get go.

I don't believe people will stop caring about their types being correct simply because they have been given the choice to be more productive by temporarily turning those types off. If that were the case, then TypeScript wouldn't have the adoption it does today. And honestly if they do never turn them back on, so what? Its their choice to code without types then. I'm certain that many people will still want their types back though.

And as @alloy said, "forcing" people simply because TypeScript sees this as a way for users to keep the ecosystem healthy isn't a good call either. It feels kinda sadistic and is wasting a lot of time for people.

excluding types would really be great

As a workaround to disallow some globals (e.g. nodeJs process) in shared code, we use es lint no-restricted-globals

We also used this rule no-restricted-modules - but unfortunately it is now deprecated. And I don't see how this can be replaced by eslint-plugin-node

Since I've not seen it mentioned, here's my workaround:

Create a package.json file containing just {"types": "index.d.ts"} in a subdirectory like typings/fake-empty/package.json and a empty index.d.ts file.

then in your main package.json, specify a dependency like "@types/node": "file:./typings/fake-empty"

This way, npm will install that empty version of that dependency in your root node_modules, which means it won't be able to hoist the real @types/node from your dependency. This means that the real @types/node will be in node_modules/react-native/node_modules/@types/node, and not affect your main project.

Same works for other types packages you want to prevent hoisting for.

This works better than the paths: workaround since that apparently does not work for global declares such as NodeJs.Timer etc.

I have this problem again with yarn workspaces. My workspace contains projects some of which use DOM library and some don't. Some dependencies will use DOM library as well.

Now, the problem is that projects which don't use DOM library no longer compile, because TypeScript will include everything under @types even though it's not referenced by the project in any way.

The only solution seems to be to add DOM library even for backend projects, which kinda sucks.

EDIT: So clearly my ask is for excludeTypes, to be able to exclude a types package which is not referenced by the project or any of its dependencies.

Two years after the original issue was submitted we still observe many definitelyTyped definitions have reference to @types/node and creating conflicts.

Just to name a few:
@types/uuid
@types/body-parser
@types/cheerio
@types/glob
@types/jszip
... and many many others.
and any packages that inherently depend on those

I think it is fair to say that "fixing the types" to resolve this problem is not easy and will take long time. Meanwhile, it will be really helpful if we could either control what type we to hoist, or make node part of the embedded types like DOM.

@types/uuid

I got uuid fixed recently :D

https://github.com/uuidjs/uuid/issues/328

But yes, would love a general fix for this problem still!

I got a slightly different situation, that has nothing to do with @types/node.

I have a monorepo which is using Yarn Workspaces, and I have some conflicting types. Take this structure example:

.
├── project-one/
├── project-two/
├── project-three/
├── node_modules/
├── package.json
└── tsconfig.json

So, here we have three projects, project-{one|two|three}, with one global tsconfig.json file.

project-one and project-two are old, legacy projects, and they're both using the package [email protected], which uses [email protected].

Now, project-three is a fairly new project. It uses another internal package of ours, which requires styled-components@^5 to be installed (it requires it as a peer dependency).

So now the issue:

[email protected] has its own typings, but styled-components@^5 does not, it relies on @types/styled-components.

Now, as both project-one and project-two have [email protected] as a common dependency, it is pulled to the root node_modules folder, which is ok.

But then, project-three ignores the @types/styled-components, and use the outdated type from the dependency in the root.

I don't wanna touch the legacy projects, nor the inner dependencies.
What I'd like to do is to remove just the undesired typings from my specific config in project-three/tsconfig.json, something like:

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "typeRoots": [
      "./definitions",
      "./node_modules/@types",
      "../node_modules/@types",
      "!../node_modules/styled-components/typings" < this isn't possible
    ]
  }
}

I believe I am looking for the same solution as the OP.
Is there something else I could try?

There are serious problems with conflicting globals being sucked in and breaking people's compilation, and we continue to be invested in solving those, but excludeTypes isn’t the solution. The only thing excludeTypes could possibly do is prevent the _automatic_ inclusion of specific libraries under node_modules/@types. But there’s no way we can exclude those libraries when others that you _do_ intentionally include reference types from them, which is the unfortunate situation that most people are in. For that, we have to explore more nuanced ways of dealing with these conflicts, as in the spirit of #31894.

To get rid of types that are automatically included but _not_ explicitly referenced, we still believe the types option is the answer. While it does perhaps require _some_ manual maintenance, I think this is justified for two reasons:

  1. It is not as much maintenance as most people claim. People often assume that specifying types means they have to list _every_ set of typings they want included in their compilation. This is not true for the same reason that excludeTypes cannot exclude types that are indirectly included by other libraries or your own source code. Most typings packages type modules, which must be imported to be used. When you write an import of a typed package anywhere in your project, that is sufficient to include the typings, so you need not include it in your types array. As a concrete example, it would never make sense to write "types": ["react"], because you must import from "react" to use it. As soon as you import it, the entry in the array becomes redundant. This is true for the huge majority of typings people consume. The outliers are typically packages that provide _globals_, like jest and webpack-env. Most projects will need to specify a small handful of types at most.
  2. The alternative to this maintenance is an excludeTypes that simply could not live up to its name, for the reasons already discussed. It would cause an incredible amount of confusion for users who see errors due in @types/node, add node to their excludeTypes, and yet still find that node is being included and errors are being issued. In my experience, this would be the norm—most of your type dependencies are there because they are actively being used, even if it’s via an indirect chain that you would be better off breaking, if it were possible. The problem is that breaking that chain, or making it so that errors cannot traverse it, is a _much bigger_ problem than excludeTypes can solve.

Again, we _really_ want to solve this problem in general, but we believe this suggestion would not solve it, and so leaving it open is misleading.

Why is it so hard to just say, for any library that is included in excludeTypes, for example "excludeTypes": ["node"], - treat ___any___ encountering of this library in my current project, _in its entirety wherever it may be encountered_, as any?

But there’s no way we can exclude those libraries when others that you do intentionally include reference types from them, which is the unfortunate situation that most people are in.

I'm struggling to understand the issue you're bringing up here. The any type (the excluded library) can bubble down to all libraries which reference them - if you import something indirectly through an intermediate library, it remains any as well. As a consumer of the library, its not our job to ensure those indirect dependencies are always working, and this is actually the core of the issue at hand here- where it more often than not occurs (through second-hand types).

The alternative to this maintenance is an excludeTypes that simply could not live up to its name, for the reasons already discussed. It would cause an incredible amount of confusion for users who see errors due in @types/node, add node to their excludeTypes, and yet still find that node is being included and errors are being issued.

This is what I'm talking about. Why would the user still be seeing those errors? If the excluded types are properly excluded, then all forms of node - be it @types/node or simply the import of node - must be made to be any and in every way completely ignored for error checking.

I don't think this issue should have been closed - its a seriously important problem that is one of my biggest gripes about TypeScript and has caused some serious stress and frustration at times. There must be a solution that allows us to specify modules to be completely excluded from TypeScript- the very nature of how you have allowed people to mish-mash types on top of whatever library they feel, often getting things wrong because its ___not___ always a perfectly compiled system, means TypeScript almost has an obligation to put this kind of functionality in somehow.

I appreciate that you say you really want to solve this problem, but by closing this and saying that excluding is not the way seems a bit illogical and that maybe you're not understanding how big of an issue this is. No one is going to do it the other way around- when it comes to issues of this nature, exclusion of the bad apple is always going to be easier than sorting through, identifying and including of only the good ones.

Please reconsider opening this and understand that all we want is simplicity- even if it comes at other smaller costs. We just want to be productive.

I think typescript shot itself in the foot by:

1) Allowing modules to pollute the global namespace
2) Allowing modules to pollute other modules (i.e. module augmentation)

If the above points were not allowed, a library that I import (which imports another library which imports @types/node) could not break my global.setTimeout type by overriding it with node's setTimeout types or pollute the global namespace with node stuff.

I think typescript will eventually need a subset of itself where above points are not allowed; which would actually harm one of my favorite libs (fp-ts) due to their HKT implementation which abuses module augmentation.

by closing this and saying that excluding is not the way seems a bit illogical and that maybe you're not understanding how big of an issue this is. No one is going to do it the other way around- when it comes to issues of this nature, exclusion of the bad apple is always going to be easier than sorting through, identifying and including of only the good ones

No one is suggesting that conflicts should (or could) be resolved by specifying which types you want. This is a false dichotomy—you’re assuming that the solution would require maintaining either an include list or an exclude list. We would like you not to have to maintain any lists. The proposal we’re currently considering achieves that goal. It would be silly to fix this problem with a heavy-handed excludeTypes option that required you to manually fiddle with stuff until you’ve introduced so many anys that everything type checks when we could achieve better results with less manual intervention. (I would have linked that proposal before, but I wasn’t sure it had been shared publicly yet—but I found it linked in https://github.com/microsoft/TypeScript/issues/40184).

Okay, thanks for clarifying.

I hope that you can make that solution work and we get past this frustration soon. It does seem very complex on the surface, whereas this proposal seems very simple (cast to any) and has already been around for 3 years now (with plenty of discussion and confirmation) and sure, it might seem slightly overboard but it doesn't explicitly break anything besides the end-users ability to have strongly typed code, which doesn't affect the final compiled code at all.

On the surface this solution seems quick and easy and I wish it could have just been implemented while the team continues to find a much better solution in the future.

It would be silly to fix this problem with a heavy-handed excludeTypes option that required you to manually fiddle with stuff until you’ve introduced so many anys that everything type checks when we could achieve better results with less manual intervention.

I wouldn't consider it "heavy-handed" at all. Often its just a single library that's causing a problem _in this instance_ that has completely blocked our ability to be productive. As I mentioned earlier - people will definitely want those types back ASAP. Its the reason we use TypeScript in the first place.

In any case, thanks for what you guys have done so far and I hope we can bring this problem to a close soon.

As the saying goes, for every difficult problem there is a solution that is simple, neat and wrong. Just forcing e.g.node to be excluded from your compilation just brings up a huge host of new problems. For all the types that are no longer resolvable, what do we do? Do we treat them as any? That's incredibly dangerous in all positions. Do we treat them as never ? Also dangerous in input positions and possibly nonsense in output positions. What about lookup types that try to refer to a property inside one of those types? What about types that extend from or intersect with unresolved types? What is the behavior of a conditional type that makes a test based on one of the types you've explicitly included from your program? It's all a giant mess and the only cases for which there are good straightforward answers are the cases where you could trivially stub out the dependency in DefinitelyTyped and not have this problem in the first place.

For all the types that are no longer resolvable, what do we do? Do we treat them as any?

Yes. This is exactly what I'm hoping for.

That's incredibly dangerous in all positions.

We lose type-safety, sure. But this is __exactly what we want__ and have deliberately set for this specific moment to get over errors which more often than not have _nothing_ to do with our direct project and its types. We just want to be productive, if that's at the cost of some type safety - then that's perfectly fine for me and I'm sure the majority of others who have faced some of these intensely annoying scenarios.

What about types that extend from or intersect with unresolved types? What is the behavior of a conditional type that makes a test based on one of the types you've explicitly included from your program?

The same behavior that any would do in those scenarios, I guess? If there is a conditional - it should completely resolve to any. It should just be any all the way down.

There of course could be more things I'm not considering. But on the surface it seems like its basically saying "switch TypeScript off for any encountering of this library". Return to basic JavaScript and set everything to any. We just want to be able to compile and get on with our lives - since the code will still work, we just consciously lose some type-safety of course. Which we will want to get back ASAP.

I understand your reservations, but there really needs to be an escape hatch for when these type issues arise. As I said, the nature of TypeScript necessitates it. Its intensely un-fun and draining of productivity and motivation.

It's all a giant mess and the only cases for which there are good straightforward answers are the cases where you could trivially stub out the dependency in DefinitelyTyped and not have this problem in the first place.

This is not true anymore. Most pure JavaScript libraries are opting to throw a d.ts file directly into their library- its definitely the common trend now. There's no easy way to stub out those types which have been set directly in a package's "types". Many libraries also now generate their types using all manner of methods (API endpoint descriptions etc.) and include them directly in "types".

This is not true anymore. Most pure JavaScript libraries are opting to throw a d.ts file directly into their library- its definitely the common trend now. There's no easy way to stub out those types which have been set directly in a package's "types". Many libraries also now generate their types using all manner of methods (API endpoint descriptions etc.) and include them directly in "types".

Can't you create a .js file, export * from 'library-with-broken-types and create a .d.ts file next to it that exports any or any type you want?

Can't you create a .js file, export * from 'library-with-broken-types and create a .d.ts file next to it that exports any or any type you want?

So I import everything from that JavaScript file then? And when the types are fixed, I have to go and change all my imports? Or have this middleman .js adapter file in my project.

I often resort to something similar to this, but by creating a special sink.d.ts file:

declare const x: {
  [key: string]: any;
};
export = x;

Or with the library module directly (something like this, though don't have the code in-front of me right now):

declare module "@elastic/eui" {
  const x: {
    [key: string]: any;
  };
  export = x;
}

But is this really the answer here? Its at best a hack. All I'm asking for here is some kind of easy native way to achieve the same result by quickly setting a property in our tsconfig.json.

Yes. This is exactly what I'm hoping for

So let's say you have a .d.ts file

// Definitions for: crc32

/// <reference types="node" />

// Computes the crc32 of a string. Also supports Buffer input if on 'node' platform
declare function crc32(input: string | Buffer): number[];

Then you write

const p = crc32(someStr.toUpperCase);

This typo just got accepted without error because crc32 now accepts string | any., which a function passes for The definition file has been rendered 50% useless.

This is what you want to happen?

That's not ideal, of course. This is just a temporary solution to compile and move on in scenarios where you are completely blocked.

There are ways to make it slightly stricter or just have strong warnings in place. But if I'm able in this instance to compile my code and be productive, its still a net win in my eyes. I would think I'd be conscious of the decision I've made and be sure any interactions with things that might make use of the library are done with extra care like back in the ol' plain JavaScript days- and of course get the types back as soon as possible.

What about making the any a special type of any - with the semantics of __any originating from node__ - hence it becomes (semantically):

declare function crc32(input: string | any<origin module node>): number[];

That would at least help prevent some of those very basic potential errors (passing in the function instead of calling it, etc.).

Anyway, I understand its not desirable - the entire situation is not desirable, but an escape-hatch is really needed for those times when you simply can't get around it unless you go and directly edit dependency types or hack some kind of d.ts - which ends up achieving the same issues you are bringing up here.

You can show a giant red warning for types which are being excluded at the end of compilation, as well.

What about making the any a special type of any - with the semantics of any originating from node

How did you know that Buffer came from node? You might have multiple types excluded; we don't know which it would have came from unless we imported the file in the first place.

Already you can see that this is turning into a very complex feature, not a "just exclude this" thing. Like we said earlier, we want to figure out a good solution instead of plowing effort into a bad solution that isn't going to have good ergonomics and creates backcompat headaches for the rest of time.

I think a good first step might be establishing and documenting best-practices for library and type definition authors as well as consumers.

  • Best practices for authoring a library/type def that has an optional dependency on node types
  • Best practices for users when their web project depends on a library that pulls in node types
  • Best practices for determining _which_ library is pulling in node types

etc.

In terms of types in projects, I would have thought they could be encapsulated.

ie. if I have my own project, and I include library X - I should get the types specifically exported by that library.
and if library X uses a type or library internally, say @types/node, then I should not see anything in my project from node because it's completely encapsulated inside library X.

Is that not a possibility?

An open issue related to this can be found at #37053. Show your support with upvotes and comments.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bgrieder picture bgrieder  ·  3Comments

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

Antony-Jones picture Antony-Jones  ·  3Comments

Roam-Cooper picture Roam-Cooper  ·  3Comments

manekinekko picture manekinekko  ·  3Comments