CJS, ESM은 자바스크립트에서 모듈을 관리하고 불러오는 방식이다.
| 구분 | CommonJS (CJS) | ECMAScript Modules (ESM) |
| 도입 배경 | Node.js 환경을 위해 설계된 비표준 모듈 시스템 | JavaScript 공식 표준(ES6)에서 도입된 모듈 시스템 |
| 문법 | require(), module.exports | import, export, import() (동적 로딩 가능) |
| 로딩 방식 | 동기적 로딩 - require() 호출 시 즉시 평가 |
비동기적 해석 기반 로딩 - 정적 import: 모듈 그래프를 비동기적으로 해석 후, 모든 의존성 로드가 끝나면 의존성 순서대로 평가됨 (로드는 비동기, 실행은 동기적) - 동적 import(): 런타임에 Promise 기반으로 비동기 로드 및 평가 |
| 모듈 해석 시점 | 런타임 해석(동적) → 조건문 내부에서 require() 가능 |
정적 해석(파싱 시점) - import/export는 최상위에서만 사용 가능 (단, import()는 런타임 동적 로딩 가능) |
| 트리 쉐이킹 | 어려움 - 동적 의존성으로 인해 분석 불가 |
가능 - 정적 구조 분석을 하여 사용되지 않는 모듈 제거 가능 |
| 브라우저 지원 | X (브라우저는 CJS 구문을 이해하지 못하므로, 번들링 필요) |
O (<script type="module">)로 네이티브 지원 |
| 최상위 this | this === exports === module.exports | undefined |
| Top-level await | 불가능 | 가능 (ESM에서만 지원) |
- ESM 주요 키워드:
- JS 공식 표준 모듈 시스템,
- 비동기적 로딩,
- 정적 분석(트리쉐이킹),
- 서버/브라우저 모두 사용 가능,
- 엄격모드
- 브라우저는 HTML의
<script type="module">를 만나면- 해당 모듈 파일을 네트워크로 요청하고
- 그 안의
import경로들을 재귀적으로 따라가며 - 모든 의존성을 병렬로 fetch 한 뒤
- 의존성이 완전히 resolve될 때까지 기다렸다가 스크립트를 평가함.
- 즉, ESM 전체 로딩 구조는 비동기적이지만, 평가(실행)는 의존성 로드 완료 후 동기적으로 이루어짐.
- 다운로드는 병렬(비동기) / 실행은 순서대로(동기)
<script type="module">은 script 태그에defer속성을 적용한 것처럼 동작한다.- HTML 파싱을 블로킹하지 않고, 파싱이 모두 끝난 후, 모듈 스크립트를 다운로드 및 실행함
- (type=”module”이 아닌 일반 script는 HTML 파싱을 중단하고 스크립트를 다운로드/실행)
- 브라우저 환경 - HTML script 태그로 모듈 방식 지정:
<script>: 일반 JS 실행(모듈 시스템 없고 전역 스코프 실행함)<script type="module">: ESM 방식<script type="commonjs">: 미지원. 브라우저는 CommonJS 모듈 시스템 인식 못함.
- Node.js 환경 - package.json의 type 필드로 모듈 방식 지정 또는 확장자:
"type": "module": ESM →import fs from 'fs'"type": "commonjs": CJS →const fs = require('fs').mjs: ESM.cjs: CJS
- Top-level await
- 이전에는
await키워드를 사용하려면 반드시async함수 내부에 있어야 했다. - 모듈의 최상위에서 비동기 작업을 기다리려면 즉시 실행 함수(IIFE, Immediately Invoked Function Expression)로 감싸야 했다.
- ES2022에 Top-level await 기능이 도입되면서 ES Module 파일의 최상위에서
await를 직접 사용할 수 있게 되어 코드를 더 간결하게 만들 수 있다. - 이 기능의 핵심은 ESM 문법에서만 사용할 수 있다는 것.
- 브라우저:
<script type="module">로 로드되는 코드에서 작동 - Node.js:
.mjs확장자를 사용하거나package.json에"type": "module"을 설정한 파일에서 작동
- 브라우저:
- 이전에는
// 이전 방식 (IIFE)
(async () => {
const data = await fetch('/api/data');
})();
// Top-level await (ESM만 가능)
const data = await fetch('/api/data');