자바스크립트 모듈 시스템의 진화는 언어의 성장 과정을 보여줍니다. 초기의 무질서한 스크립트 로딩부터 시작하여 CommonJS, 그리고 현재의 ES Modules까지. 2026년 기준으로 각 모듈 시스템을 정리해보겠습니다.
1. 모듈 시스템의 필요성
초기 자바스크립트는 <script> 태그로 로드되었고,
모든 변수가 전역 스코프(Global Scope)를 공유했습니다. 이로 인해:
- 변수명 충돌 위험
- 의존성 관리 어려움
- 라이브러리 간 간섭
이러한 문제를 해결하기 위해 파일 단위 스코프 격리와 선택적 기능 로드가 필요했고, 다양한 모듈 시스템이 등장했습니다.
2. 주요 모듈 시스템의 역사
CommonJS (CJS) - Node.js 표준
- 등장: 2009년
- 환경: Node.js (서버 사이드)
- 특징: 동기적 로딩, 런타임에서 모듈 로드
- 문법:
require(),module.exports,exports
AMD (Asynchronous Module Definition)
- 환경: 브라우저
- 특징: 비동기 로딩 (RequireJS)
- 현황: 2026년 기준 거의 사용되지 않음
UMD (Universal Module Definition)
- 환경: Node.js + 브라우저
- 특징: CJS와 AMD를 모두 지원하는 디자인 패턴
- 주용도: 라이브러리 배포
ES Modules (ESM) - 현재 표준
- 등장: ES6 (2015)
- 환경: 모던 브라우저 + Node.js 12+
- 특징: 정적 분석, 트리 쉐이킹 지원, 비동기 로딩
- 현황: 2026년 현재 모든 최신 프로젝트의 표준
3. CommonJS (CJS) - 레거시 시스템
CJS 기본 문법
Named Exports
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
// exports 객체에 속성 추가
exports.add = add;
exports.subtract = subtract;
// 또는 module.exports 사용
module.exports = {
add,
subtract
};
Import (Require)
// main.js
const math = require('./math');
console.log(math.add(1, 2)); // 3
// 구조 분해
const { add, subtract } = require('./math');
Default Export
// calculator.js
module.exports = function calculate(a, b) {
return a + b;
};
// main.js
const calculate = require('./calculator');
console.log(calculate(1, 2)); // 3
CJS의 특징
- ✅ Node.js 기본 지원
- ✅ 동기적 로딩 (서버에 최적화)
- ❌ 동적 분석 불가 (트리 쉐이킹 불가)
- ❌ 브라우저 네이티브 미지원
4. ES Modules (ESM) - 현재 표준
2026년 기준으로 모든 최신 프로젝트에서 사용하는 표준 모듈 시스템입니다.
Node.js에서 ESM 활성화
방법 1: package.json에서 설정
{
"type": "module",
"name": "my-project",
"version": "1.0.0"
}
방법 2: .mjs 확장자 사용
// math.mjs
export const add = (a, b) => a + b;
ESM 문법
Named Exports
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
Default Export
// calculator.js
export default function calculate(a, b) {
return a + b;
}
Mixed Exports (Default + Named)
// utils.js
export default function defaultFunc() {
return 'default';
}
export const namedFunc = () => 'named';
export const CONSTANT = 42;
Import 문법
// main.js
// Default import
import calculate from './calculator.js';
// Named imports
import { add, subtract } from './math.js';
// Mixed imports
import defaultFunc, { namedFunc, CONSTANT } from './utils.js';
// Alias (별칭)
import { add as addition } from './math.js';
// 전체 import (네임스페이스)
import * as math from './math.js';
console.log(math.add(1, 2)); // 3
// Dynamic import (런타임에 모듈 로드)
const module = await import('./math.js');
console.log(module.add(5, 3)); // 8
ESM의 핵심 특징
1. Static Analysis (정적 분석)
// ESM은 빌드 시간에 의존성을 분석할 수 있음
// 따라서 트리 쉐이킹이 가능
import { add } from './math.js'; // 사용하는 것만 번들에 포함
2. Live Bindings
// counter.js
export let count = 0;
export function increment() {
count++;
}
// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 (변경사항이 실시간 반영됨)
3. 비동기 로딩
// 조건부 모듈 로드
if (process.env.NODE_ENV === 'development') {
const debugModule = await import('./debug.js');
}
5. UMD (Universal Module Definition)
여러 환경을 지원해야 하는 라이브러리를 만들 때 사용합니다.
// myLibrary.js
(function(global, factory) {
// Node.js/CommonJS
if (typeof module === 'object' && module.exports) {
module.exports = factory();
}
// AMD
else if (typeof define === 'function' && define.amd) {
define(factory);
}
// 브라우저 전역
else {
global.MyLibrary = factory();
}
})(typeof self !== 'undefined' ? self : this, function() {
return {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
});
UMD 사용 예
// Node.js
const myLib = require('./myLibrary.js');
// 브라우저
<script src="myLibrary.js"></script>
<script>
const result = MyLibrary.add(1, 2);
</script>
// AMD/RequireJS
require(['myLibrary'], function(myLib) {
console.log(myLib.add(1, 2));
});
현황: 2026년 기준 ESM으로의 전환 완료. 레거시 지원이 필요한 경우에만 사용.
6. 비교 표
| 특성 | CommonJS | ESM | UMD |
|---|---|---|---|
| 로딩 방식 | 동기 | 비동기 | 하이브리드 |
| 분석 | 동적 | 정적 | 정적 |
| 트리 쉐이킹 | ❌ | ✅ | △ |
| 브라우저 | ❌ | ✅ | ✅ |
| Node.js | ✅ (기본) | ✅ (12+) | ✅ |
| Live Binding | ❌ | ✅ | ❌ |
| 사용난이도 | 쉬움 | 쉬움 | 어려움 |
7. 2026년 권장사항
✅ 새 프로젝트는 ESM 사용
- React, Vue, Angular 모두 ESM 기본
- Vite, Webpack 모두 ESM 중심
- Node.js 18+ 모든 프로젝트에서 ESM 권장
// package.json
{
"type": "module",
"engines": {
"node": ">=18.0.0"
}
}
✅ CommonJS가 필요한 경우
- 레거시 Node.js 프로젝트 유지보수
- ESM 완벽 지원 전 구형 환경
// ESM 프로젝트에서 CJS 모듈 사용
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const legacy = require('./legacy.cjs');
✅ 라이브러리 배포 시 고려사항
- Dual Package: ESM + CJS 동시 지원
- package.json exports 필드 활용
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
결론
2026년의 자바스크립트 모듈 생태계는 ESM으로 완전히 통합됨 CommonJS는 역사적 중요성이 있지만, 새로운 프로젝트에서는 ES Modules를 표준으로 사용해야 함 레거시 시스템 유지보수가 필요한 경우에만 CommonJS를 고려.