| name | webpack-vite-config-mapping |
| description | Webpack/Craco 설정을 Vite로 1:1 매핑하는 패턴. cacheGroups→manualChunks, Babel 플러그인, Webpack 플러그인 대응표, topLevelAwait, HTTPS 개발 서버 |
Webpack/Craco → Vite 설정 매핑
소스: https://vitejs.dev/config/ | https://vitejs.dev/guide/api-plugin | https://craco.js.org/docs/configuration/webpack/
검증일: 2026-08-11 (최초 작성 2026-04-20, Vite 8 대응 주의사항 추가)
주의 (Vite 8+): Vite 8부터 Rolldown이 기본 번들러로 전환되면서 build.rollupOptions는 build.rolldownOptions로 개명됨(기존 rollupOptions는 deprecated alias로 하위호환 유지, 당장 깨지지 않음). output.manualChunks 객체 형식은 더 이상 지원되지 않음(함수 형식은 deprecated로 계속 동작). 아래 예시는 Vite 6.x 기준 — Vite 8+ 환경이면 객체 형식 대신 함수형을 쓰거나 rolldownOptions.output.codeSplitting 전환을 검토할 것. 출처: https://vite.dev/guide/migration
배경: Craco는 CRA의 webpack 설정을 커스터마이징하는 래퍼. CRA deprecated(2025-02)와 함께 Craco도 maintenance-only 상태. 이 스킬은 craco.config.js의 각 설정을 vite.config.ts로 1:1 매핑한다.
craco.config.js → vite.config.ts 전체 구조 대응
craco.config.js vite.config.ts
─────────────────────────────────────────────────────
webpack.configure → build.rollupOptions
webpack.plugins → plugins[]
babel.plugins → (별도 처리, 아래 참조)
devServer.proxy → server.proxy
devServer.port / host / https → server.port / host / https
1. cacheGroups → manualChunks
Webpack cacheGroups (craco.config.js)
module.exports = {
webpack: {
configure: (webpackConfig) => {
webpackConfig.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
'common-react': {
name: 'common-react',
test: /[\\/]node_modules[\\/](react-hook-form|react-scroll)[\\/]/,
priority: 20,
},
'common-swiper': {
name: 'common-swiper',
test: /[\\/]node_modules[\\/]swiper[\\/]/,
priority: 20,
},
'vendors-sentry': {
name: 'vendors-sentry',
test: /[\\/]node_modules[\\/]@sentry[\\/]/,
priority: 20,
},
},
}
return webpackConfig
},
},
}
Vite manualChunks 대응 (vite.config.ts)
import { defineConfig } from 'vite'
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'common-react': ['react-hook-form', 'react-scroll', 'react-helmet-async'],
'common-swiper': ['swiper'],
'vendors-sentry': ['@sentry/react', '@sentry/tracing'],
'common-react-dom': ['react', 'react-dom'],
},
},
},
},
})
함수형 manualChunks (패키지 자동 분할)
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (!id.includes('node_modules')) return
const pkg = id.split('/node_modules/').pop()?.split('/')[0] ?? ''
if (['react', 'react-dom', 'react-router-dom'].includes(pkg)) {
return 'common-react-dom'
}
if (pkg.startsWith('@sentry')) {
return 'vendors-sentry'
}
if (pkg === 'swiper') {
return 'common-swiper'
}
if (pkg.startsWith('lf-') && pkg.endsWith('-api-client')) {
return pkg
}
},
},
},
},
})
주의: manualChunks 객체 형식에서 존재하지 않는 패키지명을 넣으면 빌드 에러. 실제 설치된 패키지명으로 정확히 작성.
주의 (Vite 8+): 위 객체 형식 manualChunks는 Vite 8+에서 미지원. 아래 함수형 패턴을 사용할 것(함수형도 deprecated이나 현재까지는 동작).
2. Babel 플러그인 → Vite 대응
console 제거 (transform-remove-console)
module.exports = {
babel: {
plugins: [
isProd && ['transform-remove-console', { exclude: ['error'] }],
].filter(Boolean),
},
}
export default defineConfig(({ mode }) => ({
build: {
esbuildOptions: {
drop: mode === 'production' ? ['console'] : [],
},
terserOptions: {
compress: {
drop_console: mode === 'production',
pure_funcs: mode === 'production' ? [] : [],
},
},
},
}))
주의: esbuild drop: ['console']은 console.error도 제거. 특정 메서드만 유지하려면 pure 옵션 사용.
주의 (Vite 8+): Rolldown 전환 후 drop 옵션 위치는 build.rolldownOptions.output.minify.compress.drop*로 이동. 위 esbuildOptions.drop은 Vite 6/7 기준.
3. Webpack 플러그인 → Vite 대응표
| Webpack 플러그인 | Vite 대응 |
|---|
webpack-retry-chunk-load-plugin | vite:preloadError 이벤트 리스너 (아래 참조) |
HtmlWebpackPlugin | Vite 내장 (index.html 자동 처리) |
MiniCssExtractPlugin | Vite 내장 (CSS 자동 추출) |
CopyWebpackPlugin | Vite 내장 (publicDir) 또는 vite-plugin-static-copy |
DefinePlugin | define 옵션 또는 import.meta.env |
BabelWebpackPlugin | @vitejs/plugin-react (babel 옵션 포함) |
청크 로드 실패 재시도 (webpack-retry-chunk-load-plugin 대체)
function retryChunkPlugin(): Plugin {
return {
name: 'retry-chunk-load',
transformIndexHtml(html) {
return html.replace(
'</head>',
`<script>
window.__viteChunkRetryCount = 0;
window.addEventListener('vite:preloadError', (event) => {
if (window.__viteChunkRetryCount < 3) {
window.__viteChunkRetryCount++;
window.location.reload();
}
});
</script></head>`
)
},
}
}
export default defineConfig({
plugins: [react(), retryChunkPlugin()],
})
4. topLevelAwait
webpackConfig.experiments = { topLevelAwait: true }
5. 개발 서버 설정
module.exports = {
devServer: {
https: true,
host: 'dev-local.example.co.kr',
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
}
import fs from 'fs'
export default defineConfig({
server: {
https: {
key: fs.readFileSync('./certs/key.pem'),
cert: fs.readFileSync('./certs/cert.pem'),
},
host: 'dev-local.example.co.kr',
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
})
주의: HTTPS=true는 CRA 전용 환경 변수. Vite에서는 server.https 객체로 명시.
6. 환경 변수 define (DefinePlugin 대체)
webpackConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.BUILD_TIME': JSON.stringify(new Date().toISOString()),
})
)
export default defineConfig({
define: {
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
},
})
7. path alias (baseUrl: "src")
{
"compilerOptions": {
"baseUrl": "src"
}
}
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [react(), tsconfigPaths()],
})
npm install -D vite-tsconfig-paths
흔한 실수 패턴
1. cacheGroups priority → manualChunks 우선순위 무시
manualChunks(id) {
if (id.includes('@sentry')) return 'vendors-sentry'
if (id.includes('node_modules')) return 'vendor'
}
2. webpack.configure 전체를 그대로 복사
webpackConfig.optimization.splitChunks = { ... }
3. process.env 잔존
const isDev = process.env.NODE_ENV === 'development'
const isDev = import.meta.env.DEV