📦

ESM vs CommonJS — Why require and import Are Different

Sync vs async loading, and what "type": "module" means

Core Difference

CJS: Runtime loadingrequire() is a function call. Can be conditional. Loads while executing.

ESM: Parse-time loadingimport 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

1

CJS (require): sync, runtime loading, conditional possible

2

ESM (import): async, parse-time loading, top-level only

3

ESM → CJS import OK, CJS → ESM require impossible (workaround: dynamic import)

4

"type": "module" in package.json switches entire project to ESM

Use Cases

New projects — starting with ESM is future-proof Existing CJS projects — use ESM-only libraries via dynamic import