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.