ESM vs CommonJS — Why require and import Are Different
Sync vs async loading, and what "type": "module" means
Core Difference
CJS: Runtime loading — require() is a function call. Can be conditional. Loads while executing.
ESM: Parse-time loading — import is syntax. Top-level only. Engine builds dependency graph before execution.
ESM can import CJS, but CJS can't require ESM (ESM is async, require is sync).
The Pain in Practice
When libraries go ESM-only, CJS projects can't require() them. node-fetch v3, chalk v5 caused ecosystem chaos.
Solution: dynamic import() loads ESM from CJS, but becomes async.
Key Points
CJS (require): sync, runtime loading, conditional possible
ESM (import): async, parse-time loading, top-level only
ESM → CJS import OK, CJS → ESM require impossible (workaround: dynamic import)
"type": "module" in package.json switches entire project to ESM