본문으로 건너뛰기
Advertisement

9.3 번들러 통합 — Vite, webpack, Rollup + TypeScript

번들러와 TypeScript

번들러는 여러 모듈을 하나(또는 여러)의 파일로 묶어주는 도구입니다. TypeScript와 함께 사용하려면 TS → JS 변환 설정이 필요합니다.

번들러TS 처리 방식특징
Viteesbuild (내장)빠른 개발 서버, HMR
webpackts-loader / babel-loader성숙한 생태계, 설정 복잡
Rollup@rollup/plugin-typescript라이브러리 번들링에 최적
Parcel내장 (자동 감지)무설정, 초보자 친화

Vite + TypeScript

프로젝트 생성

npm create vite@latest my-app -- --template react-ts
# 또는 Vue
npm create vite@latest my-app -- --template vue-ts
# 또는 바닐라 TS
npm create vite@latest my-app -- --template vanilla-ts

생성된 tsconfig 구조

// tsconfig.json (앱 코드용)
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true, // Vite가 번들링하므로 tsc는 emit 안 함
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
// tsconfig.node.json (vite.config.ts 등 Node.js 도구용)
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

vite.config.ts 주요 설정

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
plugins: [react()],

// 경로 별칭
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},

// 빌드 설정
build: {
target: 'es2020',
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},

// 환경변수 접두사
envPrefix: 'VITE_',
});

Vite에서 환경변수 타입 정의

// src/vite-env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
// 사용
const apiUrl = import.meta.env.VITE_API_URL; // string

webpack + TypeScript

설치

npm install --save-dev webpack webpack-cli ts-loader typescript

webpack.config.ts

import path from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';

const config: webpack.Configuration = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
optimization: {
splitChunks: {
chunks: 'all',
},
},
};

export default config;

ts-loader vs babel-loader 비교

ts-loader (권장: 타입 체크 포함):

{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
}

babel-loader + @babel/preset-typescript (타입 체크 없음, 빠름):

npm install --save-dev babel-loader @babel/core @babel/preset-typescript @babel/preset-env
{
test: /\.tsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-typescript',
['@babel/preset-react', { runtime: 'automatic' }],
],
},
},
}

ts-loader 옵션 최적화

{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true, // 타입 체크 건너뜀 (빠름)
// 타입 체크는 fork-ts-checker-webpack-plugin이 별도 수행
},
},
],
}
npm install --save-dev fork-ts-checker-webpack-plugin
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

{
plugins: [
new ForkTsCheckerWebpackPlugin(), // 별도 프로세스에서 타입 체크
],
}

Rollup + TypeScript

라이브러리 배포에 적합합니다. Tree-shaking이 뛰어납니다.

설치

npm install --save-dev rollup @rollup/plugin-typescript rollup-plugin-dts

rollup.config.ts

import { defineConfig } from 'rollup';
import typescript from '@rollup/plugin-typescript';
import dts from 'rollup-plugin-dts';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default defineConfig([
// 메인 빌드 (CJS + ESM 동시 출력)
{
input: 'src/index.ts',
external: ['react', 'react-dom'], // peer dependency 제외
output: [
{
file: 'dist/index.cjs',
format: 'cjs',
sourcemap: true,
},
{
file: 'dist/index.mjs',
format: 'es',
sourcemap: true,
},
],
plugins: [
resolve(),
commonjs(),
typescript({ tsconfig: './tsconfig.json' }),
],
},
// 타입 선언 파일 번들
{
input: 'dist/types/index.d.ts',
output: [{ file: 'dist/index.d.ts', format: 'es' }],
plugins: [dts()],
},
]);

package.json 라이브러리 설정

{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "rollup -c rollup.config.ts --configPlugin typescript",
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"react": ">=18"
}
}

Parcel + TypeScript

설정 없이 TypeScript를 사용할 수 있습니다.

npm install --save-dev parcel
{
"scripts": {
"dev": "parcel src/index.html",
"build": "parcel build src/index.html"
}
}

Parcel은 tsconfig.json을 자동으로 인식합니다. 소규모 프로젝트나 프로토타이핑에 적합합니다.


isolatedModules 옵션

번들러와 함께 사용할 때 중요한 옵션입니다.

{
"compilerOptions": {
"isolatedModules": true
}
}

각 파일을 독립적으로 트랜스파일할 수 있도록 강제합니다. esbuild, swc, Babel은 파일 단위로 처리하기 때문에 이 옵션이 필요합니다.

// isolatedModules 위반 패턴들

// ❌ const enum은 다른 파일에서 인라인 불가
const enum Direction { Up, Down } // 오류

// ❌ 타입 재export는 'export type' 필요
export { SomeType }; // 오류
export type { SomeType }; // ✅

// ❌ 네임스페이스는 타입만 포함해야 함
namespace MyNs {
export const value = 1; // 오류 (런타임 값 포함)
}

번들 최적화 공통 전략

Tree-shaking 활성화

// ✅ Named export 사용 (tree-shaking 가능)
export function add(a: number, b: number) { return a + b; }
export function subtract(a: number, b: number) { return a - b; }

// ❌ default export 객체 (tree-shaking 어려움)
export default { add, subtract };

동적 import로 코드 분할

// 필요할 때 로드 (lazy loading)
const { PDFViewer } = await import('./components/PDFViewer');

// React에서
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

고수 팁

번들러 선택 가이드

신규 React/Vue 프로젝트    → Vite (가장 빠른 개발 경험)
레거시 CRA 프로젝트 → webpack (기존 설정 유지)
라이브러리 배포 → Rollup (최적의 tree-shaking)
서버사이드 Node.js → esbuild 직접 사용 (가장 빠름)
프로토타입/소규모 → Parcel (무설정)

tsc + 번들러 역할 분리 원칙

tsc:     타입 체크만 (noEmit: true)
번들러: 트랜스파일 + 번들링 + 최적화
Advertisement