티스토리 뷰

웹 애플리케이션의 규모가 커지면서 코드의 양은 기하급수적으로 늘어났습니다. 과거에는 수천 줄의 코드를 하나의 파일에 담거나, 여러 파일을 <script> 태그로 순서에 맞춰 일일이 로드해야 했죠. 하지만 이는 전역 오염과 의존성 관리라는 지옥을 선사했습니다.
이 혼란을 잠재우기 위해 등장한 것이 바로 **ES Modules(ESM)**입니다. 이제 모듈 시스템은 단순히 파일을 나누는 도구를 넘어, 코드의 독립성을 보장하고 재사용성을 극대화하는 현대 개발의 필수 메커니즘이 되었습니다.
1. Deep Dive: 모듈 시스템의 작동 원리
모듈 시스템을 가장 쉽게 이해하는 비유는 **'레고 블록'**입니다. 완성된 성을 만들기 위해 우리는 각기 다른 모양의 블록(모듈)을 조립합니다. 이때 각 블록은 자신만의 독립된 공간을 가지며, 연결 부위(인터페이스)를 통해서만 다른 블록과 결합됩니다.
자바스크립트 엔진은 모듈을 로드할 때 다음의 3단계를 거칩니다:
- 구성 (Construction): URL을 통해 파일을 찾고, 소스 코드를 내려받아 모듈 레코드(Module Record)로 구문 분석합니다.
- 인스턴스화 (Instantiation): export된 값들을 담을 메모리 공간을 확보하고, import와 export가 해당 공간을 가리키도록 연결합니다. (이때 실제 코드는 실행되지 않습니다.)
- 평가 (Evaluation): 실제 코드를 실행하여 메모리 공간에 값을 채워 넣습니다.
2. Hands-on: 실전 비즈니스 로직 적용하기
단순한 더하기 예제 대신, 이커머스 시스템의 장바구니와 세금 계산 로직을 구현하며 import와 export의 실무적인 사용법을 알아보겠습니다.
(1) Named Export: 여러 기능을 내보낼 때
하나의 파일에서 여러 개의 변수나 함수를 공유해야 할 때 사용합니다. 내보낼 때 사용한 이름 그대로 가져와야 합니다.
// libs/taxCalculator.js
export const TAX_RATE = 0.1; // 10% 부가세율
// 특정 국가의 세금을 계산하는 로직
export function calculateTax(price, region = 'KR') {
if (region === 'KR') {
return price * TAX_RATE;
}
return price * 0.15; // 기타 지역 15%
}
export function formatCurrency(amount) {
return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(amount);
}
(2) Default Export: 모듈의 대표 기능을 내보낼 때
파일당 단 한 번만 사용할 수 있으며, 가져올 때 이름을 자유롭게 지을 수 있습니다.
// services/CartService.js
export default class CartService {
constructor() {
this.items = [];
}
addItem(product) {
this.items.push(product);
console.log(`${product.name} 상품이 장바구니에 담겼습니다.`);
}
getTotalPrice() {
return this.items.reduce((total, item) => total + item.price, 0);
}
}
(3) 통찰력 있는 결합: 메인 로직에서 사용하기
// main.js
import CartService from './services/CartService.js'; // Default import (이름 변경 가능)
import { calculateTax, formatCurrency as format } from './libs/taxCalculator.js'; // Named import (as로 별칭 가능)
const myCart = new CartService();
myCart.addItem({ name: '고성능 키보드', price: 150000 });
const subTotal = myCart.getTotalPrice();
const tax = calculateTax(subTotal);
const total = subTotal + tax;
console.log(`소계: ${format(subTotal)}`);
console.log(`세금: ${format(tax)}`);
console.log(`최종 합계: ${format(total)}`);
3. 트러블슈팅(Troubleshooting): 흔히 겪는 실수들
- 상대 경로의 명시: 브라우저 환경에서 ESM을 사용할 때는 반드시 확장자(.js)를 포함해야 합니다. (Node.js의 CommonJS와 가장 큰 차이점입니다.)
- Live Bindings: import된 값은 읽기 전용(Read-only) 뷰입니다. 내보낸 쪽에서 값이 변하면 가져온 쪽에서도 반영되지만, 가져온 쪽에서 그 값을 직접 수정하려고 하면 TypeError가 발생합니다.
- CORS 이슈: 로컬 파일 시스템(file://)에서 모듈을 열면 보안 정책상 오류가 발생합니다. 반드시 라이브 서버(Live Server) 환경에서 테스트해야 합니다.
4. Trade-offs: 모듈 시스템 선택의 고민
모듈화는 구조적으로 완벽해 보이지만, 고려해야 할 지점들이 있습니다.
| 장점 (Pros) | 단점 및 고려사항 (Cons) |
| 스코프 분리: 전역 변수 오염 방지 | 네트워크 오버헤드: 파일이 너무 잘게 쪼개지면 HTTP 요청 횟수가 증가 (HTTP/2에서는 완화) |
| 의존성 파악: 코드 간의 관계가 명확해짐 | 런타임 호환성: 구형 브라우저(IE 등) 지원을 위해 Babel/Webpack 같은 빌드 도구 필수 |
| Tree Shaking: 사용하지 않는 코드를 제거하여 번들 크기 최적화 용이 | 순환 참조: A가 B를 참조하고 B가 A를 참조할 때 발생하는 초기화 이슈 주의 |
5. 요약 및 제언
모듈 시스템은 단순히 코드를 나누는 기술이 아니라, 애플리케이션의 아키텍처를 설계하는 방식입니다. Named Export는 명확한 인터페이스를 제공할 때 유리하고, Default Export는 해당 모듈의 핵심 정체성을 드러낼 때 유용합니다.
복잡한 수식을 다루는 금융 엔진을 만든다면, 각 연산 로직을 모듈로 분리하여 관리해 보세요. 예를 들어, 복리 계산식인
를 별도의 finance.js 모듈로 분리하면 검증과 재사용이 훨씬 수월해질 것입니다.
여러분의 프로젝트에서는 Default Export와 Named Export 중 어떤 것을 더 선호하시나요? 프로젝트 전체의 일관성을 위해 팀 내 컨벤션을 먼저 수립해 보는 것을 추천합니다.
'Frontend > JAVASCRIPT' 카테고리의 다른 글
| Optional Chaining과 Nullish Coalescing: 런타임 에러를 방어하는 우아한 코드 설계 (0) | 2026.03.03 |
|---|---|
| 사용자 경험과 비용을 동시에 잡는 기술: 디바운스와 쓰로틀링 Deep Dive (0) | 2026.03.03 |
| 완벽한 에러 핸들링을 위한 전략: try...catch 그 이상의 실무 패턴 (0) | 2026.03.03 |
| 클래스 없는 상속의 마법, 자바스크립트 프로토타입 완벽 이해하기 (0) | 2026.03.02 |
| 브라우저의 기억 장치, LocalStorage 완벽 가이드: 데이터 지속성의 핵심 (0) | 2026.03.02 |
- Total
- Today
- Yesterday
- Javascript
- MSA
- Nextjs
- java
- CSS
- Rag
- SSR
- HBM
- 엣지컴퓨팅
- TypeScript
- 구글
- 멀티모달
- sLLM
- CSR
- 협력
- 웹기초
- on-device ai
- prompt engineering
- 스마트안경
- LLM
- 카카오
- It용어
- AI
- HTML
- react
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |