[GH-ISSUE #504] TypeScript NodeNext resolution fails with extensionless re-exports #900

Closed
opened 2026-03-14 08:58:58 +03:00 by kerem · 5 comments
Owner

Originally created by @schickling on GitHub (Jan 9, 2026).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/504

Summary

TypeScript NodeNext module resolution can’t resolve OpenTUI’s extensionless re-exports from its declaration files, which hides exported APIs like createCliRenderer and createRoot.

Details

  • @opentui/core index.d.ts contains export * from "./renderer"
  • @opentui/react src/index.d.ts contains export * from "./reconciler/renderer"

With TS 5.9 and moduleResolution: "NodeNext", tsc --traceResolution reports “Module name './renderer' was not resolved” even though renderer.d.ts exists. This results in TS2305 (“no exported member”).

Repro

// tsconfig.json
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}
import { createCliRenderer } from '@opentui/core'
import { createRoot } from '@opentui/react'

Workaround

Use moduleResolution: "Bundler" in the consumer tsconfig so TS accepts extensionless re-exports.

Request

Consider adding explicit extensions in declaration re-exports (e.g. ./renderer.js / ./reconciler/renderer.js) or adjusting the package exports so NodeNext resolution works without the Bundler override.


Opened on behalf of @schickling via Codex CLI.

Originally created by @schickling on GitHub (Jan 9, 2026). Original GitHub issue: https://github.com/anomalyco/opentui/issues/504 ### Summary TypeScript NodeNext module resolution can’t resolve OpenTUI’s extensionless re-exports from its declaration files, which hides exported APIs like `createCliRenderer` and `createRoot`. ### Details - @opentui/core `index.d.ts` contains `export * from "./renderer"` - @opentui/react `src/index.d.ts` contains `export * from "./reconciler/renderer"` With TS 5.9 and `moduleResolution: "NodeNext"`, `tsc --traceResolution` reports “Module name './renderer' was not resolved” even though `renderer.d.ts` exists. This results in TS2305 (“no exported member”). ### Repro ```json // tsconfig.json { "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext" } } ``` ```ts import { createCliRenderer } from '@opentui/core' import { createRoot } from '@opentui/react' ``` ### Workaround Use `moduleResolution: "Bundler"` in the consumer tsconfig so TS accepts extensionless re-exports. ### Request Consider adding explicit extensions in declaration re-exports (e.g. `./renderer.js` / `./reconciler/renderer.js`) or adjusting the package `exports` so NodeNext resolution works without the Bundler override. --- _Opened on behalf of @schickling via Codex CLI._
kerem 2026-03-14 08:58:58 +03:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@remorses commented on GitHub (Jan 9, 2026):

Simply add in tsconfig.json skipLibCheck: true.

<!-- gh-comment-id:3729457154 --> @remorses commented on GitHub (Jan 9, 2026): Simply add in tsconfig.json `skipLibCheck: true`.
Author
Owner

@schickling commented on GitHub (Feb 1, 2026):

Unfortunately skipLibCheck: true doesn't solve this issue. The problem is that TypeScript still needs to resolve module exports to understand what the library provides - skipLibCheck only skips type checking inside .d.ts files, not the resolution of re-exports.

Minimal repro demonstrating this: https://github.com/schickling-repros/opentui-nodenext-issue

With skipLibCheck: true enabled, you still get:

index.ts(1,10): error TS2305: Module '"@opentui/core"' has no exported member 'Box'.
index.ts(2,10): error TS2305: Module '"@opentui/react"' has no exported member 'render'.

The fix needs to be in the library itself - adding explicit .js extensions to the re-exports in the .d.ts files.

<!-- gh-comment-id:3830837063 --> @schickling commented on GitHub (Feb 1, 2026): Unfortunately `skipLibCheck: true` doesn't solve this issue. The problem is that TypeScript still needs to resolve module exports to understand what the library provides - `skipLibCheck` only skips type checking *inside* `.d.ts` files, not the resolution of re-exports. Minimal repro demonstrating this: https://github.com/schickling-repros/opentui-nodenext-issue With `skipLibCheck: true` enabled, you still get: ``` index.ts(1,10): error TS2305: Module '"@opentui/core"' has no exported member 'Box'. index.ts(2,10): error TS2305: Module '"@opentui/react"' has no exported member 'render'. ``` The fix needs to be in the library itself - adding explicit `.js` extensions to the re-exports in the `.d.ts` files.
Author
Owner

@schickling commented on GitHub (Feb 1, 2026):

Submitted a fix: #614

The PR adds .js extensions to all relative imports/exports in the .d.ts files, which allows TypeScript's moduleResolution: "NodeNext" to resolve the exports correctly.

<!-- gh-comment-id:3830873769 --> @schickling commented on GitHub (Feb 1, 2026): Submitted a fix: #614 The PR adds `.js` extensions to all relative imports/exports in the `.d.ts` files, which allows TypeScript's `moduleResolution: "NodeNext"` to resolve the exports correctly.
Author
Owner

@remorses commented on GitHub (Feb 1, 2026):

What are you trying to accomplish? Opentui does not support Node.js so you would still be unable to use it. Instead you could switch to Bundler resolver

<!-- gh-comment-id:3830921309 --> @remorses commented on GitHub (Feb 1, 2026): What are you trying to accomplish? Opentui does not support Node.js so you would still be unable to use it. Instead you could switch to Bundler resolver
Author
Owner

@schickling commented on GitHub (Feb 1, 2026):

Good question! We do use OpenTUI with Bun at runtime. The module: "NodeNext" / moduleResolution: "NodeNext" configuration is orthogonal to the runtime — it is the strictest TypeScript module resolution mode and we use it as a best practice across all our packages regardless of whether the code runs on Node.js or Bun. It ensures correct ESM semantics and catches module resolution issues at compile time.

Switching to "Bundler" would work around this, but it is a less strict mode that can mask real issues, so we would prefer not to relax our tsconfig for a single dependency. The fix in #614 is small (just adding .js extensions to the re-exports in .d.ts files) and would make OpenTUI compatible with all resolution modes.

<!-- gh-comment-id:3830985695 --> @schickling commented on GitHub (Feb 1, 2026): Good question! We do use OpenTUI with Bun at runtime. The `module: "NodeNext"` / `moduleResolution: "NodeNext"` configuration is orthogonal to the runtime — it is the strictest TypeScript module resolution mode and we use it as a best practice across all our packages regardless of whether the code runs on Node.js or Bun. It ensures correct ESM semantics and catches module resolution issues at compile time. Switching to `"Bundler"` would work around this, but it is a less strict mode that can mask real issues, so we would prefer not to relax our tsconfig for a single dependency. The fix in #614 is small (just adding `.js` extensions to the re-exports in `.d.ts` files) and would make OpenTUI compatible with all resolution modes.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#900
No description provided.