본문으로 건너뛰기
김신건의 로그

[JavaScript] import / export

· 수정 · 📖 약 2분 · 646자/단어 #javascript #module #import #export #esm
js-import-export, JavaScript import, JavaScript export, import statement, export statement, named import, default import, dynamic import, re-export, namespace import

정의

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">
  • import in 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;

주의:

  • __dirnameimport.meta.dirname
  • require.resolveimport.meta.resolve
  • require(...)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) 계층으로 지원합니다.…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기