CJS(CommonJS) vs ESM(ECMAScript Modules) 정리

2025. 11. 18. 20:57

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">를 만나면
    1. 해당 모듈 파일을 네트워크로 요청하고
    2. 그 안의 import 경로들을 재귀적으로 따라가며
    3. 모든 의존성을 병렬로 fetch 한 뒤
    4. 의존성이 완전히 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');