모듈 — import·export·번들러
ESM vs CommonJS, default vs named, 번들러 한 줄.
코드가 한 파일을 넘어가면 모듈이 필요합니다. JS 의 모듈은 역사상 두 가지가 공존 — ESM(현재 표준) 과 CommonJS(Node 의 옛 표준). 19편은 두 모양의 차이와, 그 위에 번들러가 무엇을 하는지 정리합니다.
ESM — 현재 표준
// math.js
export function add(a, b) { return a + b; }
export const PI = 3.14159;
// 한꺼번에
export { add, PI };
// default — 모듈의 메인 export
export default function main() { ... }
// app.js
import main, { add, PI } from "./math.js";
// ^ default ^ named
import 모든 형태
import { a, b } from "./x.js"; // named
import { a as alias } from "./x.js"; // 별칭
import x from "./x.js"; // default
import x, { a } from "./x.js"; // default + named
import * as X from "./x.js"; // 네임스페이스
import "./x.js"; // side-effect (CSS·polyfill)
// 동적 import — 코드 분할
const m = await import("./x.js");
m.add(1, 2);
CommonJS — Node 의 옛 표준
// math.cjs
function add(a, b) { return a + b; }
const PI = 3.14159;
module.exports = { add, PI };
// 또는 default 비슷
module.exports = function main() { ... };
// app.cjs
const { add, PI } = require("./math.cjs");
const main = require("./math.cjs");
ESM vs CommonJS — 한 표
| ESM | CommonJS | |
|---|---|---|
| 문법 | import/export | require/module.exports |
| 로딩 | 정적 (트리 셰이킹 가능) | 동적 (런타임) |
| 상위 await | 가능 | 불가 |
| 브라우저 | 표준 (script type="module") | 번들러 필요 |
| 파일 확장자 | .mjs 또는 type:module | .cjs 또는 기본 |
package.json — type 필드
{
"name": "my-pkg",
"type": "module", // ← 이 줄: .js 파일이 ESM 으로 해석됨
"main": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}
2026년 새 프로젝트는 거의 "type": "module". CJS 와 호환이 필요한 라이브러리는 exports 로 두 가지를 모두 제공.
브라우저에서 ESM 직접 사용
<script type="module" src="./app.js"></script>
// app.js (브라우저)
import { greet } from "./greet.js"; // 확장자 필수
greet();
// CDN 으로 라이브러리 (esm.sh, unpkg)
import { z } from "https://esm.sh/zod@3";
브라우저 ESM 의 한계. ① 네트워크에 요청이 파일마다 따로 (작은 모듈 수십 개 → 느림). ② 확장자·경로 변환 없음. 실제 서비스는 거의 항상 번들러로 한 파일(또는 청크 몇 개) 로 묶음.
번들러 — esbuild·vite·webpack
# Vite — 2026년 표준
npm create vite@latest my-app
# → React/Vue/Svelte 등 템플릿 선택
# 개발: vite (dev server + HMR)
# 빌드: vite build → 최적화된 정적 파일
# esbuild — 단순 빌더
npm install -D esbuild
npx esbuild app.js --bundle --outfile=dist.js --minify
# webpack — 옛 표준 (가장 많은 옵션, 가장 복잡)
# tsup — 라이브러리 발행용 (esbuild 위)
2026년 선택지. ① 앱 — Vite (모든 프레임워크 호환, dev/build 빠름). ② 라이브러리 — tsup(esbuild 기반, ESM+CJS 동시 출력). ③ 모노레포·극도 커스터마이즈 — webpack/turbopack. webpack 은 새로 시작할 이유 거의 없음.
트리 셰이킹 — 안 쓰는 코드 제거
// util.js
export function used() { ... }
export function unused() { ... } // 안 쓰는 함수
// app.js
import { used } from "./util.js";
used();
// 번들 결과 → unused 가 사라짐 (트리 셰이킹)
// ESM 만 가능 (CJS 는 동적이라 불가)
코드 분할 — dynamic import
// 무거운 라이브러리는 필요할 때만 로드
btn.addEventListener("click", async () => {
const { default: Chart } = await import("./chart-lib.js");
new Chart(...);
});
// React lazy
const Heavy = React.lazy(() => import("./Heavy.js"));
실전 — 모듈 디자인 가이드
- 한 파일 한 책임. 100~300줄 정도가 보통 좋은 크기.
- default 보다 named. 자동완성·검색·리팩터링 친화.
- 외부에는 작은 API.
index.js에서 필요한 것만 re-export. - 순환 import 피하기. A → B → A 는 undefined 위험.
- side-effect import 최소화. 트리 셰이킹을 위해
sideEffects: false(package.json).
순환 import 의 함정. A 가 B 를 import, B 가 A 를 import. ESM 은 부분 초기화 상태로 진행해서 undefined 가 들어갈 수 있음. 보통 한 쪽을 분리하거나 공통 모듈로 추출해 해결.
20편 — 정규식 기초
이메일·전화번호 검증, 그룹과 캡처.
📚 쉽게 배우는 자바스크립트 교재
이전: 18편 클래스 · 현재: 19편 (중급) · 다음 → 20편 정규식 · 진행: 19/26
이전: 18편 클래스 · 현재: 19편 (중급) · 다음 → 20편 정규식 · 진행: 19/26