CommonJS (CJS): require() load synchronous và blocking — có thể require() ở bất kỳ đâu trong code kể cả trong if/function, module.exports là bất kỳ giá trị gì, không hỗ trợ tree-shaking.
- ESM:
importphải ở top-level (static analysis), load asynchronous cho phép parallel fetch, tree-shakeable vì bundlers biết chính xác exports nào được dùng. - ESM exclusive features: top-level
await, import.meta.url, dynamicimport(). - Interop challenges: CJS có thể
require()ESM file? Không được, phải dùng dynamicimport(). - ESM có thể import CJS? Có, nhưng chỉ default import.
- Dual package hazard: publish cả CJS lẫn ESM cùng package — nếu app load cả hai, sẽ có 2 instances của module (state, class không share được).
- Giải pháp: conditional exports trong package.json.
type: 'module'trong package.json = tất cả.jslà ESM, CJS phải đổi thành.cjs. - Node.js 22+ hỗ trợ
--experimental-require-moduleđể require() ESM.
CommonJS (CJS): require() loads synchronously and blocks — can be called anywhere in code including inside if/function blocks; module.exports can be any value; no tree-shaking support.
- ESM:
importmust be at the top level (static analysis), loads asynchronously enabling parallel fetching, tree-shakeable because bundlers know exactly which exports are used. - ESM-exclusive features: top-level
await, import.meta.url, dynamicimport(). - Interop challenges: can CJS
require()an ESM file? No — must use dynamicimport(). - Can ESM import CJS? Yes, but only as a default import.
- Dual package hazard: publishing both CJS and ESM in the same package — if an app loads both, there will be two module instances (state, classes cannot be shared).
- Solution: conditional exports in package.json.
type: 'module'in package.json means all.jsfiles are ESM; CJS files must be renamed to.cjs. - Node.js 22+ supports
--experimental-require-moduletorequire()ESM files.