# Dual ESM/CJS format or clear ESM-only stance

- **Pattern:** `ab-002391` (`sdk-package-quality.build-distribution.dual-format`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002391
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002391)

## Why it matters

A CJS-only package blocks ESM consumers entirely — they cannot use static `import` syntax and must resort to dynamic `import()` or `createRequire` workarounds. ESM-only packages break all CommonJS consumers (many Node.js scripts, Jest without experimental VM modules, older tools). ISO 25010 interoperability requires supporting both resolution systems until the ecosystem fully migrates. SSDF PS.3.2 flags format mismatches as a distribution integrity concern: the format published must match what consumers expect given the `type` field and `exports` conditions.

## Severity rationale

High because CJS-only packages are incompatible with ESM-native bundlers and Node.js projects using `type: "module"`, and ESM-only packages break all CJS consumers.

## Remediation

Configure your build tool to emit both ESM and CJS outputs and map them in `package.json` exports.

```ts
// tsup.config.ts — dual format output:
import { defineConfig } from 'tsup'
export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  dts: true,
  splitting: false,
  clean: true
})
```

```json
{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "type": "module"
}
```

If you commit to ESM-only, set `"type": "module"` and document the minimum Node.js version (18+) in the `engines` field and README.

## Detection

- **ID:** `dual-format`
- **Severity:** `high`
- **What to look for:** List all output formats produced by the build. For each format, check the build output and configuration for module format:
  1. `package.json` `type` field: `"module"` (ESM default) or `"commonjs"` (CJS default) or absent (CJS default)
  2. `exports` field conditions: presence of both `import` and `require` conditions indicates dual format
  3. Build tool config: check `format` options in tsup, rollup, or esbuild for `['esm', 'cjs']`
  4. Output file extensions: `.mjs` (ESM), `.cjs` (CJS), `.js` (depends on `type` field)
  5. If ESM-only: check that `type: "module"` is set and there's a clear rationale (e.g., Node.js 18+ minimum)
- **Pass criteria:** The package provides BOTH ESM and CJS output formats (dual format), OR the package is explicitly ESM-only with `"type": "module"` in `package.json` and the README or `engines` field indicates the minimum Node.js version requirement — at least 2 formats (ESM and CJS) must be published for broad compatibility. Report: "X output formats found."
- **Fail criteria:** The package provides only CJS without ESM (blocking ESM-only consumers), OR the build output format doesn't match the `type` field and `exports` conditions (e.g., `.js` files that are actually ESM but `type` is not `"module"`).
- **Skip (N/A) when:** Python (uses wheels/sdist), Rust (cargo handles this), Go (source distribution), or browser-only package that only ships a UMD/IIFE bundle.
- **Cross-reference:** The `exports-field` check verifies these formats are correctly mapped in package.json exports.
- **Detail on fail:** `"Package ships only CommonJS (no 'type' field, single .js output). ESM consumers using 'import' syntax will get ERR_REQUIRE_ESM or need dynamic import() workarounds. Add ESM output or set type: 'module'."`
- **Remediation:** Most modern tools expect ESM. Dual format (ESM + CJS) gives maximum compatibility.

  ```ts
  // tsup.config.ts — dual format:
  import { defineConfig } from 'tsup'
  export default defineConfig({
    entry: ['src/index.ts'],
    format: ['esm', 'cjs'],
    dts: true,
    splitting: false,
    clean: true
  })
  ```

  ```json
  // package.json — dual format exports:
  {
    "type": "module",
    "exports": {
      ".": {
        "types": "./dist/index.d.ts",
        "import": "./dist/index.js",
        "require": "./dist/index.cjs"
      }
    }
  }
  ```

## External references

- iso-25010 compatibility.interoperability
- ssdf PS.3.2

Taxons: code-quality, operational-readiness

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