Skip to content

feat(dts): add cjsReexport option to eliminate dual module type hazard#856

Open
mandarini wants to merge 2 commits intorolldown:mainfrom
mandarini:feat/reexport-cjs
Open

feat(dts): add cjsReexport option to eliminate dual module type hazard#856
mandarini wants to merge 2 commits intorolldown:mainfrom
mandarini:feat/reexport-cjs

Conversation

@mandarini
Copy link

Description

When building dual ESM+CJS formats, tsdown runs two separate TypeScript compilation passes, one producing .d.mts, one producing an independent .d.cts. Because TypeScript's class identity is nominal, these two declarations are treated as unrelated types by consumers that mix CJS and ESM resolution paths (e.g. NestJS apps importing a package that also has ESM-only internal dependencies). This produces TS2352 "neither type sufficiently overlaps" when casting between types derived from the same class.

This PR adds a dts.cjsReexport: boolean option (opt-in, default false). When enabled in a dual-format build, tsdown skips the second compilation pass and writes a .d.cts stub that re-exports from the corresponding .d.mts:

  // dist/index.d.cts (generated)
  export * from './index.d.mts'                                                     

Both CJS and ESM consumers now resolve to the same declaration, eliminating the dual module type hazard entirely.

Note: export * does not re-export default. Packages with a default export and named exports should be aware of this — for named-export-only packages this is a`non-issue.

Additional info

This PR contains AI-generated code, but I have carefully reviewed it myself. We use tsdown on https://github.com/supabase/supabase-js and it would be nice if we could use this.

@netlify
Copy link

netlify bot commented Mar 23, 2026

Deploy Preview for tsdown-main ready!

Name Link
🔨 Latest commit d4bb391
🔍 Latest deploy log https://app.netlify.com/projects/tsdown-main/deploys/69c15f3273b5c20009c68d7e
😎 Deploy Preview https://deploy-preview-856--tsdown-main.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 23, 2026

Open in StackBlitz

tsdown

pnpm add https://pkg.pr.new/tsdown@856 -D
npm i https://pkg.pr.new/tsdown@856 -D
yarn add https://pkg.pr.new/tsdown@856.tgz -D

create-tsdown

pnpm add https://pkg.pr.new/create-tsdown@856 -D
npm i https://pkg.pr.new/create-tsdown@856 -D
yarn add https://pkg.pr.new/create-tsdown@856.tgz -D

@tsdown/css

pnpm add https://pkg.pr.new/@tsdown/css@856 -D
npm i https://pkg.pr.new/@tsdown/css@856 -D
yarn add https://pkg.pr.new/@tsdown/css@856.tgz -D

@tsdown/exe

pnpm add https://pkg.pr.new/@tsdown/exe@856 -D
npm i https://pkg.pr.new/@tsdown/exe@856 -D
yarn add https://pkg.pr.new/@tsdown/exe@856.tgz -D

tsdown-migrate

pnpm add https://pkg.pr.new/tsdown-migrate@856 -D
npm i https://pkg.pr.new/tsdown-migrate@856 -D
yarn add https://pkg.pr.new/tsdown-migrate@856.tgz -D

commit: d4bb391

@sxzz
Copy link
Member

sxzz commented Mar 25, 2026

Thanks for the PR! The use case is valid — the dual module type hazard is a real pain point.

However, the current implementation writes the .d.cts stub directly to the filesystem via fs.writeFile. This should be implemented as a Rolldown plugin instead (e.g. using the generateBundle hook to emit the reexport stub), so it integrates properly with the build pipeline and respects the write option naturally.

Could you refactor the approach to use a Rolldown plugin?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants