Skip to content

fix: emit CommonJS type declarations for require consumers - #24

Open
erossignon wants to merge 1 commit into
PeculiarVentures:masterfrom
erossignon:fix/cjs-type-declarations
Open

erossignon wants to merge 1 commit into
PeculiarVentures:masterfrom
erossignon:fix/cjs-type-declarations

Conversation

@erossignon

Copy link
Copy Markdown

Fixes #23.

Problem

The package root declares "type": "module", and every exports entry pointed at a single declaration set under build/types. TypeScript derives a declaration file's module format from the "type" field of its nearest package.json, not from the export condition that resolved it -- so those .d.ts files were classified as ESM even when reached through require. Any CommonJS consumer on moduleResolution: node16 got:

error TS1479: The current file is a CommonJS module whose imports will produce
'require' calls; however, the referenced file is an ECMAScript module and cannot
be imported with 'require'.

The runtime was never broken -- build/cjs loads correctly via require. Only the declarations were misclassified.

Change

  • New tsconfig.build.types.cjs.json emits a second declaration set to build/types-cjs.
  • scripts/write-cjs-package-json.mjs now writes the {"type": "commonjs"} marker to build/types-cjs as well as build/cjs, reusing the approach already in place for the JavaScript output.
  • exports selects declarations per condition (import.types / require.types).
  • files publishes the new directory and its marker.
  • The prune script skips types-cjs alongside types.

No source changes, and the ESM declarations are untouched.

Verification

npm run check passes (lint, tsc --noEmit, 31 tests).

Beyond that I packed the tarball and type-checked a real consumer against it with skipLibCheck: false, on TypeScript 5.9.3:

Consumer Baseline 2.0.3 This PR
CJS + node16, root import TS1479 pass
CJS + node16, /bytes subpath TS1479 pass
CJS + node16, /pem subpath TS1479 pass
ESM + node16, root import pass pass
ESM + node16, /bytes subpath pass pass
CJS + node10, root import (asn1-schema#130) pass pass
CJS + node10, /bytes subpath TS2307 TS2307

The last row is unchanged from baseline -- subpath exports have never been reachable under classic node resolution, which is why asn1-schema#130 was fixed by moving to root imports. I mention it only so the matrix is complete; this PR neither fixes nor worsens it.

Runtime smoke test on both formats:

require('@peculiar/utils')          -> OK
import from '@peculiar/utils' (ESM) -> OK

Context

Reached this through node-opcua-crypto, which uses the @peculiar ASN.1/X.509 stack for certificate handling. Downstream CJS projects currently need skipLibCheck: true to build. This is the node16 half of the compatibility problem reported in PeculiarVentures/asn1-schema#130.

The package root declares "type": "module", so the single declaration set
under build/types was classified as ESM regardless of which export
condition resolved it. CommonJS consumers on moduleResolution node16 got
TS1479 even though build/cjs loads fine via require.

Emit a second declaration set to build/types-cjs and mark it with a
"type": "commonjs" package.json, reusing the marker approach already used
for build/cjs. The exports map now selects declarations per condition.

Fixes PeculiarVentures#23
@donskov
donskov requested a review from microshine September 3, 2026 08:06
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.

Types are ESM-only: CJS consumers on moduleResolution node16 get TS1479

1 participant