Closed renke closed 6 months ago
same problem
So, aside from finding a solution to this problem, is my assumption correct that having multiple packages with typings that have the same version should not cause any problems?
I'd also appreciate any hints on where to look at the source code to solve this problem.
same problem!
@renke i have a repro where everything has the same version here : https://github.com/quadristan/ts-indirect-type-reference-bug
same problem :( have you solved it?
I haven't solved it yet, but there is also a similar issue https://github.com/microsoft/TypeScript/issues/48212 with a milestone of TypeScript 4.8.0. Let's hope it will be solved soon.
same problem
I got this issue because the mentioned dependency had this code in its index.d.ts
:
declare module 'react' {
interface DOMAttributes<T> {
css?: InterpolationWithTheme<any>
}
}
and the file it was complaining in was using React.HTMLAttributes<HTMLElement>
which references React.DOMAttributes
. I worked around this issue by omitting the declared properties Omit<React.HTMLAttribute<HTMLElement>, "css">
Same problem :(
Same Problem for me too :(
is there any expected timeline for a fix for this problem?
same here
@RyanCavanaugh
Same problem :(
same here
same here
What's worse is that neither // @ts-ignore
nor // @ts-expect-error
will allow us to ignore the incorrect error. This is a pretty gnarly bug.
This bug occurs pretty readily when using pnpm since the node_modules directory layout is different under pnpm than npm/yarn. Any npm package whose types reference another npm package's type will generate the error
I've made a minimal reproduction here: https://github.com/mrmeku/portable_types_repro
I've been deep-diving the issues related to this error and symlinks and pnpm specifically. Since 2019 it appears that this regression is reintroduced at least twice for each major version. That would suggest that tests are not sufficient for the case, nor to catch the regression.
I have a repro + a workaround/solution here
https://github.com/quadristan/ts-indirect-type-reference-bug
tl-dr:
import type {} from "Y"; // or whatever Y really refers to
I have a repro + a workaround/solution here
https://github.com/quadristan/ts-indirect-type-reference-bug
tl-dr:
import type {} from "Y"; // or whatever Y really refers to
Nice job! This is by far the best practice.👍
I've been deep-diving the issues related to this error and symlinks and pnpm specifically. Since 2019 it appears that this regression is reintroduced at least twice for each major version. That would suggest that tests are not sufficient for the case, nor to catch the regression.
The reason you see a history of this "regressing" at each version is that pnpm patches TypeScript when you install it, so every time TS updates, there's a few days where it appears to not work because pnpm hasn't updated their patching code yet.
@RyanCavanaugh really? Do PNPM is patching TS in order for it to be compatible each and every time it installs it? That's a huge surprise for me really. Feels very unsustainable and kinda scary. Why is TSC itself is not compatible with PNPM? I hear about the first time in my life and never saw anything like that in the PNPM change log (not that I reading them (aside from major releases)) :)
I've been deep-diving the issues related to this error and symlinks and pnpm specifically. Since 2019 it appears that this regression is reintroduced at least twice for each major version. That would suggest that tests are not sufficient for the case, nor to catch the regression.
The reason you see a history of this "regressing" at each version is that pnpm patches TypeScript when you install it, so every time TS updates, there's a few days where it appears to not work because pnpm hasn't updated their patching code yet.
could you point me at the code responsible for that? I'm a core contributor to PNPM and this is the first time I'm hearing of it. not to say that I disagree with you or that it isn't true, I'm just unaware of that.
Sorry, you're right. I've misconfused this with yarn.
@shellscape while I've got you here, we've been trying to reach security@
pnpm.io regarding an issue we identified a while back, but haven't received a reply. Can you poke the appropriate folks to take a look?
sure thing. feel free to DM me info on twitter as well.
In my case the error looks like this
The inferred type of 'Foo' cannot be named without a reference to '.pnpm/@morphic-ts+model-algebras@3.0.0_.../node_modules/@morphic-ts/model-algebras/lib/types'. This is likely not portable. A type annotation is necessary
The type definition wasn't exported from root of the package but it was from/lib/types
.
So instead of just importing the package:
import type {} from '@morphic-ts/model-algebras'
I had to specify the path which the type definition was exported:
import type {} from '@morphic-ts/model-algebras/lib/types'
@quadristan
You saved my life! I added:
import type {} from 'react'
This is definitely a super-nasty TypeScript bug. One of the worst I've encountered because it can't be ignored and, in this case, took me hours to debug and find this solution.
import type {} from "Y"
Oooh! Thank you so much!! And this can be done in the root index.ts or anywhere really. So good. 😄
I wonder if the root cause is at all similar to this eslint issue, I've been using that patch to modify eslint to stat linked files to work out if they are actually the same. Perhaps a similar approach might work here?
And because I didn't see it listed here. I had this same problem and couldn't get the "import type {} from ..." trick to work, but adding preserveSymlinks: true
to my tsconfig compiler options completely fixed my problem. See this issue for more comments. Probably relevant, but my issue was with a pnpm monorepo.
@ggascoigne - This got rid of the error but broke inference in my Redux store. Fwiw, I'm also encountering this issue in a monorepo (turborepo) + pnpm
@ggascoigne - This got rid of the error but broke inference in my Redux store. Fwiw, I'm also encountering this issue in a monorepo (turborepo) + pnpm
Yeah, I've just found another project, pnpm and monorepo, where neither solution works. I've slowly been converting all my projects over to use pnpm and this bug is making me rethink that whole plan.
@ggascoigne I'm a pnpm core contributor and if you need help getting past this, please ping
Happy to help too. My simple repro if the issue is using pnpm and I have a fix for it
https://github.com/quadristan/ts-indirect-type-reference-bug
Possibly also you are using another tool that tinker with the imports. Maybe a ttsc? ts-loader ? Webpack?
It's a monorepo where the main app uses next.js, and the library module is pulled in using next.js's experimental transpilePackages support. And it was working fine with this setup until I added a library that had previously only been used in that library to the main app. Now when code in the library is compiled in the app it gets a conflict.
The specific library is tss-react, it's installed in two places:
$ ls -l apps/acnw/node_modules
...
lrwxr-xr-x 1 ggp 93 Dec 14 16:45 tss-react -> ../../../node_modules/.pnpm/tss-react@4.4.4_7gkcoq2nf4xufkduddb7kah6jm/node_modules/tss-react
$ ls -l packages/ui/node_modules
...
lrwxr-xr-x 1 ggp 93 Dec 11 15:33 tss-react -> ../../../node_modules/.pnpm/tss-react@4.4.4_hp5f5nkljdiwilp4rgxyefcplu/node_modules/tss-react
$ls -ld node_modules/.pnpm/tss-react*
drwxr-xr-x 3 ggp staff 96 Dec 14 16:45 node_modules/.pnpm/tss-react@4.4.4_7gkcoq2nf4xufkduddb7kah6jm
drwxr-xr-x 3 ggp staff 96 Dec 11 15:33 node_modules/.pnpm/tss-react@4.4.4_hp5f5nkljdiwilp4rgxyefcplu
eventually these resolve to identical hard linked files, though all of the directories along the way are unique.
And then it fails to compile with:
../../packages/ui/components/Card/Card.tsx:5:14
Type error: The inferred type of 'useStyles' cannot be named without a reference to '../../../../apps/acnw/node_modules/tss-react/types'. This is likely not portable. A type annotation is necessary.
3 | import {} from 'tss-react/types'
4 |
> 5 | export const useStyles = makeStyles()({
| ^
So, is this a pnpm bug? Well probably not really, but is it caused by pnpm, yes, it really is. And you can see from the error, the import trick isn't working in this situation. the preserveSymlinks trick didn't help on this one either, it just moved the error around.
Can you try import {} from 'tss-react'
instead import {} from 'tss-react/types'
?
Basically, using imports outside of the root folder could make old versions of typescript behave strangely... . See https://github.com/microsoft/TypeScript/issues/33079
I see that tss-react types is defining by-folder exports, but they do not redefine the types
Well that worked, thank you. I thought that I'd tried that one, apparently not. And thanks for the explanation about what tss-react is doing, I'd no idea that that was even a thing.
Basically, using imports outside of the root folder could make typescript ... behave oddly.
hey @quadristan, im having a hard time following. can you ELI5 to help me catch up? It seemed to me that the imports were to dependencies local to the lib or apps local package.json(s) (eg workspace libs), despite being resolved deep off into the pnpm store. What am I missing? Also, hey @ggascoigne , long time no see. I hope you’re well!
When using exports
in the package.json
file, you can specify sub-paths instead of importing the whole index, or diving into the package deployed folders on your disk
A good example is rxjs
You can see that for example if you do import {something} from 'rxjs/operators'
you can see that it impacts require('rxjs/xx')
, import from'rxjs/xx'
, and it will also tell typescript where to find the types.
Doc is here
However, this feature is "quite new" ( i would have to confirm which versions brings it. i think ..4.7 ? ) If there is any issue regarding typings and importing from a sub-folder of a package that is using exports .. i simply recomand to import the root of the package.. or to upgrade TS version.
因为我没有看到它在这里列出。我遇到了同样的问题,无法从......获得“导入类型 {}”技巧工作,但添加到我的 tsconfig 编译器选项完全解决了我的问题。有关更多评论,请参阅此问题。可能相关,但我的问题是 pnpm 单存储库。
preserveSymlinks: true
same problem with using pnpm but not monorepo. "preserveSymlinks": true also worked.
Keep looking for the real problem and the better solution
Basically there are 3 issues playing a role:
*
pattern, only fully written/qualified entries reverse map properly: https://github.com/effect-ts-app/libs/blob/12a04db846d6cf5a535c811e7c60f0099e750602/patches/typescript%405.0.0-tsplus.20230113.patch#L10pnpm specific:
.pnpm
inside nodemodules can cause havoc with @types https://github.com/effect-ts-app/libs/blob/12a04db846d6cf5a535c811e7c60f0099e750602/patches/typescript%405.0.0-tsplus.20230113.patch#L20
I'm not suggesting this as a fix, but as a workaround it helped us.If no one can run with this, I hope to get some time to look at a clean patch and error report
Hey @patroza, pretty cool that you've managed to "properly" patch tsc. I was wondering if those patches are for the special tsplus typescript? Only second point mentions that it's for tsplus. Not that I know the difference between tsplus/tsc and microsoft/tsc but I wanted to apply your patch if it works properly.
Super weird bug, I've created a reproduction repo so I could test some workarounds and turned out I've manage to get it working without changing pnpm
configuration or using other hack BUT.... now I find that typescript breaks when types come from aliased files.
Repo: https://github.com/wladiston/create-t3-ts-error-repro
If you clone this repo, and change this line from instead of using ../trpc
to ~/trpc
, you'll immediately get typechecking errors on the other packages. Change any file to load from ~/
and you'll get the same error.
I though it could be because of the *
in the tsconfig or even because I use the same alias on the other packages but even though setting a specific alias to a file it gives the same kind of error.
Peeps, I've tried almost all workarounds found here without success.
However, since the error (kind of) happened "out of nowhere" today while I was building a package npm run build
AND I didn't have changed anything code-wise since the last build (which was yesterday)
EXCEPT a couple of dependency tweaks (upgrading packages), I've decided to delete my node_modules
folder and reinstall everything and the error simply disappeared. 🤷♂️
After reading this thread, I had a gut feeling that it could be actually some transient-ish dependency-ish issue. Who knows?! Fixed to me. 🙇♂️
Just a cautionary note -- preserveSymlinks: true
can be a double edged sword. I wasn't able to use it because It can cause havoc with "goto definition" in vscode. Not sure if that's a bug in vscode or tsserver.
Sometimes you'll cmd+click and end up opening a path like packages/my_module/node_modules/my_other_module/src/foo.ts
when you should have been navigated to packages/my_other_module/src/foo.ts
. When this happens tsserver and vscode go nuts with errors all over the place because they can't resolve tsconfig or other files correctly from the "fake" symlink path that got opened.
In my case, I changed to interface
instead of type
when reexporting.
I'm using monorepo with pnpm with ui
and forms
package. On ui
I was exporting the InputProps
this way:
import { type InputProps as InputPropsUi } from 'theme-ui;
export type InputProps = InputPropsUI & {
leadingIcon?: IconType;
trailingIcon?: IconType;
}
And on forms
, I was importing:
import { InputProps } from "ui";
That was causing the error:
src/FormFieldInput.tsx(5,14): error TS2742: The inferred type of 'FormFieldInput' cannot be named without a reference to '.pnpm/@theme-ui+components@0.15.7_@emotion+react@11.10.6_react@18.2.0/node_modules/@theme-ui/components'. This is likely not portable. A type annotation is necessary.
The error disappeared when I changed from type
to interface
on ui
:
import { type InputProps as InputPropsUi } from 'theme-ui;
export interface InputProps extends InputPropsUI {
leadingIcon?: IconType;
trailingIcon?: IconType;
}
We at @hokify migrated a monorepo (~170 packages) based on npm workspaces to pnpm workspaces, and we had this error message a couple of times.
I want to share some insights we gained while resolving them - hope it helps others!
I created a minimal repository showing the error https://github.com/pkerschbaum/typescript-issue-pnpm-workspaces-this-is-likely-not-portable.
The package, just called express-app
, has one dependency, express
.
It takes only three lines to get the error:
import * as express from "express";
const app = express();
// ^ The inferred type of 'app' cannot be named without a reference to '.pnpm/@types+express-serve-static-core@4.17.33/node_modules/@types/express-serve-static-core'. This is likely not portable. A type annotation is necessary.ts(2742)
export { app };
Seems like there must be some prerequisites fulfilled to get this error:
"declaration": true
in tsconfig.json, or something like "composite": true
which also enables "declaration"
)While compiling, for every .ts
source file, tsc has to create the corresponding declaration file .d.ts
. But in declaration files, type inference can technically not work; there are only types, no function bodies or assignments etc. - so where should a type even get inferred from?
Consequently, when tsc compiles the source code and wants to output the .d.ts
declaration files, then it must explicitly type the variables and functions. It must fill in the "holes" we leave when relying on type inference, so to say.
For the code above, tsc wants to create the following declaration file:
declare const app: import("express-serve-static-core").Express;
export { app };
This is because how express
is typed:
import * as core from 'express-serve-static-core';
declare function e(): core.Express;
But here's the thing: express-serve-static-core
is not a dependency of express-app
. Only express
is. So any package consuming that declaration file of express-app
might not have express-serve-static-core
installed.
Hence the error This is likely not portable
.
(the repository https://github.com/pkerschbaum/typescript-issue-pnpm-workspaces-this-is-likely-not-portable has a branch for each of these workarounds)
express-app
(GitHub compare)Just set "declaration": false
in tsconfig.json. Without declaration emit, tsc does not have to replace type inference by explicit types.
Is of course only an option if the package is not consumed by any other TS package.
import * as express from "express";
- const app = express();
+ const app: ReturnType<typeof express> = express();
export { app };
tsc will keep that explicit type in the declaration file:
import * as express from "express";
declare const app: ReturnType<typeof express>;
export { app };
Now the code only imports express
, which is a dependency of express-app
- thus it is valid and portable.
When we change the types of express
like this:
import * as core from 'express-serve-static-core';
+ export { Express } from 'express-serve-static-core';
...the code compiles!
This is because now, tsc can emit a valid declaration file:
import * as express from "express";
declare const app: express.Express;
export { app };
But I think Express
must be an interface
and not a type
for this to work, see also my comment below.
You can use pnpm patch to modify the code of dependencies.
If workaround 3.1 does not work because Express
is a type (and not an interface), you can introduce an "intermediary interface" in the types of the dependency like this:
import * as core from 'express-serve-static-core';
+ export interface MyExpress extends core.Express {}
- declare function e(): core.Express;
+ declare function e(): MyExpress;
This fixes the problem in a similar way like workaround 3.1 - the declaration then is:
import * as express from "express";
declare const app: express.MyExpress;
export { app };
We can fix compilation of express-app
by adding express-serve-static-core
to the dependencies.
Then, tsc can emit the declaration file and any package installing express-app
will also get express-serve-static-core
.
Drawback is of course that now the implementation details of express
leak into our express-app
.
In pnpm you can configure the option node-linker=hoisted
. This will put express-serve-static-core
to the root of node_modules, thus tsc will emit the declaration file
import * as core from 'express-serve-static-core';
declare function e(): core.Express;
...because it thinks this dependency is available for sure.
But that might not be the case; imagine you publish express-app
to the npm registry and someone installs it in a project using isolated mode - then express-serve-static-core
is not present and it could introduce TypeScript errors on their side!
Add this to tsconfig.json:
{
"compilerOptions": {
"paths": {
"express-serve-static-core": [
"./node_modules/.pnpm/@types+express-serve-static-core@4.17.33/node_modules/@types/express-serve-static-core"
]
}
}
}
...and an import statement to the TS source file:
import * as express from "express";
+ import "express-serve-static-core";
const app = express();
export { app };
The import statement will put express-serve-static-core
into the tsc compilation process, and thanks to the path alias it finds it.
But this suffers from the same problem as the previous workaround - the declaration file:
import * as core from 'express-serve-static-core';
declare function e(): core.Express;
...could be problematic for package consumers.
"preserveSymlinks"
in tsconfig.json (GitHub compare)tsc emits a declaration file which is inherently broken:
declare const app: core.Express; // TS error here: Cannot find namespace 'core'
export { app };
declare module
to silent the error (GitHub compare)Add a file global.d.ts
and add:
declare module "express-serve-static-core" {}
This silences the error, however the emitted declaration is broken as it is in workaround #7:
declare const app: core.Express; // TS error here: Cannot find namespace 'core'
export { app };
@pkerschbaum sadly none of these work for my use case. The best I could do is use node-linker=hoisted
, but that also requires additional work, explained further below.
I have a project structure with 3 different "lib" packages and one app package:
There's also an app that consumes the main JS bundle, and copies the web worker and the Wasm resources to a public folder.
The only thing that works is that I install all peer deps as dependencies or dev dependencies, and import an empty type from each of them. This means the Wasm, worker and main package, including @types/emscripten
and Comlink.
When an error references a package, I have to be able to do import type {} from "package";
, which fixes it. Hoisting helps insofar so that I don't need to explicitly declare peer dependencies.
Bug Report
🔎 Search Terms
🕗 Version & Regression Information
Problems occurs with 4.5.x and 4.6.x and most likely earlier versions (I've tried a few other versions). It stills occurs on
4.7.0-dev.20220321
.⏯ Playground Link
I've created minimal repository that shows the problem: https://github.com/renke/typescript-package-id-merge-repro
The problem occurs when using pnpm (due to the modules layout it uses). No problems occur when using npm/yarn.
To reproduce the problem run
pnpm install
and thenpnpm check
pnpx tsc -b
.💻 Code
I don't think the code itself matters to much except from the fact that it in fact does not have explicit type annotations (which is kind of the idea when using zod and by extension @renke/vommer).
🙁 Actual behavior
The following error occurs when trying to build a composite TypeScript project (same happens when just using
declaration: true
).The dependency tree of
@renke/vommer
Looking at the resolution trace TypeScript tries to resolve
@renke/vo
two times the first time from@renke/vommer
and the second time from@renke/vod
. Both end up having the package ID@renke/vo/dist/index.d.ts@0.2.0
.Using
"preserveSymlinks": true
doesn't solve the problem in so far that the error disappears but the type is inferred as any, because the dependencies of@renke/vommer
are not found. Also I don't actually want to use it.🙂 Expected behavior
The error should not occur when there are two (or more) modules that have the same resolved package ID. It would make sense for the error to occur when they have different versions.