Skip to content

fix(path-equal): serve CJS type declarations to CJS consumers - #248

Merged
unional merged 1 commit into
mainfrom
fix/dual-package-exports
Sep 6, 2026
Merged

fix(path-equal): serve CJS type declarations to CJS consumers#248
unional merged 1 commit into
mainfrom
fix/dual-package-exports

Conversation

@unional

@unional unional commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

A CommonJS consumer resolving this package under node16/nodenext is told it is ESM:

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 cause is the manifest, not the build

"exports": {
  "types": "./esm/index.d.ts",   ← ESM declarations, served to everyone
  "import": "./esm/index.js",
  "default": "./cjs/index.js"
}

One top-level types, and no require condition. A CJS consumer gets ./cjs/index.js for code but ./esm/index.d.ts for types — so TypeScript reads the package as ESM and refuses the require.

Nothing is wrong with the emitted output. cjs/package.json already declares {"type":"commonjs"} and cjs/index.d.ts already ships; the manifest simply never pointed at it. The two declaration files are byte-identical in content — what makes one CJS and the other ESM is the package.json beside each, which is precisely why the condition needs its own types rather than one shared entry.

This survived because moduleResolution: "node" ignores exports maps entirely. It only appears once a consumer moves to modern resolution.

The fix

"exports": {
  "require": { "types": "./cjs/index.d.ts", "default": "./cjs/index.js" },
  "import":  { "types": "./esm/index.d.ts", "default": "./esm/index.js" }
}

The top-level types also moves to ./cjs/index.d.ts so it agrees with main (./cjs/index.js) instead of contradicting it, for consumers on legacy resolution that never read exports.

Verification

Found by iso-path, which imports this package and could not adopt moduleResolution: node16 because of it. Against this manifest, iso-path builds clean — TS1479 gone. path-equal's own tests pass unchanged: 6 passed.

Patch changeset included. The sibling uni-path is ESM-only and makes no CJS claim, so it needs no equivalent change.

🤖 Generated with Claude Code

https://claude.ai/code/session_0163FWV18YYf6AVN3qAd4fYi

The `exports` map carried a single top-level `types` pointing at
`./esm/index.d.ts` and no `require` condition, so a CommonJS consumer
resolving under `node16`/`nodenext` was handed the ESM declarations and told
the package was ESM:

    TS1479: The current file is a CommonJS module whose imports will produce
    'require' calls; however, the referenced file is an ECMAScript module

Nothing was wrong with the emitted code. `cjs/package.json` already declares
`{"type":"commonjs"}` and `cjs/index.d.ts` already shipped — the manifest
just never pointed at it. The two declaration files are byte-identical in
content and differ only in the `package.json` beside them, which is exactly
why the condition needs its own `types` rather than one shared entry.

`moduleResolution: "node"` ignores `exports` maps outright, which is why this
survived: it only surfaces once a consumer moves to modern resolution.

`exports` now resolves `require` and `import` separately, each pointing at
the declarations matching its format, and the top-level `types` agrees with
`main` instead of contradicting it.

Verified against the consumer that found it: `iso-path` builds clean with
`module`/`moduleResolution: node16`, where it failed before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0163FWV18YYf6AVN3qAd4fYi
@changeset-bot

changeset-bot Bot commented Sep 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e2cb9c7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
path-equal Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@unional
unional enabled auto-merge September 6, 2026 04:59
@unional
unional added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit afc9864 Sep 6, 2026
5 checks passed
@unional
unional deleted the fix/dual-package-exports branch September 6, 2026 05:02
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