Skip to content

Add exports map with ESM-flavoured type declarations for node16/nodenext - #1525

Open
markedwards wants to merge 1 commit into
KevinVandy:v3from
markedwards:fix/add-exports-map
Open

Add exports map with ESM-flavoured type declarations for node16/nodenext#1525
markedwards wants to merge 1 commit into
KevinVandy:v3from
markedwards:fix/add-exports-map

Conversation

@markedwards

@markedwards markedwards commented Jul 31, 2026

Copy link
Copy Markdown

Problem

Under TypeScript's moduleResolution: node16 / nodenext, material-react-table resolves to the wrong type shape. Types-only cause: the package has no exports field, and its single dist/index.d.ts is read as CommonJS when the package has no "type": "module" — regardless of which condition resolved it — so ESM consumers get CommonJS-shaped declarations and named imports/types resolve incorrectly.

Fix

Add an exports map that routes types per condition, and emit an ESM-flavoured dist/index.d.mts. Runtime resolution is deliberately unchanged:

"exports": {
  ".": {
    "module":  "./dist/index.esm.js",                                  // bundlers → ESM (matches the existing `module` field)
    "import":  { "types": "./dist/index.d.mts", "default": "./dist/index.js" },
    "require": { "types": "./dist/index.d.ts",  "default": "./dist/index.js" }
  },
  "./*": "./*"
}
  • Node's import and require both resolve to the CJS build (dist/index.js) — exactly what main resolved to before, so there is no runtime change for Node consumers.
  • The module condition (honoured by webpack/Vite/etc., ignored by Node) keeps the tree-shakeable ESM build for bundlers, matching the pre-existing module field. Bundlers that don't honour it fall back to import → CJS.

dist/index.d.mts is produced as a second output of the existing rollup-plugin-dts step — identical bundled declarations, no copy step, no new dependency. main/module/typings are retained and "./*" preserves deep imports (e.g. locales), so the change is additive.

Compatibility — please review before choosing a release type

Introducing an exports map restricts the package to its declared entry points. "./*": "./*" preserves arbitrary subpaths (dist/*, src/*, package.json, and any file imported with an extension), so the common surfaces are unaffected. One raw-Node pattern does change:

  • require('material-react-table/locales/<lang>') resolved to locales/<lang>/index.js before (CJS directory resolution via each locale's own package.json); with the exports map it becomes ERR_PACKAGE_PATH_NOT_EXPORTED, because exports patterns don't perform directory/index resolution. Bundler consumers (webpack/Vite/Next/etc.) are unaffected — they directory-resolve the subpath themselves — and ESM import of an extensionless locale subpath was already unsupported. So this only affects code doing a raw-Node require() of a locale directory.

If you'd like to preserve that path (and keep this non-breaking), add a locale subpath entry — verified to restore both require and import of locales/<lang> to the pre-exports target:

"./locales/*": {
  "module":  "./locales/*/index.esm.js",
  "import":  { "types": "./locales/*/index.esm.d.ts", "default": "./locales/*/index.js" },
  "require": { "types": "./locales/*/index.d.ts",  "default": "./locales/*/index.js" }
}

I left it out of this PR to keep it focused on the type-resolution fix — happy to add it if you'd prefer. Either way, the semver/release decision is yours.

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

@markedwards is attempting to deploy a commit to the Kevin Vandy OSS Team on Vercel.

A member of the Team first needs to authorize it.

`moduleResolution: node16`/`nodenext` consumers currently read
`dist/index.d.ts` in a CommonJS context, so named imports/types resolve
incorrectly.

Add an `exports` map that routes types per condition: `import` → a new
`dist/index.d.mts` (read as ESM), `require` → the existing
`dist/index.d.ts`. Runtime resolution is unchanged — both `import` and
`require` `default` point at the existing CJS build (`dist/index.js`),
and a `"module"` condition points at the ESM build so bundlers keep the
tree-shakeable output (matching the existing `module` field; Node
ignores this condition). The `.d.mts` is a second output of the existing
`rollup-plugin-dts` step — no copy step, no new dependency. `"./*"`
preserves deep imports (e.g. `locales`).
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