ES6 모듈 시스템으로 코드 모듈화하기
import/export를 활용한 체계적인 JavaScript 코드 구성 방법
모듈화가 필요한 이유
프로젝트를 시작할 때 모든 JavaScript 코드를 하나의 파일에 작성하면 간단해 보입니다. 하지만 코드가 500줄, 1000줄로 늘어나면 어디에 무엇이 있는지 찾기 어려워집니다. ES6 모듈 시스템은 이 문제를 해결해줍니다.
모듈화의 장점
- 코드 구조화: 관련된 코드를 하나의 파일로 묶어 관리합니다
- 네임스페이스 관리: 전역 변수 충돌을 방지합니다
- 의존성 명시: 어떤 모듈이 어떤 모듈을 사용하는지 명확합니다
- 재사용성: 다른 프로젝트에서도 모듈을 쉽게 재사용할 수 있습니다
- 지연 로딩: 필요한 모듈만 로드하여 성능을 개선할 수 있습니다
기본 문법: export와 import
Named Export
여러 개의 값을 내보낼 때 사용합니다. 저는 TestModel 클래스를 export 할 때 이 방식을 사용했습니다.
// models/TestModel.js
export class TestModel {
constructor() {
this.tests = [];
}
getAllTests() {
return this.tests;
}
}
export function helperFunction() {
// 헬퍼 함수
}
export const CONFIG = {
maxTests: 10
};
import 할 때는 중괄호를 사용하고, 정확한 이름을 지정해야 합니다:
import { TestModel, helperFunction, CONFIG } from './models/TestModel.js';
Default Export
모듈에서 하나의 주요 값만 내보낼 때 사용합니다. 파일 하나당 하나의 default export만 가능합니다.
// utils/analytics.js
export default function trackEvent(eventName, data) {
console.log('Event tracked:', eventName, data);
}
// 사용할 때
import trackEvent from './utils/analytics.js';
// 또는 원하는 이름으로 import 가능
import myTracker from './utils/analytics.js';
실전 적용: 프로젝트 구조
심리 테스트 프로젝트에서 저는 다음과 같이 모듈을 구성했습니다:
js/
├── app.js # 진입점
├── models/
│ └── TestModel.js # 데이터 모델
├── views/
│ ├── CardView.js # 카드 뷰
│ └── ModalView.js # 모달 뷰
└── controllers/
└── TestController.js # 메인 컨트롤러
진입점 (app.js)
애플리케이션의 시작점입니다. Controller를 import하고 초기화합니다.
// js/app.js
import { TestController } from './controllers/TestController.js';
document.addEventListener('DOMContentLoaded', () => {
const testController = new TestController();
testController.init();
// 디버깅을 위해 전역으로 등록
window.testController = testController;
});
Controller에서 여러 모듈 import
Controller는 Model과 View 모두를 사용하므로 여러 모듈을 import합니다.
// js/controllers/TestController.js
import { TestModel } from '../models/TestModel.js';
import { CardView } from '../views/CardView.js';
import { ModalView } from '../views/ModalView.js';
export class TestController {
constructor() {
this.testModel = new TestModel();
this.cardView = new CardView('test-cards-container');
this.modalView = new ModalView('modal-container');
}
init() {
const tests = this.testModel.getAllTests();
this.cardView.render(tests, this.handleCardClick.bind(this));
}
}
HTML에서 모듈 사용하기
ES6 모듈을 사용하려면 script 태그에 type="module" 속성을 추가해야 합니다.
<!-- index.html -->
<script type="module" src="js/app.js"></script>
중요: 모듈은 CORS 정책의 영향을 받으므로 로컬 파일 시스템에서는 작동하지 않습니다. 반드시 로컬 서버를 통해 실행해야 합니다.
# Python으로 로컬 서버 실행
python -m http.server 8000
# Node.js http-server 사용
npx http-server -p 8000
모듈 로딩 최적화
동적 import
모든 모듈을 처음부터 로드할 필요는 없습니다. 필요할 때만 로드하는 동적 import를 사용하면 초기 로딩 속도를 개선할 수 있습니다.
// 버튼 클릭 시 모듈 로드
button.addEventListener('click', async () => {
const { TestResultView } = await import('./views/TestResultView.js');
const resultView = new TestResultView();
resultView.render(data);
});
트리 쉐이킹 (Tree Shaking)
모듈 시스템을 사용하면 번들러(Webpack, Rollup 등)가 사용하지 않는 코드를 자동으로 제거할 수 있습니다. 이를 통해 최종 번들 크기를 줄일 수 있습니다.
// utils.js
export function usedFunction() { }
export function unusedFunction() { } // 이 함수는 번들에서 제외됨
// main.js
import { usedFunction } from './utils.js';
usedFunction();
흔한 실수와 해결 방법
1. 파일 확장자 생략
Node.js와 달리 브라우저에서는 파일 확장자를 반드시 명시해야 합니다.
// ❌ 잘못된 예
import { TestModel } from './models/TestModel';
// ✅ 올바른 예
import { TestModel } from './models/TestModel.js';
2. 순환 의존성
A 모듈이 B를 import하고, B 모듈이 A를 import하는 순환 의존성은 피해야 합니다.
// ❌ 순환 의존성
// A.js
import { B } from './B.js';
export class A { }
// B.js
import { A } from './A.js';
export class B { }
// ✅ 해결: 공통 모듈 분리
// Common.js
export class Common { }
// A.js
import { Common } from './Common.js';
export class A extends Common { }
// B.js
import { Common } from './Common.js';
export class B extends Common { }
3. this 바인딩 문제
콜백 함수로 메서드를 전달할 때 this 바인딩에 주의해야 합니다.
// ❌ this가 undefined
this.cardView.render(tests, this.handleCardClick);
// ✅ bind 사용
this.cardView.render(tests, this.handleCardClick.bind(this));
// ✅ 화살표 함수 사용
this.cardView.render(tests, (test) => this.handleCardClick(test));
모듈 패턴의 실전 활용
프로젝트가 성장하면서 모듈 시스템의 진가를 실감했습니다. 새로운 View를 추가할 때 기존 코드를 전혀 수정하지 않고 새 파일만 만들면 됐습니다.
// 새로운 View 추가
// js/views/ChartView.js
export class ChartView {
constructor(containerId) {
this.container = document.getElementById(containerId);
}
render(data) {
// 차트 렌더링 로직
}
}
// Controller에서 사용
import { ChartView } from '../views/ChartView.js';
this.chartView = new ChartView('chart-container');
this.chartView.render(chartData);
"모듈 시스템은 단순히 코드를 파일로 나누는 것이 아니라, 애플리케이션의 구조를 논리적으로 설계하는 도구입니다."
💡 핵심 요약
- ES6 모듈은 export/import로 코드를 구조화합니다
- type="module" 속성과 로컬 서버가 필요합니다
- 파일 확장자(.js)를 반드시 명시해야 합니다
- 동적 import로 성능을 최적화할 수 있습니다
- 순환 의존성과 this 바인딩에 주의하세요