Without a modern exports field, Node.js 16+ consumers get inconsistent resolution across bundlers and runtimes. Tools like webpack 5 and Vite treat the exports map as authoritative — packages that rely on legacy main/module fields alone can fail outright under strict resolution or expose internal files via unguarded deep imports. ISO 25010 modifiability and interoperability both degrade when entry points are implicit rather than declared. The practical result: import errors in production that only surface at the consumer's build step, not yours.
Critical because misconfigured or absent entry-point declarations cause outright import failures at build time for Node.js 16+ and all strict-resolution bundlers.
Add the exports field to package.json mapping the root entry point with import, require, and types conditions. Keep legacy main, module, and types fields as fallbacks for older tooling.
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
Use a build tool like tsup (tsup src/index.ts --format esm,cjs --dts) to generate both formats and the exports map automatically. Verify the referenced files exist in dist/ before publishing.
ID: sdk-package-quality.package-config.exports-field
Severity: critical
What to look for: List all export paths configured in package.json. For each export path, check package.json for the exports field. This is the modern way to define package entry points and controls what consumers can import. Look for a structure that maps import paths to file locations, with conditions for import (ESM), require (CJS), and types (TypeScript). Example patterns:
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
Also check that the files referenced in exports actually exist or would be produced by the build script. If only main and module are used (legacy approach), note this but check that they point to valid paths.
Pass criteria: The exports field is present in package.json and maps at least the "." entry point with appropriate conditions (import, require, and/or types). OR the package uses legacy main/module fields that correctly point to built output AND the package targets a single runtime (e.g., CJS-only for Node.js CLI tools) — 100% of public entry points must be declared in the exports field. Report the count: "X export paths found, all Y correctly configured."
Fail criteria: No exports field AND no main/module fields, OR exports/main/module point to source files (e.g., src/index.ts) instead of built output, OR referenced files clearly do not exist and no build script would produce them.
Skip (N/A) when: Python package (uses pyproject.toml [project.scripts] or setup.py entry_points), Rust crate (uses Cargo.toml [lib]), or Go module (uses package directory structure). For these ecosystems, check that the equivalent entry point mechanism is properly configured — if it is, mark as pass; if not, mark as fail.
Cross-reference: The dual-format check verifies that exports include both ESM and CJS paths.
Detail on fail: Describe what's missing or misconfigured. Example: "No exports field in package.json. main points to src/index.ts (source file, not built output). Consumers using Node.js 16+ with exports-aware resolution will fail to resolve imports."
Remediation: The exports field is the modern standard for defining what your package exposes. Without it, consumers may get inconsistent resolution behavior across Node.js versions and bundlers.
// Before — legacy only, no exports map:
{
"main": "src/index.js"
}
// After — modern exports with dual format:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
Keep main, module, and types as fallbacks for older tools that don't support exports. Use a build tool like tsup that generates the exports map automatically.