TypeScript 모듈 시스템과 Webpack 실전 설정 – “코드가 어떻게 실행되는지”부터 잡기
ESM과 Webpack 실행 모델, ts-loader 역할, import/export 패턴과 번들 전략을 실무 관점에서 정리
Posted
By okorion
TypeScript 모듈 시스템과 Webpack 실전 설정 – “코드가 어떻게 실행되는지”부터 잡기
결론: 모듈과 번들링을 이해하지 못하면 “돌아가는 코드”는 만들어도 “예측 가능한 코드”는 만들 수 없다
TypeScript에서 모듈과 Webpack은 선택 기술이 아니라 실행 모델을 통제하기 위한 필수 지식이다. 이 글의 핵심은 문법이 아니라, 코드가 어떻게 연결되고, 언제 실행되며, 어떤 형태로 배포되는지를 명확히 이해하는 데 있다.
Namespace가 레거시가 되는 이유는 “실행 단위”를 분리하지 못하기 때문이다
결론: Namespace는 파일을 나눌 뿐, 실행 경계를 만들지 못한다.
Namespace의 본질
- 전역 스코프를 나누기 위한 과거 TS 전용 문법
- 하나의 JS 파일(또는 전역 공간)에 모두 합쳐짐
1
2
3
4
5
namespace Utils {
export function sum(a: number, b: number) {
return a + b;
}
}
한계
- 파일 단위 의존성 그래프가 없다
- lazy loading, tree shaking 불가
- 표준 아님 (TS 전용)
ES Module이 표준인 이유
- 파일 = 실행 단위
- import/export로 의존성 명시
- 브라우저, Node, 번들러 공통 표준
1
2
3
4
// sum.ts
export function sum(a: number, b: number) {
return a + b;
}
import/export 패턴은 “편의”가 아니라 “유지보수 비용”의 문제다
결론: import 스타일은 팀 규모가 커질수록 비용 차이가 난다.
기본 export
1
2
3
export default function sum(a: number, b: number) {
return a + b;
}
- ✔ 단일 책임 모듈
- ❌ 이름 변경이 쉬워 추적 어려움
명명 export (권장)
1
2
3
export function sum(a: number, b: number) {
return a + b;
}
- ✔ 명확한 API
- ✔ 자동완성/리팩터링 안전
배럴(barrel) 패턴
1
2
3
// index.ts
export * from "./sum";
export * from "./multiply";
주의:
- 대규모 프로젝트에서 배럴 남용 → 순환 참조, 번들 비대화
Webpack은 “파일을 하나로 합치는 도구”가 아니다
결론: Webpack의 핵심은 “의존성 그래프 기반 실행 통제”다.
Webpack이 해결하는 문제
- 여러 파일을 하나의 번들로 묶음
- import/export 기반 의존성 그래프 생성
- 개발 서버(dev-server) 제공
- 코드 분할, 캐싱, 최적화
Webpack이 없으면 생기는 문제
- 브라우저에서 모듈 로딩 순서 직접 관리
- 다수의 네트워크 요청
- 구형 브라우저 대응 어려움
ts-loader는 “타입 체크 도구”가 아니라 “변환기”다
결론: 번들링과 타입 체크는 완전히 다른 단계다.
빌드 파이프라인 개념
1
2
3
4
5
TypeScript
↓ (ts-loader)
JavaScript
↓ (Webpack)
Bundle
ts-loader의 역할
- TS → JS 변환
- Webpack 파이프라인에 TypeScript 연결
중요한 오해
- ❌ Webpack이 타입을 검사한다
- ❌ 번들 에러 = 타입 에러
→ 타입 체크는 tsc, 번들은 Webpack의 책임
ESM 기반 폴더 구조 예시
결론: 폴더 구조는 “의존성 방향”을 드러내야 한다.
1
2
3
4
5
6
7
8
9
10
src/
├─ index.ts
├─ app/
│ ├─ App.ts
│ └─ app.service.ts
├─ utils/
│ ├─ sum.ts
│ └─ index.ts
└─ types/
└─ common.ts
index.ts: 진입점(entry)- utils/types는 하위에서만 참조
- 순환 참조 방지
Webpack 최소 구성 (개발/운영 분리)
결론: 개발과 운영은 요구사항이 다르다.
webpack.config.js (최소 예시)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import path from "path";
export default {
entry: "./src/index.ts",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
resolve: {
extensions: [".ts", ".js"]
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/
}
]
},
devtool: "source-map"
};
npm scripts
1
2
3
4
5
6
{
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production"
}
}
개발 vs 운영 분리 포인트
- dev: source-map ON, 빠른 빌드
- prod: minify, tree-shaking, 캐시 최적화
흔한 오류 3가지와 해결책
결론: 대부분 “모듈 해석” 문제다.
1️⃣ 경로 오류
1
import { sum } from "./utils"; // ❌ index.ts 없음
해결:
./utils/index.ts존재 확인- barrel 패턴 명확화
2️⃣ 모듈 해석 실패
1
Cannot use import statement outside a module
원인
- module 설정 불일치 (CommonJS vs ESModule)
해결
- tsconfig / webpack module 설정 일치
3️⃣ 타입 선언 문제
1
Cannot find module 'lodash'
원인
- JS 라이브러리 타입 선언 없음
해결
@types/lodash설치- 또는 최소한의
declare module작성
Webpack 없이도 되는 경우 / 꼭 필요한 경우
결론: 번들링 필요 여부는 “실행 환경”이 결정한다.
Webpack 없이도 되는 경우
- Node 18+ (ESM 지원)
- 단순 스크립트/라이브러리
- Vite 같은 상위 툴 사용
꼭 필요한 경우
- 복잡한 프론트엔드 SPA
- 레거시 브라우저 대응
- 커스텀 빌드 파이프라인 필요
체크리스트
- Namespace를 새 코드에 쓰고 있지 않은가?
- import/export 스타일이 팀 기준으로 통일돼 있는가?
- 번들링과 타입 체크를 분리해서 이해하고 있는가?
- Webpack 설정이 실행 환경과 맞는가?
- 순환 참조가 구조적으로 발생하지 않는가?
요약 5줄
- Namespace는 실행 단위를 만들지 못해 레거시가 됐다.
- ES Module은 파일 기반 실행 모델을 제공하는 표준이다.
- Webpack은 번들러이자 의존성 그래프 관리자다.
- ts-loader는 변환기이며, 타입 체크는 별도 단계다.
- 번들링 필요 여부는 프로젝트의 실행 환경이 결정한다.
자기점검 질문 5개
- 내 프로젝트의 “실행 진입점(entry)”은 명확한가?
- import/export 패턴이 유지보수에 불리하게 쓰이고 있지는 않은가?
- 번들 에러와 타입 에러를 구분해서 설명할 수 있는가?
- Webpack 없이도 가능한 구조인데 관성으로 쓰고 있지는 않은가?
- 모듈 의존성 방향이 한쪽으로 흐르고 있는가?
실전 미션 3개
- Namespace로 작성된 코드를 ES Module 구조로 리팩터링하라.
- Webpack 설정에서 source-map ON/OFF 차이를 직접 디버깅으로 확인하라.
- 순환 참조가 발생하는 import 구조 하나를 찾아 분리하라.
