[JS Study #07] 모듈 시스템, ESM과 CommonJS는 뭐가 다를까?
🚀 시작하며
이번엔 자바스크립트의 두 가지 대표 모듈 시스템인 ESM과 CommonJS를 비교하고,
각각 언제 어떻게 쓰이는지, 어떤 차이가 있는지를 정리합니다.
“모듈 시스템을 왜 도입했는지”, “ESM과 CJS는 어떻게 다르고 어떤 기준으로 선택해야 하는지”,
실무적으로 어떤 의사결정을 내려야 할지를 함께 고민해보는 시간을 가져보겠습니다.
모듈 시스템이란?
✅ 정의
모듈 시스템이란, 코드를 여러 파일로 나누고, 이들 간의 의존성을 정의하여 재사용성과 유지보수성을 높이는 구조적 방식입니다.
✅ 왜 필요한가?
초기의 자바스크립트는 <script>
태그로 여러 파일을 단순히 순서대로 불러오는 방식이었습니다. 이로 인해:
- 전역 스코프 오염
- 변수 이름 충돌
- 파일 순서에 따른 오류 발생
- 코드 복잡성 증가
등의 문제가 발생했죠.
이를 해결하기 위해 모듈 시스템이 도입되었고,
Node.js 환경에서는 CommonJS,
브라우저와 ES6 이후의 JS 표준에선 ESM(ECMAScript Modules)이 등장했습니다.
CommonJS(CJS)란?
✅ 정의
CommonJS는 Node.js에서 채택한 서버 사이드용 모듈 시스템입니다.
동기 방식이며,require()
와module.exports
문법으로 모듈을 로딩/내보냅니다.
✅ 주요 특징
- 동기적 로딩(Synchronous):
require()
가 실행될 때 모듈을 즉시 읽음 - Node.js 런타임 환경에 최적화
- 런타임 로딩 가능: 조건문, 루프 내
require()
가능 - 완전한 CommonJS 사양을 구현한 환경은 Node.js가 대표적
✅ 문법 예시
// math.js
function add(a, b) {
return a + b;
}
module.exports = { add };
// main.js
const math = require('./math');
console.log(math.add(1, 2));
✅ 장점과 단점
장점 | 단점 |
---|---|
직관적이고 오래된 프로젝트에서 널리 사용 | 브라우저에서 직접 실행 불가 |
require() 가 함수이기 때문에 유연함 |
정적 분석이 어려워 최적화가 제한적임 |
도입이 쉬움 | 트리 쉐이킹(Tree-shaking) 불가능 |
트리 쉐이킹(Tree-shaking)
사용되지 않는 코드를 번들에서 제거하여 최종 결과물을 더 작고 빠르게 만드는 기법
나무를 흔들어서 죽은 가지를 떨어트리는 것을 비유한 거라고 합니다.
ESM (ECMAScript Modules)이란?
✅ 정의
ESM은 ES6(2015)에 도입된 표준 모듈 시스템입니다. 브라우저, Node.js 양쪽 모두를 위한 공식 표준이며,
import/export
문법을 사용합니다.
✅ 주요 특징
- 정적 분석 가능(Static Analysis):
import
문이 파일 최상단에 있어야 하며, 런타임 이전에 분석됨 - 비동기 로딩 방식: 모듈을 필요에 따라 지연 로딩 가능 (
import()
) - 브라우저/Node.js 호환: Node에선
"type": "module"
설정 필요
✅ 문법 예시
// math.mjs
export function add(a, b) {
return a + b;
}
// main.mjs
import { add } from './math.mjs';
console.log(add(2, 3));
또는 .js
파일에서 사용하려면 package.json
에 아래 설정을 추가해야 함:
{
"type": "module"
}
✅ 장점과 단점
장점 | 단점 |
---|---|
정적 분석을 통해 트리 쉐이킹 등 최적화 가능 | import 는 최상단에서만 사용 가능 |
브라우저에서도 그대로 사용 가능 | 기존 CommonJS와 호환 어려움 |
표준 사양 기반으로 향후 JS 생태계에서 주도적임 | .mjs 확장자나 설정이 번거로울 수 있음 |
주요 차이점 비교
항목 | CommonJS (CJS) | ESM |
---|---|---|
문법 | require() , module.exports |
import , export |
로딩 방식 | 동기(Synchronous) | 비동기(Asynchronous, 정적 분석 기반) |
해석 시점 | 실행 시 | 파싱 시(정적 분석) |
브라우저 지원 | X | O |
사용 위치 | Node.js | 브라우저, Node.js |
조건부 로딩 | 가능 | 제한적 (import() 사용해야 가능) |
트리쉐이킹 | 불가능 | 가능 |
파일 확장자 | .js |
.mjs , 또는 "type": "module" 사용 시 .js |
Node.js에서 둘 다 쓸 수 있을까?
Node.js는 기본적으로 CommonJS 기반이지만,
package.json
에 "type": "module"
을 추가하면 ESM도 사용할 수 있습니다.
json복사편집{
"type": "module"
}
단, 다음과 같은 제약이 생깁니다:
.js
에서require()
사용 불가.mjs
또는.js + "type": "module"
로 통일해야 함__dirname
,__filename
같은 CJS 전용 변수 사용 불가
✔️ 혼용은 되도록 피하고, 처음부터 프로젝트 구조를 결정하는 것이 좋습니다.
실무에서 언제 무엇을 선택해야 할까?
상황 | 추천 모듈 시스템 | 이유 |
---|---|---|
기존 Node.js 프로젝트 | CommonJS | 호환성, 종속 라이브러리 대부분 CJS 기반 |
새 Node.js 프로젝트 | ESM | 최신 표준 채택, 브라우저와 호환성 확보 |
브라우저 기반 라이브러리 개발 | ESM | 그대로 script type=”module”로 로딩 가능 |
번들러 사용 (Vite, Webpack 등) | ESM | 내부적으로 변환되기 때문에 ESM 권장 |
라이브러리(NPM) 배포 | ESM + CJS 병행 | 여러 환경에서 사용할 수 있게 dual export 구조 고려 필요 |
🤔 추가로 생각해볼 점
📦 1. ESM으로의 전환이 왜 중요한가?
자바스크립트 생태계는 점점 ESM 중심으로 이동 중입니다. 최신 도구(Vite, Bun, Deno 등)는 ESM을 기본으로 채택하고 있고, 브라우저와 Node.js 간의 경계도 점점 흐려지고 있습니다.
→ 앞으로는 ESM이 기본이 될 것이므로, 학습과 구조 설계 시 ESM을 전제로 삼는 것이 유리합니다.
🔁 2. ESM으로 이관 시 고려할 점
- 기존 CJS 기반 라이브러리와의 호환성 문제
import.meta.url
,top-level await
,dynamic import()
같은 기능 이해 필요- jest 같은 테스트 도구도 CJS 위주이기 때문에 설정 필요
🎯 3. 트리쉐이킹(Dead Code Elimination)의 중요성
ESM은 정적 분석이 가능하므로 번들링 시 사용하지 않는 코드를 제거할 수 있습니다. 이건 번들 크기, 로딩 속도, 성능 최적화에 큰 영향을 줍니다.
댓글남기기