# Modern exports field defined

- **Pattern:** `ab-002371` (`sdk-package-quality.package-config.exports-field`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002371
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002371)

## Why it matters

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.

## Severity rationale

Critical because misconfigured or absent entry-point declarations cause outright import failures at build time for Node.js 16+ and all strict-resolution bundlers.

## Remediation

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.

```json
{
  "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.

## Detection

- **ID:** `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:
  ```json
  "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.

  ```json
  // 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.

## External references

- iso-25010 maintainability.modifiability
- iso-25010 compatibility.interoperability

Taxons: code-quality

HTML version: https://auditbuffet.com/patterns/ab-002371
