🟨 1-12. 모듈(Module) 시스템 — import/export로 구조적 자바스크립트 설계하기
1. 왜 모듈이 필요한가
프로젝트가 커질수록, 한 파일 안의 코드 길이는 기하급수적으로 늘어난다.
예전에는 이런 식이었다.
// script.js
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
console.log(add(2, 3));
그런데 만약 다른 파일에서 add()를 쓰고 싶다면 어떻게 해야 할까?
이전 자바스크립트(ES5)까지는 이런 “모듈화” 기능이 없어서,
모든 스크립트를 전역(global)으로 불러오는 방식을 썼다.
결과적으로 전역 네임스페이스 충돌이 발생하고,
파일 간 의존성이 꼬이기 시작했다.
이 문제를 해결하기 위해 등장한 것이 바로 모듈 시스템(Module System) 이다.
2. 모듈이란 무엇인가
모듈(Module) 은 독립된 기능을 수행하는 코드 묶음이다.
하나의 파일이 하나의 모듈이 되며,
필요한 부분만 외부로 내보내고(import/export) 다른 파일에서 재사용할 수 있다.
즉, 모듈은 다음 두 가지 역할을 수행한다.
- 캡슐화(Encapsulation) — 내부 로직을 숨기고 필요한 부분만 노출
- 재사용성(Reusability) — 여러 파일에서 동일 기능을 불러서 사용
3. ES6 모듈(ESM) 기본 구조
ES6 이후 자바스크립트는 표준 모듈 문법을 공식적으로 지원한다.
즉, import와 export를 사용할 수 있다.
모듈 내보내기 (export)
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
모듈 가져오기 (import)
// app.js
import { add, multiply } from "./math.js";
console.log(add(2, 3)); // 5
console.log(multiply(3, 4)); // 12
이제 각 기능을 독립적인 파일로 분리하고,
필요한 곳에서만 불러올 수 있다.
4. export의 종류
ES6에는 named export와 default export 두 가지 방식이 있다.
1️⃣ Named Export
여러 개의 함수를 내보낼 때 사용한다.
// utils.js
export const hello = () => console.log("Hello!");
export const bye = () => console.log("Goodbye!");
불러올 때는 이름 그대로 써야 한다.
import { hello, bye } from "./utils.js";
hello();
2️⃣ Default Export
모듈당 하나의 “대표 기능”을 내보낼 때 사용한다.
// logger.js
export default function log(message) {
console.log(`[LOG]: ${message}`);
}
불러올 때는 이름을 마음대로 지정할 수 있다.
import log from "./logger.js";
log("모듈 시스템 시작");
실무에서는 default export는 주요 로직이나 컴포넌트,
named export는 여러 유틸이나 상수 등을 내보낼 때 자주 사용한다.
5. export 혼합 예시
두 가지 방식을 함께 쓰는 것도 가능하다.
// api.js
export const BASE_URL = "https://api.example.com";
export function fetchUser(id) {
return fetch(`${BASE_URL}/users/${id}`).then(res => res.json());
}
export default function fetchAll() {
return fetch(`${BASE_URL}/users`).then(res => res.json());
}
// main.js
import fetchAll, { BASE_URL, fetchUser } from "./api.js";
console.log(BASE_URL);
fetchUser(1).then(console.log);
fetchAll().then(console.log);
이렇게 구조를 나누면, API 로직이 깔끔하게 정리된다.
6. import 시 이름 변경 (aliasing)
때로는 같은 이름의 함수가 여러 파일에 존재할 수 있다.
그럴 땐 as 키워드로 이름을 바꿔서 가져올 수 있다.
import { add as plus } from "./math.js";
console.log(plus(5, 10)); // 15
7. 모든 모듈 한꺼번에 불러오기
* as 문법을 사용하면,
모든 export를 한 객체 형태로 가져올 수 있다.
import * as MathUtils from "./math.js";
console.log(MathUtils.add(1, 2));
console.log(MathUtils.multiply(3, 4));
8. CommonJS와 ES 모듈의 차이
Node.js는 원래 CommonJS(CJS) 시스템을 사용했다.
(지금도 많은 라이브러리가 여전히 이 형식을 쓴다.)
구분 CommonJS ES Module
| 내보내기 | module.exports | export |
| 가져오기 | require() | import |
| 실행 시점 | 런타임 | 컴파일 시점 |
| 확장자 | .cjs, .js | .mjs, .js |
예시 비교 👇
// CommonJS
const fs = require("fs");
module.exports = { fs };
// ES Module
import fs from "fs";
export { fs };
요즘은 Node.js 14+ 버전부터 ESM이 정식 지원되며,
package.json에 "type": "module"을 설정하면 import/export를 그대로 사용할 수 있다.
9. 모듈 경로의 규칙
- 상대 경로 (./, ../) : 현재 파일 기준
- 절대 경로 : 번들러(Webpack, Vite 등)에서 설정 가능
- 확장자는 .js, .mjs, .ts 등 명시적으로 적는 것이 안전하다.
예시 👇
import { func } from "../utils/helper.js";
만약 파일 경로를 잘못 쓰면 “Failed to resolve module” 오류가 발생한다.
이는 대부분 import 경로가 틀렸거나 .js 확장자가 빠진 경우다.
10. 실무에서의 모듈 구조 설계
실제 프로젝트에서는 다음과 같이 폴더 구조를 나눈다.
src/
┣ components/
┃ ┣ Header.js
┃ ┗ Footer.js
┣ utils/
┃ ┗ formatDate.js
┣ api/
┃ ┣ fetchUser.js
┃ ┗ fetchPost.js
┗ main.js
- components/ : 화면(UI) 구성
- api/ : 서버 통신 로직
- utils/ : 재사용 가능한 함수들
- main.js : 진입점(entry point)
이 구조는 React, Vue, Next.js 등 거의 모든 프레임워크의 기본 설계 철학과 같다.
파일을 기능 단위로 분리하고, import/export로 연결한다.
11. 번들러와 모듈 시스템
브라우저는 원래 import/export를 지원하지 않았다.
그래서 Webpack, Vite, Rollup, Parcel 같은 번들러가 등장했다.
이 도구들은 여러 모듈을 하나의 파일로 묶어서 브라우저가 읽을 수 있게 만든다.
또한 트리 셰이킹(Tree Shaking, 사용하지 않는 코드 제거) 기능으로
최종 번들 크기를 줄인다.
12. 정리
개념 설명 비고
| 모듈 | 코드 재사용 단위 | 파일 기반 구조 |
| export | 외부로 기능 공개 | named / default |
| import | 다른 파일에서 불러오기 | 구조적 코드 |
| CommonJS | Node.js 전통 방식 | require / module.exports |
| ES Module | 현대 JS 표준 | import / export |
| 번들러 | 모듈을 묶는 도구 | Webpack, Vite 등 |
13. 마무리
모듈 시스템을 이해하면
코드를 ‘파일 단위’가 아니라 ‘기능 단위’로 바라보게 된다.
이제 더 이상 “한 파일 안에 모든 함수를 몰아넣는” 일은 없다.
대신 “이 파일은 어떤 역할을 담당하고 있는가?”를 기준으로 구조를 설계하게 된다.
코드의 크기가 아니라 의존 관계의 명확함이 프로젝트의 품질을 결정한다.
다음 편에서는
1-13. ES6 클래스(Class)와 프로토타입(Prototype) — 객체지향 자바스크립트의 핵심 구조
를 통해 자바스크립트가 어떻게 객체를 만들고 상속을 구현하는지,
그리고 class 문법이 실제로 어떻게 동작하는지 깊이 있게 다뤄보자.