과거 Node.js는 브라우저와 독립적으로 자바스크립트 파일을 모듈로 나누기 위해 독자적인 CommonJS (CJS) 규격을 사용했습니다. 하지만 최근 자바스크립트 표준 위원회에서 ES Modules (ESM)라는 공식 표준을 제정함에 따라, 현재 Node.js는 두 가지 모듈 시스템을 모두 지원하는 과도기를 겪고 있습니다. 이 두 시스템이 런타임에서 엔진에 의해 어떻게 다르게 해석되고 로드되는지 이해하는 것은 매우 중요합니다.
Node.js는 기본적으로 확장자가 .js인 파일을 CommonJS로 취급합니다. Node.js 환경에서 ES Modules를 사용하려면 package.json 파일에 "type": "module" 필드를 추가하거나, 파일 확장자를 .mjs로 명시적으로 변경해야 합니다.
| 특징 | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| 불러오기 문법 | const lib = require('module') |
import lib from 'module' |
| 내보내기 문법 | module.exports = { ... } |
export const lib = ... |
| 분석 시점 | 런타임(Runtime) 동적 분석 (코드 실행 중 동적으로 require 발생 가능) |
빌드/파싱 타임 정적 분석 (실행 전 최상단에서 의존성 맵 완벽 구성) |
| Tree-Shaking 지원 | 어려움 (불필요한 코드도 함께 번들링) | 강력하게 지원 (사용하지 않는 코드 제거) |
| Top-level Await | 지원하지 않음 | 지원함 (함수 밖에서 await 사용 가능) |
| 기본 파일 확장자 | .cjs (또는 type 명시 안된 .js) |
.mjs (또는 type:"module" 인 .js) |
CommonJS는 require() 함수를 만나는 순간 동기적으로 실행을 멈추고 파일을 읽어와서 해석(평가)합니다.
ESM은 실제 코드를 실행하기 전에 전체 파일의 import 구조를 먼저 정적(Static)으로 파악하여 연결(Linking)한 뒤 비동기적으로 실행합니다. 이 덕분에 불필요한 코드를 제거하는 Tree-Shaking이 가능합니다.