Summary
When a consumer package depends on another package via a pnpm/npm workspace file: link, genType emits the import path for cross-package types as a relative path (./S.gen) instead of the package-qualified path (sury/src/S.gen). The relative path does not resolve — there's no S.gen.{ts,d.ts} next to the generated file — so downstream tsc fails with TS2307: Cannot find module './S.gen'.
Versions
rescript: 12.2.0 (also reproduces on 12.0.0-beta.5)
- Package manager:
pnpm@9.0.5
- Node:
22.6.0
- OS: Ubuntu 24.04
Minimal repro
Two-package pnpm workspace. lib publishes hand-written S.gen.d.ts; app imports its type via @genType.
repro/
├─ pnpm-workspace.yaml # packages: ["packages/*"]
├─ packages/
│ ├─ lib/
│ │ ├─ package.json # { "name": "lib" }
│ │ ├─ rescript.json # below
│ │ └─ src/
│ │ ├─ S.res # let string: t<string> ; type error = {…}
│ │ └─ S.gen.d.ts # hand-written: export type t<T> = …; export type error = …
│ └─ app/
│ ├─ package.json # { "dependencies": { "lib": "file:../lib" } }
│ ├─ rescript.json # below
│ └─ src/
│ └─ Demo.res # @genType let x: S.error = …
packages/lib/rescript.json:
{
"name": "lib",
"namespace": false,
"suffix": ".res.mjs",
"package-specs": { "module": "esmodule", "in-source": true },
"sources": [{ "dir": "src", "public": ["S"] }]
}
packages/app/rescript.json:
{
"name": "app",
"sources": [{ "dir": "src", "subdirs": true }],
"package-specs": { "module": "esmodule", "in-source": true },
"suffix": ".res.mjs",
"dependencies": ["lib"],
"gentypeconfig": { "generatedFileExtension": ".gen.ts" }
}
packages/app/src/Demo.res:
@genType
let err: S.error = S.Error.make(…)
Run pnpm install && pnpm --filter=app rescript build.
Actual output
packages/app/src/Demo.gen.ts:
import type {error as S_error} from './S.gen'; // ❌ relative — no such file next to Demo.gen.ts
tsc --noEmit reports:
Demo.gen.ts(…,…): error TS2307: Cannot find module './S.gen' or its corresponding type declarations.
Expected output
import type {error as S_error} from 'lib/src/S.gen';
(This was the behavior in earlier versions — the same repo has a committed Demo.gen.ts from a prior generation that used from 'lib/src/S.gen'.)
Context / investigation
app/lib/bs/.sourcedirs.json correctly lists ["lib", "/abs/path/packages/lib"] under "pkgs", so the dependency is known to the build.
- Poking at the
bsc.exe strings, the relevant logic lives in compiler/gentype/ModuleResolver.ml (resolve_module, import_path_for_reason_module_name, read_bs_dependencies_dirs) and compiler/gentype/ImportPath.ml.
- It looks like when the dep's source directory is reachable via a workspace path, the resolver adds
S to the consumer's local module_name_map and that takes precedence over the dep lookup, so the import is emitted relative.
- No
gentypeconfig option currently works around this: shims treats its value as a shim-file path (emits ./lib/src/S.gen.shim), and there's no packageName / importPath / modulesMap option documented in the bsc binary.
Workaround
Ship a shim next to the generated file that re-exports from the dep:
// packages/app/src/S.gen.ts
export type { error, t } from "lib/src/S.gen";
This keeps the relative ./S.gen import resolving, but is obviously a workaround.
Does it reproduce outside file: deps?
Not when lib is a real npm dependency (resolved under node_modules/lib) — that case emits 'lib/src/S.gen' correctly. Only workspace / file: installs hit this.
Summary
When a consumer package depends on another package via a pnpm/npm workspace
file:link,genTypeemits the import path for cross-package types as a relative path (./S.gen) instead of the package-qualified path (sury/src/S.gen). The relative path does not resolve — there's noS.gen.{ts,d.ts}next to the generated file — so downstreamtscfails withTS2307: Cannot find module './S.gen'.Versions
rescript:12.2.0(also reproduces on12.0.0-beta.5)pnpm@9.0.522.6.0Minimal repro
Two-package pnpm workspace.
libpublishes hand-writtenS.gen.d.ts;appimports its type via@genType.packages/lib/rescript.json:{ "name": "lib", "namespace": false, "suffix": ".res.mjs", "package-specs": { "module": "esmodule", "in-source": true }, "sources": [{ "dir": "src", "public": ["S"] }] }packages/app/rescript.json:{ "name": "app", "sources": [{ "dir": "src", "subdirs": true }], "package-specs": { "module": "esmodule", "in-source": true }, "suffix": ".res.mjs", "dependencies": ["lib"], "gentypeconfig": { "generatedFileExtension": ".gen.ts" } }packages/app/src/Demo.res:Run
pnpm install && pnpm --filter=app rescript build.Actual output
packages/app/src/Demo.gen.ts:tsc --noEmitreports:Expected output
(This was the behavior in earlier versions — the same repo has a committed
Demo.gen.tsfrom a prior generation that usedfrom 'lib/src/S.gen'.)Context / investigation
app/lib/bs/.sourcedirs.jsoncorrectly lists["lib", "/abs/path/packages/lib"]under"pkgs", so the dependency is known to the build.bsc.exestrings, the relevant logic lives incompiler/gentype/ModuleResolver.ml(resolve_module,import_path_for_reason_module_name,read_bs_dependencies_dirs) andcompiler/gentype/ImportPath.ml.Sto the consumer's localmodule_name_mapand that takes precedence over the dep lookup, so the import is emitted relative.gentypeconfigoption currently works around this:shimstreats its value as a shim-file path (emits./lib/src/S.gen.shim), and there's nopackageName/importPath/modulesMapoption documented in the bsc binary.Workaround
Ship a shim next to the generated file that re-exports from the dep:
This keeps the relative
./S.genimport resolving, but is obviously a workaround.Does it reproduce outside
file:deps?Not when
libis a real npm dependency (resolved undernode_modules/lib) — that case emits'lib/src/S.gen'correctly. Only workspace /file:installs hit this.