import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import path from "path"; import { visualizer } from "rollup-plugin-visualizer"; import { sentryVitePlugin } from "@sentry/vite-plugin"; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => ({ server: { host: "0.0.0.0", port: 3000, cors: true, headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }, }, plugins: [ react(), visualizer({ filename: "dist/stats.html", open: false, gzipSize: true, brotliSize: true, }), // Sentry 플러그인 (프로덕션 빌드 + Auth Token 있을 때만 활성화) mode === "production" && process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_DISABLE_SOURCEMAPS !== "true" && sentryVitePlugin({ org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, authToken: process.env.SENTRY_AUTH_TOKEN, // 소스맵 업로드 설정 sourcemaps: { // 빌드된 모든 JS, CSS 파일의 소스맵 업로드 assets: ["./dist/**/*.js", "./dist/**/*.css"], // 업로드 후 로컬 소스맵 파일 삭제 (보안) filesToDeleteAfterUpload: ["./dist/**/*.map"], // 소스맵 업로드 재시도 설정 rewrite: true, // 업로드 진행상황 표시 verbose: true, }, // 릴리즈 설정 release: { name: process.env.SENTRY_RELEASE || `zellyy-finance@${new Date().toISOString().slice(0, 19)}`, // 릴리즈에 Git 커밋 정보 자동 첨부 setCommits: { auto: true, ignoreMissing: true, }, // 배포 환경 정보 deploy: { env: mode, name: `${mode}-deployment`, url: process.env.VERCEL_URL || process.env.DEPLOY_URL, }, // 릴리즈 완료 처리 finalize: true, }, // 에러 리포팅 설정 errorHandler: (err) => { console.warn("Sentry 소스맵 업로드 실패:", err.message); // 빌드는 계속 진행 (소스맵 업로드 실패로 빌드가 중단되지 않도록) return false; }, // 텔레메트리 비활성화 telemetry: false, // 디버그 모드 (개발자가 문제를 진단할 수 있도록) debug: process.env.SENTRY_DEBUG === "true", // 업로드 타임아웃 설정 (네트워크 상황에 따라 조정) uploadTimeout: 120000, // 2분 }), ].filter(Boolean), resolve: { alias: { "@": path.resolve(__dirname, "./src"), }, }, build: { // 소스맵 생성 설정 (프로덕션에서도 에러 디버깅을 위해 생성) sourcemap: mode === "production" ? "hidden" : true, // 고급 Tree shaking 최적화 treeshake: { // 모듈 사이드 이펙트 제거 (CSS imports 제외) moduleSideEffects: (id) => { return ( id.includes(".css") || id.includes(".scss") || id.includes(".less") ); }, // 프로퍼티 읽기 사이드 이펙트 제거 propertyReadSideEffects: false, // 사용하지 않는 클래스 및 함수 제거 tryCatchDeoptimization: false, // 알려진 글로벌 사이드 이펙트 제거 unknownGlobalSideEffects: false, }, // 청크 로딩 실패에 대한 재시도 설정 target: "esnext", rollupOptions: { // 외부 종속성 명시적 처리 (CDN 오류 방지) external: (id) => { // Clerk CDN 관련 오류 방지를 위해 조건부 외부화 if ( id.includes("@clerk") && process.env.VITE_DISABLE_CLERK === "true" ) { return true; } return false; }, output: { // 청크 파일명 일관성 보장 (ChunkLoadError 방지) chunkFileNames: "assets/[name]-[hash].js", entryFileNames: "assets/[name]-[hash].js", assetFileNames: "assets/[name]-[hash].[ext]", // 청크 로딩 실패 시 재시도 로직 추가 intro: ` window.__vitePreloadOriginal = window.__vitePreload; window.__vitePreload = function(baseModule, deps) { return window.__vitePreloadOriginal(baseModule, deps).catch(err => { console.warn('Chunk loading failed, retrying...', err); // 청크 오류 처리 시스템이 이미 활성화되어 있으므로 에러를 다시 던짐 throw err; }); }; `, manualChunks: (id) => { // 노드 모듈들을 카테고리별로 분할 if (id.includes("node_modules")) { // 핵심 React 라이브러리 if (id.includes("react") || id.includes("react-dom")) { return "vendor-react"; } // 라우팅 if (id.includes("react-router")) { return "vendor-router"; } // UI 컴포넌트 라이브러리 (Radix UI) if (id.includes("@radix-ui")) { return "vendor-ui"; } // 차트 라이브러리 (Recharts - 가장 큰 청크) if (id.includes("recharts")) { return "vendor-charts"; } // 상태 관리 및 쿼리 if ( id.includes("@tanstack/react-query") || id.includes("zustand") ) { return "vendor-state"; } // 인증 및 백엔드 if (id.includes("@clerk") || id.includes("@supabase")) { return "vendor-auth"; } // 모니터링 및 에러 추적 if (id.includes("@sentry")) { return "vendor-sentry"; } // 유틸리티 라이브러리 if ( id.includes("date-fns") || id.includes("clsx") || id.includes("tailwind-merge") ) { return "vendor-utils"; } // 폼 관련 if ( id.includes("react-hook-form") || id.includes("@hookform") || id.includes("zod") ) { return "vendor-forms"; } // 나머지 모든 노드 모듈들 return "vendor-misc"; } // 소스 코드를 기능별로 분할 if (id.includes("/src/")) { // 페이지 컴포넌트들 if (id.includes("/pages/")) { return "pages"; } // 차트 및 분석 관련 컴포넌트 if (id.includes("/analytics/") || id.includes("Chart")) { return "analytics"; } // 거래 관련 컴포넌트 if (id.includes("/transaction/") || id.includes("Transaction")) { return "transactions"; } // 예산 관련 컴포넌트 if (id.includes("/budget/") || id.includes("Budget")) { return "budget"; } // 인증 관련 컴포넌트 if (id.includes("/auth/") || id.includes("Auth")) { return "auth"; } // UI 컴포넌트 if (id.includes("/ui/")) { return "ui-components"; } // 훅 및 유틸리티 if ( id.includes("/hooks/") || id.includes("/utils/") || id.includes("/lib/") ) { return "core-utils"; } } }, }, }, // 청크 크기 경고 임계값 조정 chunkSizeWarningLimit: 1000, // 압축 최적화 (esbuild 사용) minify: "esbuild", }, }));