| name | bundling-compiler |
| description | tsup/Vite/Turbopack 번들러 선택 기준, React Compiler, Tree Shaking, 코드 스플리팅 패턴 |
| disable-model-invocation | true |
번들링 & 컴파일러 패턴
소스: https://tsup.egoist.dev | https://vitejs.dev | https://nextjs.org/docs | https://react.dev/learn/react-compiler
검증일: 2026-06-20
번들러 선택 기준
프로젝트 타입?
├─ 라이브러리 (npm 배포용)
│ └─ 단순 TS/JS → tsup (추천)
│
└─ 애플리케이션
├─ Next.js → Turbopack (내장, 기본값)
└─ SPA/기타 → Vite
| 도구 | 용도 | Dev 속도 | 설정 복잡도 |
|---|
| tsup | 라이브러리 빌드 | - | ⭐ 매우 간단 |
| Vite | SPA 앱 | ⭐⭐⭐ 빠름 | ⭐⭐ |
| Turbopack | Next.js 앱 | ⭐⭐⭐⭐ 매우 빠름 | ⭐⭐ (내장) |
tsup (라이브러리 빌드)
기본 설정
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
sourcemap: true,
splitting: false,
treeshake: true,
})
다중 Entry 패턴
export default defineConfig({
entry: {
index: 'src/index.ts',
utils: 'src/utils/index.ts',
types: 'src/types/index.ts',
},
format: ['esm', 'cjs'],
dts: true,
outExtension({ format }) {
return { js: format === 'esm' ? '.mjs' : '.cjs' }
},
})
package.json exports 설정
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"sideEffects": false
}
Vite (SPA 앱 빌드)
기본 설정
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': path.resolve(__dirname, './src') }
},
build: {
outDir: 'dist',
sourcemap: true,
},
server: {
port: 3000,
}
})
라이브러리 모드
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'MyLibrary',
formats: ['es', 'cjs'],
fileName: (format) => `my-library.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: { react: 'React', 'react-dom': 'ReactDOM' }
}
}
}
})
Turbopack (Next.js 내장)
활성화 방법
const nextConfig = {}
export default nextConfig
const nextConfig = {
turbopack: {
},
}
Webpack vs Turbopack
| 항목 | Webpack | Turbopack |
|---|
| 언어 | JavaScript | Rust |
| Dev 시작 | 느림 | 매우 빠름 |
| HMR | 느림 | 거의 즉각 |
| 플러그인 호환 | 전체 | 제한적 (재구현 필요) |
| 프로덕션 빌드 | 안정 | Next.js 16에서 안정화 (기본값) |
Turbopack 제약: 일부 Webpack 플러그인 미지원 → 대안 확인 필요
React Compiler (자동 메모이제이션)
v1.0 안정화: 2025년 10월 | 출처: https://react.dev/learn/react-compiler
활성화
const nextConfig = {
reactCompiler: true,
}
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
})
import react, { reactCompilerPreset } from '@vitejs/plugin-react'
import babel from '@rolldown/plugin-babel'
export default defineConfig({
plugins: [react(), babel(reactCompilerPreset())],
})
React Compiler 활성화 시 변경되는 것
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
)
const handleClick = useCallback(() => onSelect(id), [onSelect, id])
const MemoizedComponent = memo(MyComponent)
const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name))
const handleClick = () => onSelect(id)
React Compiler 제약사항
function BadComponent() {
if (someCondition) {
const [state, setState] = useState(0)
}
}
function GoodComponent({ condition }: { condition: boolean }) {
const [state, setState] = useState(0)
return condition ? <div>{state}</div> : null
}
Vanilla Extract (빌드타임 CSS)
Next.js 설정
import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin'
const withVanillaExtract = createVanillaExtractPlugin()
export default withVanillaExtract({
})
사용 패턴
import { style, styleVariants } from '@vanilla-extract/css'
export const base = style({
display: 'flex',
padding: '12px',
})
export const variants = styleVariants({
primary: { backgroundColor: 'blue', color: 'white' },
secondary: { backgroundColor: 'gray', color: 'black' },
})
import { base, variants } from './styles.css'
function Button({ variant = 'primary' }: { variant: keyof typeof variants }) {
return <button className={`${base} ${variants[variant]}`}>Click</button>
}
특징: 빌드 타임에 static CSS 생성 → 런타임 오버헤드 없음
주의: @vanilla-extract/css의 webpack 플러그인은 현재 Turbopack에서 미지원. Turbopack 기반 Next.js 환경에서는 사용 불가.
Tree Shaking
핵심 원칙
{
"sideEffects": false
}
{
"sideEffects": ["**/*.css", "./src/polyfills.ts"]
}
ESM vs CJS
export function add(a: number, b: number) { return a + b }
export function subtract(a: number, b: number) { return a - b }
import { add } from '@myorg/utils'
module.exports = { add, subtract }
배럴(Barrel) 파일 주의
export * from './heavy-module'
export { HeavyComponent } from './heavy-module'
import { HeavyComponent } from '@myorg/ui/heavy-module'
코드 스플리팅
React lazy + Suspense
import { lazy, Suspense } from 'react'
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
)
}
Next.js dynamic
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('../components/Chart'), {
ssr: false,
loading: () => <ChartSkeleton />,
})
const PDFViewer = dynamic(() => import('../components/PDFViewer'))
스플리팅 전략
라우트 단위 스플리팅: 필수 (페이지별 독립 청크)
컴포넌트 단위 스플리팅: 선택적 (무거운 컴포넌트만)
라이브러리 단위 스플리팅: 자동 (번들러가 처리)
❌ 피해야 할 것: 너무 잘게 쪼개기 (HTTP 요청 오버헤드)
✅ 기준: 30KB 이상인 컴포넌트 또는 특정 조건에서만 사용하는 컴포넌트