Skip to content

fix: use ESM specifiers for NodeNext module resolution#194

Draft
LordCoughmann wants to merge 3 commits into
cloudflare:mainfrom
LordCoughmann:fix/esm-type-resolution
Draft

fix: use ESM specifiers for NodeNext module resolution#194
LordCoughmann wants to merge 3 commits into
cloudflare:mainfrom
LordCoughmann:fix/esm-type-resolution

Conversation

@LordCoughmann

@LordCoughmann LordCoughmann commented May 28, 2026

Copy link
Copy Markdown

Problem

Extension-less relative imports in the package declaration files fail with moduleResolution: NodeNext or Node16:

index.ts(1,15): error TS2614: Module '"@cloudflare/playwright"' has no exported member 'Browser'.
index.d.ts(2,30): error TS2834: Relative import paths need explicit file extensions...

Reproduction: https://github.com/LordCoughmann/repo-cf-playwright-esm-nodenext

pnpm install
pnpm tsc --noEmit

Solution

Use explicit ESM .js specifiers in declaration files. TypeScript's NodeNext resolver maps these specifiers to the corresponding .d.ts files, while .d.ts specifiers themselves are rejected for value imports and exports with TS2846.

  • Update the hand-written declaration files to use .js specifiers.
  • Post-process generated declarations to add .js extensions to relative from, declare module, and dynamic import() specifiers.
  • Keep an assertion that fails if an extension-less relative specifier remains.
  • Declare the package's default export as an ambient value so strict consumers can type-check without skipLibCheck.
  • Add a strict NodeNext consumer fixture covering the package root entry point.

Validation

npm run build
npm run test:node-types

Both pass with moduleResolution: NodeNext and skipLibCheck: false.

Extension-less imports in declaration files fail with moduleResolution:
NodeNext, causing TypeScript to report:

  Module '"@cloudflare/playwright"' has no exported member 'Browser'.

Fix all declaration files:
- Hand-written: index.d.ts, test.d.ts, internal.d.ts
- Generated: add post-processing step in copy_types.js to rewrite
  relative specifiers with .d.ts extensions, with assertion guard
- Package exports: add ./types/types entry in package.json
@LordCoughmann
LordCoughmann force-pushed the fix/esm-type-resolution branch from 6959dfe to bc289eb Compare June 5, 2026 07:38
@LordCoughmann LordCoughmann changed the title fix: add .d.ts extensions for NodeNext module resolution fix: use ESM specifiers for NodeNext module resolution Jul 27, 2026
@LordCoughmann

Copy link
Copy Markdown
Author

While validating the published package shape, we noticed a separate issue: strict TypeScript consumers importing @cloudflare/playwright/test may need a playwright-core type dependency because types/test.d.ts contains typeof import('playwright-core'). This does not affect runtime imports or the package-root NodeNext fix, so we left it out of this PR. If the /test entry point should also be self-contained for strict consumers, we would be happy to discuss a follow-up.

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.

1 participant