[JavaScript] import / export
정의
import / export 는 ES2015 (ES6) 에서 도입된 정적 모듈 문법. 파일 간 값 (함수, 클래스, 상수) 공유의 표준 방법입니다.
CommonJS 의 require / module.exports 와 다른 근본 차이:
- 정적 분석 가능 (top-level 만)
- 비동기 로딩 지원
- Tree shaking 자연스러움
자세한 차이는 CJS vs ESM 참조.
Export 문법
Named Export
여러 값을 이름으로 내보냄:
// math.js
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Vector { /* ... */ }
// 또는 한 번에
const PI = 3.14159;
function add(a, b) { return a + b; }
class Vector { /* ... */ }
export { PI, add, Vector };
// 이름 변경 (rename)
export { add as sum };
Default Export
파일당 하나의 기본 export:
// user.js
export default class User {
constructor(name) { this.name = name; }
}
// 또는
class User { /* ... */ }
export default User;
// 즉시 표현식
export default function() { return 42; }
주의: default export 는 하나만. Named 와 병존 가능.
Re-export (Barrel)
다른 모듈의 export 를 다시 내보냄:
// index.js (barrel)
export { add, sub } from './math.js';
export { default as User } from './user.js';
export * from './utils.js';
export * as api from './api.js'; // namespace re-export
주의: Barrel 파일은 tree-shaking 저해 가능. 큰 앱은 주의.
Import 문법
Named Import
import { add, PI } from './math.js';
add(1, 2);
console.log(PI);
// 이름 변경
import { add as sum } from './math.js';
sum(1, 2);
Default Import
import User from './user.js';
const u = new User('Alice');
// Named 와 함께
import User, { UserRole } from './user.js';
Namespace Import
모든 export 를 하나의 객체로:
import * as math from './math.js';
math.add(1, 2);
math.PI;
Side-Effect Import
값 없이 실행만:
import './polyfill.js';
import './styles.css';
용도: 폴리필, CSS/SCSS (번들러가 처리), auto-registration.
Dynamic Import
표현식 으로 import (조건부, 지연 로딩):
const module = await import('./heavy.js');
module.default();
// 조건부
if (userNeedsAdmin) {
const { AdminPanel } = await import('./admin.js');
new AdminPanel();
}
// 번들러가 code splitting
button.onclick = async () => {
const { showDialog } = await import('./dialog.js');
showDialog();
};
Promise 반환. top-level 이 아니어도 됨.
Import 규칙
Top-Level 만
정적 import 는 반드시 파일 최상위:
// 오류
if (condition) {
import { foo } from './mod.js'; // ❌
}
// OK - dynamic import
if (condition) {
const { foo } = await import('./mod.js');
}
확장자 (Node ESM)
Node.js ESM 은 확장자 명시 필수:
import { foo } from './mod.js'; // ✅
import { foo } from './mod'; // ❌ (Node ESM)
번들러 (Vite, Webpack) 는 관용적으로 생략 허용. TypeScript 는 moduleResolution 에 따라.
순환 참조
// a.js
import { b } from './b.js';
export const a = () => b();
// b.js
import { a } from './a.js';
export const b = () => a();
ESM 은 live binding 이라 실행 순서에 따라 동작. 하지만 사용 시점에 정의 안 됐으면 undefined.
Live Binding
CJS 는 값 복사, ESM 은 live reference:
// counter.js
export let count = 0;
export function inc() { count++; }
// main.js
import { count, inc } from './counter.js';
console.log(count); // 0
inc();
console.log(count); // 1 (자동 반영)
CJS 라면 count 는 import 시점 값 (0) 유지.
Import Attributes (ES2025+)
JSON, CSS 등 특수 유형 명시:
import config from './config.json' with { type: 'json' };
import styles from './styles.css' with { type: 'css' };
이전 assert 문법에서 with 로 변경.
Import.meta
모듈 자체 메타 정보:
console.log(import.meta.url); // 파일 URL
console.log(import.meta.dirname); // Node 20.11+
console.log(import.meta.filename); // Node 20.11+
// Vite
if (import.meta.env.DEV) { }
if (import.meta.hot) { /* HMR */ }
Tree Shaking
Named import 는 정적 분석 가능 → 번들러가 미사용 export 제거:
// utils.js
export function used() {}
export function unused() {}
// main.js
import { used } from './utils.js';
used();
// bundle: unused() 제거
Namespace import 는 tree shaking 어려움 (모두 사용 취급):
import * as utils from './utils.js';
utils.used();
// 번들러가 unused 를 못 지울 수도 있음
Default + module.exports: CJS 스타일은 정적 분석 어려움.
Node vs 브라우저
Node.js
.mjs또는"type": "module"package.json- 파일 확장자 필수
- Node 22.12+ 는 CJS 에서
require(esm)가능
브라우저
<script type="module" src="main.js">importin browser 는 절대 경로 or 완전 URL- CORS 적용
- 캐시는 module map
Bundler
- 확장자 관대
node_modules해석- 각 파일 유형 loader/plugin
마이그레이션 팁 (CJS → ESM)
// CJS
const { readFile } = require('fs');
module.exports = { foo, bar };
module.exports.baz = baz;
// ESM
import { readFile } from 'node:fs/promises';
export { foo, bar };
export const baz = baz;
주의:
__dirname→import.meta.dirnamerequire.resolve→import.meta.resolverequire(...)→await import(...)(동적)- Top-level await 사용 가능 (ESM)
함정
WARNING
Node ESM 에서 확장자 필수. import './util' 이 아니라 import './util.js'.
CAUTION
Default export 는 리팩터링 어려움. Named 가 rename 안전.
WARNING
Barrel 파일 (index.js re-export) 남용 → tree shaking 저해 + 순환 참조 위험.
IMPORTANT
Dynamic import 는 Promise. await 또는 .then().
CAUTION
Namespace import (import * as) 는 tree shaking 저해 가능. 필요한 것만 named import.
관련 위키
이 글의 용어 (5개)
- [JavaScript] Bundling (번들링 개요)javascript
- 정의 JavaScript 번들링 (Bundling) 은 여러 소스 파일 (JS, CSS, 이미지, JSON 등) 을 브라우저나 런타임이 효율적으로 실행할 수 있는 최소한의 결과물…
- [JavaScript] CommonJS vs ESM (package.json 설정 완전 정복)javascript
- 정의 JavaScript 는 두 개의 서로 다른 모듈 시스템 을 가집니다. - CommonJS (CJS): Node.js 초기 (2009) 부터 사용. / . 동기 로딩. - E…
- [Javascript] ES Modules (import / export)javascript
- 정의 ES Modules (ESM) 은 표준 JS 모듈 시스템 (ES6). / 키워드. 브라우저와 Node.js 16+ 모두 지원. export named export defau…
- [Node.js] 런타임 개요javascript
- 정의 Node.js 는 Ryan Dahl 이 2009년 발표한 JavaScript 서버 사이드 런타임 입니다. V8 엔진 (Chrome) + libuv (비동기 I/O) + 표준…
- [TypeScript] Modules (import/export, moduleResolution)typescript
- 정의 TypeScript 모듈 시스템은 ES Modules (ESM) 를 기본 언어 모델로 삼되, CommonJS (CJS) 를 상호운용성 (interop) 계층으로 지원합니다.…
💬 댓글