ES 모듈 시스템의 named export와 default export를 구분하고, import 다양한 문법을 익힌다. CommonJS와의 차이, 동적 import, 번들러의 역할을 이해한다.
모듈 — 코드를 파일 단위로 나누기
모듈(Module)은 코드를 논리적 단위로 분리하는 시스템입니다. 모든 변수와 함수를 하나의 파일에 쓰면 충돌, 유지보수 어려움이 생깁니다. 모듈을 사용하면 파일별로 독립된 스코프를 가지고, 필요한 것만 외부에 공개할 수 있습니다.
자바스크립트는 두 가지 모듈 시스템이 공존합니다: CommonJS(CJS)와 ES Module(ESM).
ES 모듈 기본 — export와 import
// utils/math.js
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export const PI = 3.14159;
// utils/string.js
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// main.js
import { add, PI } from "./utils/math.js";
import { capitalize } from "./utils/string.js";
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
Named Export vs Default Export
// Named Export — 여러 개 내보내기 가능
export const name = "Kim";
export function greet() { ... }
export class User { ... }
// 한번에 묶어서 내보내기
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
export { add, subtract };
// 별칭으로 내보내기
export { add as sum, subtract as minus };
// Default Export — 파일당 하나만 가능
export default function fetchUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// 또는
function fetchUser(id) { ... }
export default fetchUser;
다양한 import 문법
// Named import
import { add, subtract } from "./math.js";
// 별칭으로 import
import { add as sum, subtract as minus } from "./math.js";
// 전체를 네임스페이스로 import
import * as MathUtils from "./math.js";
MathUtils.add(2, 3); // 5
MathUtils.PI; // 3.14159
// Default import (이름은 자유롭게)
import fetchUser from "./api.js";
import getUser from "./api.js"; // 같은 것, 이름만 다름
// Named + Default 동시 import
import fetchUser, { BASE_URL, handleError } from "./api.js";
// 사이드 이펙트만 실행 (반환값 없음)
import "./polyfills.js";
import "./global-styles.css";
재내보내기 (Re-export) — 배럴 파일 패턴
// utils/index.js — 하위 모듈들을 한 곳에서 내보내기
export { add, subtract, PI } from "./math.js";
export { capitalize, truncate } from "./string.js";
export { formatDate, parseDate } from "./date.js";
export { default as fetchUser } from "./api.js";
// 사용하는 쪽
import { add, capitalize, formatDate, fetchUser } from "./utils";
// 내부 구조 알 필요 없이 utils에서 모두 가져옴
CommonJS — Node.js의 전통 방식
// CommonJS (Node.js, .cjs 파일)
const fs = require("fs");
const { join } = require("path");
module.exports = {
add: (a, b) => a + b,
PI: 3.14159
};
// 또는
module.exports = function fetchUser(id) { ... };
// ESM과 CJS 주요 차이
// CJS: 동기적, require는 어디서나 호출 가능
// ESM: 정적 분석 가능, 트리 쉐이킹 최적화
// ESM: 비동기 로딩 지원, import는 최상위에서만
동적 import — 코드 분할
// 정적 import는 항상 파일 상단에 위치
// 동적 import는 조건부, 지연 로딩 가능
// 버튼 클릭 시에만 무거운 모듈 로드
button.addEventListener("click", async () => {
const { default: Chart } = await import("./chart.js");
const chart = new Chart(container, data);
});
// 조건부 로드
async function loadLocale(lang) {
const messages = await import(`./locales/${lang}.js`);
return messages.default;
}
// 여러 모듈 동적 로드
const [moduleA, moduleB] = await Promise.all([
import("./moduleA.js"),
import("./moduleB.js")
]);
번들러 — Vite, Webpack, esbuild
브라우저는 아직 모든 ESM 기능(경로 별칭, npm 패키지 직접 import 등)을 완벽히 지원하지 않습니다. 번들러는 여러 모듈 파일을 하나(또는 몇 개)의 파일로 합쳐줍니다.
번들러의 역할:
1. import/export 분석 → 의존성 그래프 구성
2. 여러 파일을 하나로 합치기 (번들링)
3. 사용하지 않는 코드 제거 (트리 쉐이킹)
4. TypeScript, JSX, CSS 변환 (트랜스파일링)
5. 코드 최소화 (미니피케이션)
6. 개발 서버 + HMR(핫 모듈 교체)
주요 번들러:
- Vite (현재 가장 빠름, Rollup 기반)
- Webpack (가장 오래됨, 설정 복잡)
- esbuild (Go 기반, 초고속)
- Parcel (설정 없이 사용)
package.json "type" 필드
{
"type": "module" // .js 파일을 ESM으로 처리
}
// "type": "commonjs" (기본값) — .js 파일을 CJS로 처리
// 개별 파일 강제:
// .mjs → 항상 ESM
// .cjs → 항상 CJS
모듈 설계 원칙
// 1. 단일 책임: 한 모듈은 하나의 역할
// auth.js — 인증 관련만
// api.js — API 호출만
// utils.js — 유틸리티 함수들
// 2. 명확한 공개 API
// 내부에서만 쓰는 것은 export 하지 않음
// 3. 순환 의존성 주의
// a.js → b.js → a.js (순환)는 예상치 못한 버그 유발
// 4. index.js 배럴 파일로 폴더 단위 캡슐화
// components/Button/Button.jsx
// components/Button/index.js → export { default } from "./Button"
// import Button from "./components/Button" (내부 구조 숨김)
관련 검색어 · 자바스크립트 모듈, import export 문법, named vs default export, 동적 import, CommonJS ESM 차이, 번들러란