docs/solutions/test-failures/2026-03-25-csv-papaparse-cjs-interop-needs-default-import.md
@platejs/csv exposes an ESM entrypoint from packages/csv/dist/index.js. That entrypoint imported parse as a named export from papaparse.
That looks harmless until native Node ESM loads the package. papaparse is CommonJS, so Node rejects import { parse } from 'papaparse' and the whole package explodes before any CSV logic runs.
The deserializer in packages/csv/src/lib/deserializer/utils/deserializeCsv.ts used a named import:
import { parse } from "papaparse";
The build preserved that shape in the published ESM output. Native Node ESM does not guarantee named exports from a CommonJS dependency, so the runtime import failed with:
SyntaxError: Named export 'parse' not found. The requested module 'papaparse' is a CommonJS module
There was one extra trap here: Bun can hide the issue. A Bun test that shells out through process.execPath is still launching Bun, not Node, so the regression test can go green while the published package stays broken.
Load Papa Parse through the CommonJS-safe default import and call parse from that namespace:
import Papa from "papaparse";
const parseCsv = <T>(data: string, config?: CsvParseOptions) =>
Papa.parse<T>(data, {
...config,
download: false,
worker: false,
});
That makes the built entrypoint emit:
import Papa from "papaparse";
Node can load that form correctly from an ESM package, so Vitest and other native ESM consumers stop failing at module load time.
Add a regression test at packages/csv/src/lib/esmInterop.spec.ts that imports the built package entrypoint through real Node ESM:
execFileSync("node", [
"--input-type=module",
"--eval",
`await import(${JSON.stringify(distEntry)});`,
]);
That test matters because it proves the package surface, not just the TypeScript source.
Named export ... not found, test the built package entrypoint directly.node, not process.execPath.