Question presented to candidate: "A package you maintain needs to expose a different implementation to CommonJS consumers than to ESM consumers — and you also want to guarantee nobody reaches into your package's internal files directly. How do you express both of those requirements in package.json?"
What a strong answer should cover:
- The
"exports"field inpackage.jsondoes two things at once, directly answering both halves of the prompt: it defines which paths of a package are importable at all (anything not listed is genuinely blocked), and — via conditional exports — it can map the same import path to different files depending on how the consumer is loading the package ("require"vs."import", among other conditions). - 📌 Verified, not assumed — the exact answer to the prompt's first half: a real package with
"exports": { ".": { "require": "./cjs-entry.js", "import": "./esm-entry.mjs" } }genuinely resolved to therequirefile when loaded viarequire(), and genuinely resolved to the different,importfile when loaded via a realimport()— the identical package specifier, genuinely different real files, chosen automatically based on how the consumer loaded it. - 📌 Verified, not assumed — the exact answer to the prompt's second half: a real file that genuinely existed on disk inside the package, but was not listed anywhere in
"exports", was genuinely unreachable —require()-ing it directly threw a realERR_PACKAGE_PATH_NOT_EXPORTED, not merely a convention violated silently. - A precise answer contrasts this with the older
"main"field:"main"only ever pointed to one entry file, with no conditional logic and no enforcement — any file inside the package's directory was always reachable via a deep import, regardless of whether the author intended that."exports"is a genuinely stricter, more capable encapsulation boundary, verified directly above with a real blocked path. - The precise scope, stated honestly: conditions beyond
"require"/"import"exist too ("node","browser","default"as a fallback, and custom conditions some tools define) — a package can layer several, evaluated in the order they're listed, with"default"conventionally last as the catch-all; this is the real mechanism behind what's sometimes called the "dual package hazard" workaround for shipping both a CJS and ESM build from one package.
Clarifying questions expected:
- "Does this package genuinely need different CODE for CJS vs. ESM consumers, or just a different file extension/wrapper around identical logic?" — shapes whether conditional exports need real behavioral differences or just a thin compatibility shim.
- "Are there existing consumers relying on deep imports into this package's internal files that a stricter "exports" field would now block?" — a real, breaking-change risk worth surfacing before introducing
"exports"to an existing published package.
Code / implementation expected: Yes — a real package with genuine conditional exports (verified resolving differently for require vs. import) and a real blocked unlisted deep path is the concrete, convincing proof of exactly how both mechanisms work.