70 lines
3.8 KiB
Markdown
70 lines
3.8 KiB
Markdown
## Module System Fundamentals
|
|
|
|
**CommonJS** represents a synchronous, dynamic module loading paradigm designed for server environments where filesystem I/O is relatively fast. **ES Modules (ESM)** embody an asynchronous, static module system architected for both browser and server contexts with compile-time optimizations.
|
|
|
|
## Syntactic and Semantic Differences
|
|
|
|
**CommonJS** uses `require()` for imports and `module.exports`/`exports` for exports:
|
|
```javascript
|
|
// Dynamic import resolution
|
|
const dependency = require('./path/to/module');
|
|
// Export assignment
|
|
module.exports = { function: myFunction };
|
|
```
|
|
|
|
**ESM** employs declarative `import`/`export` statements:
|
|
```javascript
|
|
// Static import declaration
|
|
import { namedExport } from './module.js';
|
|
// Named export declaration
|
|
export const myFunction = () => {};
|
|
```
|
|
|
|
## Loading Semantics and Timing
|
|
|
|
The critical distinction lies in **when** module resolution occurs:
|
|
|
|
- **CommonJS**: Runtime module resolution with synchronous loading. The `require()` calls are evaluated during execution, enabling conditional imports and dynamic module paths.
|
|
|
|
- **ESM**: Parse-time static analysis with asynchronous loading. Import declarations are hoisted and resolved during the parsing phase, before code execution begins.
|
|
|
|
## Module Graph Construction
|
|
|
|
**CommonJS** builds its dependency graph dynamically through depth-first traversal during runtime execution. This creates a **mutable module namespace** where exports can be modified post-load.
|
|
|
|
**ESM** constructs a static module graph during the parsing phase through three distinct phases:
|
|
1. **Construction**: Parse modules and build dependency graph
|
|
2. **Instantiation**: Allocate memory for exports and create live bindings
|
|
3. **Evaluation**: Execute module code and populate exports
|
|
|
|
## Binding Semantics
|
|
|
|
This is where it gets spicy:
|
|
|
|
**CommonJS** creates **value copies** - when you import something, you get a snapshot of the exported value at that moment. Mutations to the original don't propagate.
|
|
|
|
**ESM** establishes **live bindings** - imports are read-only references to the actual exported values. Changes to exports are immediately visible to all importers.
|
|
|
|
## Circular Dependency Handling
|
|
|
|
**CommonJS** handles cycles through partial evaluation - modules return their current `exports` object even if not fully initialized, potentially causing undefined behavior.
|
|
|
|
**ESM** supports cycles through forward references in the instantiation phase, though accessing uninitialized bindings throws `ReferenceError`.
|
|
|
|
## Interoperability Challenges
|
|
|
|
The **dual module hazard** emerges when the same package exists in both formats simultaneously, potentially creating separate instances and breaking singleton patterns. Node.js addresses this through:
|
|
|
|
- **Conditional exports** in package.json
|
|
- **ESM wrapper patterns** for CommonJS modules
|
|
- **Dynamic import()** for loading CommonJS from ESM contexts
|
|
|
|
## Performance Implications
|
|
|
|
**ESM's** static analysis enables superior **tree-shaking** (dead code elimination) and **bundler optimizations**. The upfront parsing cost pays dividends in production through smaller bundle sizes and better runtime performance.
|
|
|
|
**CommonJS's** dynamic nature prevents many compile-time optimizations but offers runtime flexibility for plugin architectures and conditional loading patterns.
|
|
|
|
TL;DR: CommonJS prioritizes runtime flexibility with synchronous, dynamic loading, while ESM optimizes for static analysis and performance through asynchronous, declarative imports. The transition reflects JavaScript's evolution from a simple scripting language to a platform for complex, optimized applications.
|
|
|
|
The interop story is still evolving, particularly around top-level await and package dual-mode publishing strategies. Understanding both systems remains essential for navigating the current JavaScript ecosystem's transitional state. |