-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
disable certain global types for specific files, or specify type roots for specific files. #37053
Comments
The main concept is that we often need to install If someone tries use a The idea here is that some option would allow us to specify which files certain |
The the new The doc says
This seems to imply that isolation of globals may be possible (f.e. so that things like EDIT: Yes, |
This issue keeps popping up the more TS projects I work on. What we need is a more robust way of managing which global types are included or excluded. A couple cases are illustrated next. Case 1: ergonomics: Setting For this use case, we simply need a way to exclude certain global types. Case 2: libraries with global builds and modules: The import {Camera} from 'three'
// all good, no type or runtime error
const camera = new Camera
// no type error, but runtime error "can't read property Scene of undefined"
const scene = new THREE.Scene For this use case, we simply need a way to exclude certain global types. Case 3: multiple libs in a project depend on the same global namespace, but expect different types Sometimes a project needs to use both React (for old parts of the code base) and some new library that also relies on JSX (for new parts of the code base). What ends up happening is that both React and the new lib (f.e. Solid.js, Preact, and a bunch of others) need to define For this case, it would be great to be able to control which global types are available for which sets of files (I imagine it similar to a paths mapping, but mapping sets of files to type roots or similar). Then the project author can put React files in one place, and other-library files (f.e. Preact files or Solid.js files) in another place, and simply configure which files particular global types are available to. An alternate existing solution for this case is to split the project into multiple TypeScript Project References, but this can be cumbersome and lead to other issues. Having an option to control which globals are available to which files (which is a more fine-grained control than just "which globals to exclude" with the set of files to which we control global visibility being the whole project, as shown in the previous cases) would be useful here, without needing to restructure a project and introducing new issues. Furthermore, this option could still be helpful in Project References (it is tangential, and can be an option that works in any tsconfig.json, regardless if that tsconfig.json is for a project reference). Case 4: source files vs test code Projects often include both For this case the solution is the same one suggested for Case 3, for example exposing or excluding some globals to or from one set of files. Here's a set of issues with people effectively asking for more control over global types:
@RyanCavanaugh Hoping this comment can satisfy the |
This seems to be the current proposal:
Really not a fan of that idea. Excluding type packages with file globbing sounds much nicer. |
I experienced a global clash issue today, and reproduced it here Say you had a monorepo with two packages
The problem is |
This is just going to create compile errors in lib files that need them (and they surely must need them, otherwise they wouldn't reference them in the first place). The problem here isn't that random definition files end up in your program, the problem is that transitive dependencies end up in your program even though they're immaterial to your particular use case. |
Hello everyone and @RyanCavanaugh (and @aleclarson because you pointed out Ryan's proposal). I've come up with a new compiler option idea that may solve some of the problems above (unless I missed that such a solution already exists): The hypothetical new Rather than all This seems like a very practical and standard role for To make this work well, you'd want to import particular files into your project, rather than simply importing package index files that import everything you may not use (this good practice also helps reduce bundle size without any tooling). |
The reason for my previous comment is, apparently, global types are picked up even when "types": [],
"typeRoots": [] and you've imported anything from a package that has global types (even if you never import the files that
@RyanCavanaugh Another problem is that some parts of one project need certain globals, while other parts of a project need other globals. For example, having two forms of JSX in a project where some files need to pick up React's global JSX, and in the other files we want some other JSX types without React's JSX types polluting global. |
Everyone has the same issues, which indicate something must be done. A purely browser project shouldn't be forced with ambient node types which almost always end up being loaded (I don't think we can fix it at this level as it would require every single lib and their dependencies to be a good actor) and pollute the global namespace. |
@RyanCavanaugh I have a suggestion for your proposal. In addition to setting // tsconfig.json
{
"compilerOptions": {
"strictEnvironment": true,
"types": {
"**/__tests__/**": ["jest"],
"src/node/**": ["node"],
"src/client/**": ["lib:dom"], // Maybe allow lib definitions too?
}
}
} edit: On second thought, once // tsconfig.test.json
{
"include": ["**/__tests__/**"],
"compilerOptions": {
"strictEnvironment": true,
"types": ["jest"]
}
}
// src/node/tsconfig.json
{
"compilerOptions": {
"strictEnvironment": true,
"types": ["node"]
}
}
// src/client/tsconfig.json
{
"compilerOptions": {
"strictEnvironment": true,
"lib": ["dom"]
}
} But would I need two |
One other use case for this would be when a misbehaving package adds a global type that it shouldn't. For example, declare global {
interface Observable<T> {
['@@observable'](): Observable<T>;
}
} (https://cdn.jsdelivr.net/npm/@apollo/[email protected]/utilities/observables/Observable.d.ts) This conflicts with See:
|
yeah....I really need this. I try to develop a plugin under typescript for Blockbench, which is a combined environment under electron. .Its need lib.dom.d.ts type but its override some lib.dom.d.ts global type in plugin context. And its drive me crazy, since it just simple won't work caus of duplicated declared problem and seems not possible to override the global object under typescript d.ts which can be done under javascript. I guess I have to made a custom lib.dom.d.ts type as workaround :/ |
This is one of the few things about TypeScript that have irked me enough times over the years that I was going to open my own issue. But then I found this, which is sorta close enough, so I’m chiming in here: IMO, global type declarations, despite the name, shouldn’t be made available globally at all. The “global” refers to them defining something in the global context. That doesn’t mean, however, that every file in a project should have access to it implicitly. Especially in modules (files with Suggestion 1I suggest adding another opt-in “strict” option, to remain backwards compatible. Having it enabled would mean global declarations would only be available in the file that imported it explicitly and importing a type declaration with global types wouldn’t make it implicitly available in all other project files as well. This is probably similar to https://gist.github.com/RyanCavanaugh/702ebd1ca2fc060e58e634b4e30c1c1c, but for types instead of libs. This way, tests could be compiled along with source files in the same tsconfig without the source files suddenly having Suggestion 2I think what lies at the core of this issue is that many module type declarations contain global declarations and exports, for legacy/UMD reasons. So one other partial solution would be ignore the global declarations when importing something from a module (as opposed to importing the module itself). In this version, This wouldn’t help much with the unit test example since many unit test frameworks’ typings only expose global declarations and have no exports declared, as the globals are injected by the test framework. Open questions
|
Any updates on this feature request? I've run into this limitation many times. Adding a global way to control typeRoots would really make things much easier than having to manage configuration via inheritance and overrides. So far I've been creating child |
Any updates on this? Dealing with an issue where It's very annoying that something like this exists for 'inside the project' files but not for types. The current experience just isn't a good developer experience and is a massive annoyance on larger projects. |
I don't have any suggestions, but another use case: I want to use Storybook which internally adds |
Monorepo using NX here - We build browser, NodeJS, and workers from a set of common libraries. The issues this aims to solve are easy to make and very time-consuming to resolve. |
I am running into this while developing a app with I am using jest for testing, but as soon as I include For anybody looking for workaround ideas: |
Having spent several hours trying to get One particular place I've recently found this frustrating is module augmentation for testing frameworks. const { Assertion: untypedAssertion } = await import("@esm-bundle/chai"); // Partial ESM compat workaround
const Assertion: typeof import("chai").Assertion = untypedAssertion;
Assertion.addMethod("identicalAlg", function (expected: string): Promise<void> {
// ...
}); The following doesn't work: declare module "chai" {
// .. only augment Chai.Assertion
} So I have to do this: const { Assertion: untypedAssertion } = await import("@esm-bundle/chai"); // Partial ESM compat workaround
const Assertion: typeof import("chai").Assertion = untypedAssertion;
Assertion.addMethod("identicalAlg", function (expected: string): Promise<void> {
// ...
});
declare global {
export namespace Chai {
interface Assertion {
identicalAlg(expected: string): Promise<void>;
}
}
} Now these types are available from every file in the project. But:
The current situation forces me to choose between several non-obvious behaviours that make it harder to make the project easy to maintain and contribute to. (Of course, there are also issues like others have described in this thread, such as |
Any updates on this issue? It's something that has come up quite a few times recently. Personally, I think @aleclarson's suggestion would be the most easy to understand while giving the most fine grained control. |
The linked suggestion doesn't really solve anything IMO. If different parts of the program can "see" different global files, then they have different global scopes, and they have different interpretations of the same type. Let's say you had something like this // lib.object_rotation
interface Object {
rotate(): void;
} where file1 can "see" These problems can be "solved" by the same mechanisms that we use for project references (IOW, separate compilations), but it's not clear what this extremely confusing per-file-global system would have over the existing solution of project references. |
Eslit does a great job here with env and so on. Where you have a global and override scope. Whereas overrides can provide the same options as the toplevel scope but overrides object properties (or extends them) and arrays. See https://github.com/angular-eslint/angular-eslint "Notes on eslint" for an example what I mean. |
True, but I don't think anyone asking for these changes would actually use the system in that way. If file1 and file2 share data, then usually they're either in the same environment or the interface between them is explicitly defined. If the interface isn't explicitly defined, it wouldn't be surprising if the compiler raised an error. |
Global augmentation in TypeScript is still troublesome. In particular, it's difficult to exclude (and therefore difficult to avoid compile errors) if it's available in a project or `node_modules`, even if not imported directly: - microsoft/TypeScript#37053 - #148
Reproducible:
We want Perhaps
{
"compilerOptions": {
"types": ["!(mocha)", "*"]
}
}
{
"compilerOptions": {
"types": ["*"]
}
} |
Am I correct that if you want a web project that has tests which run in node (meaning |
@MicahZoltu Assuming you want to include all other From https://www.typescriptlang.org/tsconfig#types:
As a workaround in the absence of negated pattern matching: you could write a simple script to generate a complete list by reading dependencies from |
for now, in install those types into another folder
and refer it as needed in specific files
|
Can somebody please summarize in short what the problem is in regards to why there's not much progress and this issue is still not solved after three years, given how serious it is? It's obvious that a glob pattern is needed (as OP already mentioned), because the problem is on a file basis. |
@gernotpokorny Other tasks have been identified to be more important. TypeScript is Free Open Source Software, so anyone (including you and me) can fix the issues — and even contribute our fixes upstream (using the Pull Request feature), so that everyone can benefit. |
@jsejcksn What is more important then having clean types in TypeScript? ^^ Shouldn't this be at the top of the list after critical bugs? |
If you use eslint, you can use this rule https://eslint.org/docs/latest/rules/no-restricted-globals to disable some globals If you want to enable/disable on certain file, you can use either |
This may now be solved with |
Nice copout. What do you gain from it? Consider the differential between the complexity of the TypeScript codebase vs. the resources of any individual developer blocked by any particular issue. Consider that there is no guarantee that the proposals of contributors outside Microsoft will be included: in my experience, maintainers react with "idk what this guy is talking about" to issues that do not further the product agenda - regardless of years upon years of valid arguments provided by the community. Do you honestly expect all PRs to be reviewed in good faith? Of course, TS devs are a competent bunch, so they excel at making excuses for TS being ridden with antifeatures that disallow people from just solving their own problem. See #18588 - or this absolutely brilliant and totally fair rationale for closing #14979:
I'd rather humans be left to figure out the correctness of types for themselves, too: by writing tests to verify their code actually works - rather than delegating about half of that task to a static analysis tool that also, by necessity, is a compiler, then disregarding the other half. However, my coworkers only know TypeScript. They do not seem to know JavaScript. Or testing. I like them, and I like where I work. So I'm stuck wrangling TS, too - for mine and their sake. For example, let's say I want to include The FOSS solution would be to fork TypeScript - and then the community would be forced to forever maintain an "alternative TS", that would slowly grow incompatible with upstream (just like TypeScript is now practically incompatible with JavaScript, talk of "supersets" notwithstanding). Good luck getting people to know about it and switch over! Boo, Microsofties. Shame on you. Instead of accommodating developers' needs, you keep gaslighting them into exhausted compliance. I hope one day every TS dev and TS apologist learns the error of their ways. Right now, I'd say TS5 sucks about 10% less than TS4, so maybe in TS6 another batch of papercuts that wasted innumerable dev hours will be resolved 🤞 Sorry, your comment struck a nerve there. I stand by my words, though: TypeScript is fake "Open Source Software". P.S. I've got half a mind to email the downvoters and ask them when was the last time they got a PR merged into TypeScript. But instead, I'll just leave this here https://graydon2.dreamwidth.org/306832.html and leave you to experience your knee-jerk reactions - I've got some AST rewriters to implement... |
@jsejcksn is there a solution to this problem that core typescript maintainers have indicated support for that someone from outside that core group could implement and PR upstream? the only comments in this thread from a core member are #37053 (comment) (in which @RyanCavanaugh indicates that a file glob pattern solution is inviable) and #37053 (comment) (in which @RyanCavanaugh reiterates that the “linked suggestion doesn't really solve anything IMO”). over in #52433, which requests support for treating libs as a union (not an intersection, as they are right now) in order to support type checking code that runs in multiple environments, there’s #52433 (comment), which again indicates a posture that is generally closed towards any of the proposed solutions (“The problem we've had here in the past is that a huge amount of code can be seen to be running in one environment by inspection, but not in a way that's susceptible to static analysis.”), but it does end with this:
that’s a suggestion for a user-land-based solution that would help with the isomorphic problem-space described in #52433, but would do nothing to help with the issue here as described by Ryan Cavanaugh as:
all of this leaves me with two questions:
|
Driving me nuts. JSX is becoming a more and more popular choice on the server, but as soon as a React project imports anything from the server, like a strong typing contract for the API, all types in React break because the non-React JSX redefines JSX globally |
@acusti What I got from this whole debate is that the right way of dealing with this issue would be for libraries to provide a separate However, doing it that way is discouraged by the fact that most users would view it as an inconvenience rather than a feature, as well as being a major change for existing libraries, many of which are no longer actively maintained in the first place. So other than doing it the wrong way, I don't think there's another practical solution to this issue. |
It's absolutely wild that importing any library that happens to have For example importing a type from a library to use in a shared API interface (Which then gets imported in the client side code) will leak NodeJS types to the client. |
This continues some ideas from #17042, which was closed by @microsoft (for inactivity, I think?).
Search Terms
disable certain global types for specific files, specific type roots for certain files
Suggestion
Ability to specify which
@types
(or in general, which specifiedtypeRoots
) apply to which files in a project.Use Cases
My project's
src
folder contains both.ts
and.test.ts
files, and I'd like an easy way for globals likedescribe
to be defined only for the.test.ts
files.Examples
Not sure, but maybe some sort of new options in
tsconfig.json
for specifying whichtypes
items,lib
items, ortypeRoots
items apply to which files.This would be a beneficial feature because various editors (VS Code aside) look for a
tsconfig.json
file at the root of a project, and at the moment the only way to make types work in all files is to just provides all types for all files, which is undesirable becausedescribe
is not a function that is available in source files (as an example).Example configuration: maybe instead of a
typeRoots
array, it can be an object like:Or something. That's just an example to get the idea rolling.
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: