Repository navigation
Module "..." cannot be named without a reference to "..." error when decl emitting references to nested modulesΒ #48212
Description
Activity
- addedBugA bug in TypeScriptA bug in TypeScriptDomain: Declaration EmitThe issue relates to the emission of d.ts filesThe issue relates to the emission of d.ts files
on Mar 10, 2022 This seems to be related to the issue I've opened earlier here: #47663
Althought I don't know why ,
"preserveSymlinks": trueresolved my problemsReacted by Ttou, Caleb Bergman, Mohsen Azimi, Arnab Tarwani, Leo Aso and Gao PengReacted by Lex and piscopancerI think
preserveSymlinkscan sometimes solve this (or at least similar problems) but that's not really solution to the underlying problem.This error is also not suppressible through
@ts-expect-error. It will just complainUnused '@ts-expect-error' directive.Thanks for scheduling this for 4.8! I'm looking forward to tracking the fix. Would it be possible to get some insight from language designers on what this is trying to protect us from[1]? Understanding this or having some clue about the recommended mitigation while we await upstream fix would be helpful. Here's a dump of what I've found so far.
A note, the problem I'm seeing might not be representative of the full presentation of this error. To provide some context, our use case similar to pnpm's where the real path of
node_moduleslives outside of the project root (we create carefully sandboxed roots for each build action to ensure we have a clean build graph), and the main area I'm seeing this error is in React code likeexport const A = forwardRef<HTMLElement, B>(...)whereBis in a third-party package whose definition relies on a transitive depC.For reference, the type definition of
@types/[email protected]and@types/[email protected]forwardRefis:function forwardRef<T, P = {}>(render: ForwardRefRenderFunction<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;Our code looks like:
import * as DropdownMenu from `@radix-ui/react-dropdown-menu`; export const Separator = forwardRef<HTMLDivElement, DropdownMenu.MenuSeparatorProps>(...);And
@radix-ui/react-dropdown-menucode is:import * as MenuPrimitive from "@radix-ui/react-menu"; type MenuSeparatorProps = Radix.ComponentPropsWithoutRef<typeof MenuPrimitive.Separator>; export interface DropdownMenuSeparatorProps extends MenuSeparatorProps { } export const DropdownMenuSeparator: React.ForwardRefExoticComponent<DropdownMenuSeparatorProps & React.RefAttributes<HTMLDivElement>>; tUsing traditional
node_moduleslinking, TS expands the generic arguments fully when generating the type declaration from our code. Turning on our pnpm-like linking method causes TS to fail to compile this file.import type { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes } from 'react'; export declare const Separator: import("react").ForwardRefExoticComponent<Pick<import("@radix-ui/react-menu").MenuSeparatorProps & import("react").RefAttributes<HTMLDivElement>, "className" | "children" | "..."> & import("react").RefAttributes<HTMLDivElement>>;To begin, this seems like problematic behavior because only
@radix-ui/react-dropdown-menuand not@radix-ui/react-menuis a direct dep of our code, so it's not guaranteed thatimport("@radix-ui/react-menu")resolves to the correct version when resolved from our code. I don't know if this is a configuration error on our part, a bug in TS, or some compromise to make the ecosystem work. Naively, I'd expect the generated type to look something like this:import type { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes } from 'react'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; export declare const Separator: ForwardRefExoticComponent<PropsWithoutRef<DropdownMenu.MenuSeparatorProps> & RefAttributes<HTMLDivElement>>;In fact, around 50% of exported types already look like this, but not all, for reasons I don't understand yet:
export declare const Content: import("react").ForwardRefExoticComponent<DropdownMenuProps & import("react").RefAttributes<HTMLDivElement>>; export declare const Trigger: import("react").ForwardRefExoticComponent<DropdownMenu.DropdownMenuTriggerProps & import("react").RefAttributes<HTMLButtonElement>>; export declare const Item: import("react").ForwardRefExoticComponent<DropdownMenu.DropdownMenuItemProps & import("react").RefAttributes<HTMLDivElement>>; export declare const Label: import("react").ForwardRefExoticComponent<DropdownMenu.DropdownMenuLabelProps & import("react").RefAttributes<HTMLDivElement>>; export declare const Separator: import("react").ForwardRefExoticComponent<Pick<import("@radix-ui/react-menu").MenuSeparatorProps & import("react").RefAttributes<HTMLDivElement>, "className" | "children" | "slot" | "style" | "title" | "key" | "color" | "translate" | "hidden" | "id" | "dir" | "accessKey" | "draggable" | "lang" | "prefix" | "contentEditable" | "inputMode" | "tabIndex" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "contextMenu" | "placeholder" | "spellCheck" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "asChild"> & import("react").RefAttributes<HTMLDivElement>>; export declare const TriggerItem: import("react").ForwardRefExoticComponent<DropdownMenu.DropdownMenuTriggerItemProps & import("react").RefAttributes<HTMLDivElement>>; export declare const CheckboxItem: import("react").ForwardRefExoticComponent<Pick<import("@radix-ui/react-menu").MenuCheckboxItemProps & import("react").RefAttributes<HTMLDivElement>, "className" | "children" | "slot" | "style" | "title" | "key" | "color" | "translate" | "hidden" | "disabled" | "id" | "dir" | "accessKey" | "draggable" | "lang" | "prefix" | "contentEditable" | "inputMode" | "tabIndex" | "checked" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "contextMenu" | "placeholder" | "spellCheck" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "asChild" | "textValue" | "onCheckedChange"> & import("react").RefAttributes<HTMLDivElement>>;I have so far found 2 partial workarounds that sometimes work:
- Add an explicit type to the exported member. This isn't always possible because declaring the type correctly can sometimes require using non-exported types from the API whose type is inferred. But for our use case, this is possible, if verbose. This causes the exported declarations to match my expectation, which is to use the immediate dep
@radix-ui/react-dropdown-menuand never try to import the transitive dep@radix-ui/react-menu. - Add a useless
import type {} from 'module-name'for the module that TS is complaining about. This isn't always possible because the module may not be a direct dependency of the code being compiled, and if it's a transitive dep, importing it directly could fail outright or technically resolve to the wrong version. This appears to cause TS to happily generate the code we saw during traditional nm linking, transitive dep import and everything.
Between the two, the first option seems better, even if it's technically different behavior, but I'd obviously like to make sure I'm not shooting myself in the foot somehow. Thanks!
[1] - My guess looking at the behavior and some past analysis is that it's trying to avoid unnamed deps that are technically resolveable at build time but are not distributed in a proper way that would be available to downstream users e.g. from
node_modulesin a user's home directory.Reacted by Shawn McKnight, Chriest Yu, Dennis Miasoutov and piscopancer- Add an explicit type to the exported member. This isn't always possible because declaring the type correctly can sometimes require using non-exported types from the API whose type is inferred. But for our use case, this is possible, if verbose. This causes the exported declarations to match my expectation, which is to use the immediate dep
RyanCavanaugh commented
on Aug 4, 2022 MemberAuthorMore actionsWould it be possible to get some insight from language designers on what this is trying to protect us from
You get this error any time the declaration emitter can't synthesize a workable specifier for a module which it needs to name a type from. For example, if it appears that the only legal path is
../../other_module/foovia some file that's in<<outDir>>/whatever, then that's not likely to work because the disk layout of the produced artifacts don't really have implicit dependencies on what peer directories of the output directory have.The logic to synthesize these specifiers starts with the easy route of "Has this already been imported?", in which case re-use is easy and fine. Immediately past that lie many dragons and it's easy to get into a novel corner case where there is a speakable name to a module but TS just can't figure it out. Adding the import yourself is the easiest way to resolve the situation.
This isn't always possible because the module may not be a direct dependency of the code being compiled, and if it's a transitive dep, importing it directly could fail outright or technically resolve to the wrong version.
Note that if this isn't possible, then the error is correct and working around it by manually adding an import you know to be invalid is, well, invalid.
Reacted by stevenxu-db, Tingan Ho and Jiri DanΔkReacted by LexSame problem
saiichihashimoto commented
on Sep 2, 2022 More actionsWe're also running into this with saiichihashimoto/sanity-typed-schema-builder#155. It's unclear what should happen here, considering transitive type dependencies should work.
Reacted by Greg McKelvey, Fabian Eichenberger and Artiom NeganovI've made a smaller reproduction here: #47663 (comment)
Hope its helpful
1 remaining item
- addedRescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Feb 1, 2023 I got this when upgrading from 4.9.5 to 5.0.2 with no other changes.
using
pnpmin monorepoReacted by Michael Loughry, kondei, Julien Goux, Christopher Yovanovitch, erhise and piscopancerI started seeing this after upgrading from 4.9 to 5.0, but only for one dependency, and only when building everything at once (rather than using project references), and only when using
"resolvePackageJsonExports": trueSame problem
This isn't always possible because the module may not be a direct dependency of the code being compiled, and if it's a transitive dep, importing it directly could fail outright or technically resolve to the wrong version.
Note that if this isn't possible, then the error is correct and working around it by manually adding an import you know to be invalid is, well, invalid.
After reading the comments above and this comment (#58176 (comment)), I'm left with some questions, probably due to the fact I'm not very familiar with how TypeScript emits declarations, so I'll write down some of the thoughts I have to see if I'm understanding the problem correctly.
Let's say we have 3 packages:
app,middle-libraryandbase-libraryand that the dependency tree is as follows:app | ---> middle-library @^1.0.0 | ----> base-library @^1.4.5So, if
middle-libraryuses types provided bybase-library, the declaration files emitted by tsc formiddle-librarywill look something like this:// middle-library/dist/index.d.ts import type { ... } from "base-library/types"; // more declarations that use base-library's exported types go here...
Note that the types imported from
base-libraryare not re-exported, unless the author ofmiddle-librarydoes that explicitly, which is expected.Now, since
apphasmiddle-libraryas a direct dependency, it can import the types exported bymiddle-library. When TypeScript is resolving those types, it finds the imports frombase-libraryand tries to resolve them. However, sincebase-libraryis not a direct dependency ofapp, TypeScript fails to resolve the import.There are a few workarounds, listed in #47663 (comment), however, each has its own issues:
-
Disable declaration emit for
app- If
appis in a monorepo with Project References, then it must havecomposite: trueand declaration emit cannot be turned off.
- If
-
Explicitly type the variable/function in
app- Although this might be possible/feasible in many situations, it can lead to a poor DX for the developer of
app. This is because some libraries use very complex types, which are cumbersome to type by hand. Aditionally, this issue sometimes happens in libraries that make use of type inference and, therefore, typing the return type by hand goes against the purpose of making the return type inferable.
- Although this might be possible/feasible in many situations, it can lead to a poor DX for the developer of
-
Within the types of the
middle-library, export the interface/type which is required inappor introduce an "intermediary interface" in the types of themiddle-library-
In my opinion, this is the best workaround, however, currently, I don't know of any way for the developer of
middle-libraryto know which types need to be reexported. An option to enforce such a constraint was already "proposed" (first sentence in the following comment, Check nearest package.json dependencies for possible package names for specifier candidatesΒ #58176 (comment)), and, at least from my point-of-view, I can see why such an option would be helpful (Check nearest package.json dependencies for possible package names for specifier candidatesΒ #58176 (comment)). Currently, since such an option does not exist, if the developer ofmiddle-libraryreally wants to be sure that the developers ofappdon't experience this issue, thenmiddle-libraryneeds to reexport all of the type imports it uses frombase-library. As such, without such an option implemented in tsc, this approach is cumbersome for the developers ofmiddle-libraryand hard to enforce in a CI, for instance. Also, sometimes, the developers ofmiddle-librarymight forget to reexport a new type that they have started importing frombase-library, which will then breakapp.However, even if
middle-libraryreexports all of the types it imports frombase-library, there's still another problem, which is outlined in Check nearest package.json dependencies for possible package names for specifier candidatesΒ #58176 (comment).But TypeScript favors
import('sub_dep').Optionsoverimport('other_package').Optionseven ifother_packageexplicitly exportsOptions.This means that even if
middle-libraryreexports all of the types it imports frombase-library, the type declarations emitted when buildingappwill have imports that are not resolvable.
-
-
Add the missing transitive dependency to the direct dependencies
- I'm not really knowledgeable enough about how TypeScript handles version mismatches, but what I'm guessing could happen is that
appcould installbase-libraryas a dependency, which could install[email protected]. This could be an issue since the types exported bymiddle-libraryexpect types frombase-library@^1.4.5, a different major version. Again, I want to emphasize that I'm not sure if this is what really happens, and would really appreciate it if someone could correct me! Either way, in my opinion, installing a transitive dependency as a direct dependency would be problematic because what happens ifmiddle-librarydeclares a new dependency on another package? Then, suddenly, all of the dependents ofmiddle-libraryalso need to install it as a direct dependency. Otherwise, they risk getting declaration emit errors like this one. Doesn't this mean that what could otherwise be a minor or patch change formiddle-librarymight instead become a breaking change?
- I'm not really knowledgeable enough about how TypeScript handles version mismatches, but what I'm guessing could happen is that
Now, from what I understand, as explained above, there are no perfect workarounds for this problem and a more in-depth solution from tsc might be required.
I think an option to let library maintainers know what types they need to reexport is viable, but this would also need to take into account the package exports. I can also see TypeScript generating this "bulk type export" file automatically so it becomes a "standard".
I've also though of another approach that I would like to get your opinions on: since this is specific to .d.ts files, I'm guessing the import doesn't need to be accessible at runtime, since it is only resolved by TypeScript. Therefore, TypeScript could have a specific dependency specifier for transitive dependencies, something like this:
// app/dist/index.d.ts import type { ... } from "middle-library::base-library/types";
This dependency specifier would be automatically created by TypeScript when a type from a transitive dependency is used. I think this solution would be the simplest for library authors and application developers to adopt, since everything is determined automatically by TypeScript. On the other hand, this would require changes to the current resolution mechanism, I'm guessing.
Finally, this was a very long message and I probably wrote a lot of stuff that's just wrong, so I'd appreciate it if you could correct me. Also, thank you for reading, this is an issue that I'm facing right now and the only way for me to work around it is to patch ~8 packages that I'm using, so... yeah, that's not very viable and I'm trying to understand what could be done to improve TypeScript's capabilities in this domain.
Reacted by jasontlouroReacted by Sam A. Horvath-Hunt-
Let's say we have 3 packages:
app,middle-libraryandbase-libraryand that the dependency tree is as follows:This is exactly the situation I'm in, AndrΓ© Lima (@limwa), using Turborepo. Except that I've made sure that
base-libraryis installed as a direct dependency inmiddle-libraryandapp. And since they're symlinked there's no version mismatch. Still the same error, it's never-ending. Did you come across any more elegant solution? Or have any idea why this would be happening even withbase-librarybeing installed everywhere?Edit: wow, I literally just found it. I needed to make sure that all my types in
base-librarywere exported from the entry points of the library (in my case,src/lib/**/index.ts)Reacted by piscopanceri have the same issue in my turborepo + tsup project
packages trpc (builds fine, exports types for trpc client, prisma client to be used by studio) studio (unable it build it, uses local trpc package, @trpc/react-query and @aws-sdk/client-s3)building studio fails, for example this hook cannot be decomposed to DTS
export function useParsedFieldQuery<S extends z.ZodType>({ documentId, path, shape }: { documentId: string; path: string; shape: S }) { return trpc.field.find.useQuery( { documentId, path: path, }, { select(data) { if (data) { const res = shape.safeParse(data.value) if (res.success) { return { value: res.data, } } else { return { value: data.value, errors: res.error.issues.map((i) => i.message), } } } }, } ) }
as it throws
C:/dev/web/jalyk/packages/studio/src/field.ts (29,17): The inferred type of 'useParsedFieldQuery' cannot be named without a reference to '../node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs'. This is likely not portable. A type annotation is necessary.which can be fixed as i prefix this hook with this quirky stuff
import * as T from '../node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs' type _ = typeof T
and it's so weird I have to do it. That cannot be the right way. I cannot see why people recommend monorepos if such error is common - people know the nature of this problem but cannot come up with a solid solution as if the symptom was too individual like we are not building repos the same way or not using hyped react libs like zod, react-query or s3 client that cause this issue with DTS:
C:/dev/web/jalyk/packages/studio/src/trpc.ts (4,14): The inferred type of 'trpc' cannot be named without a reference to '../node_modules/zod/dist/types/v4/core/util'. This is likely not portable. A type annotation is necessary.
Bug Report
π Search Terms
cannot be named without a reference to symlink
π Version & Regression Information
β― Playground Link
N/A
π» Code
https://github.com/jcreamer898/monorepo-examples/tree/main/pnpm-example
TL;DR file layout:
@fluentui/reactis nested in/monorepo-examples/pnpm-example/node_modules/.pnpm/@fluentui+reactAdding a blank import to
@fluentui/merge-stylesinindex.tsmakes the problem go awayπ Actual behavior
π Expected behavior
No error