Files
goldenChart/frontend/vite.config.ts
T
2026-06-15 10:31:44 +09:00

124 lines
4.5 KiB
TypeScript

import { defineConfig, createLogger } from 'vite';
import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer';
import { readFileSync } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..');
function readAppVersionForBuild(): string {
const fromEnv =
process.env.VITE_APP_VERSION?.trim()
|| process.env.FRONTEND_APP_VERSION?.trim()
|| process.env.DESKTOP_APP_VERSION?.trim();
if (fromEnv) return fromEnv;
try {
const latestPath = path.resolve(__dirname, 'public/desktop/updates/latest.json');
const latest = JSON.parse(readFileSync(latestPath, 'utf8')) as { version?: string };
const fromLatest = latest.version?.trim();
if (fromLatest) return fromLatest;
} catch {
/* optional */
}
try {
const confPath = path.resolve(repoRoot, 'desktop/src-tauri/tauri.conf.json');
const conf = JSON.parse(readFileSync(confPath, 'utf8')) as { version?: string };
return String(conf.version ?? '').trim();
} catch {
return '';
}
}
const appVersion = readAppVersionForBuild();
// WebSocket proxy 재연결/종료 시 발생하는 ECONNRESET·EPIPE 는 정상 TCP 이벤트이므로 로그 억제
const SUPPRESS = ['ECONNRESET', 'EPIPE', 'ws proxy socket error'];
const logger = createLogger();
const _origError = logger.error.bind(logger);
logger.error = (msg, opts) => {
if (SUPPRESS.some(s => msg.includes(s))) return;
_origError(msg, opts);
};
export default defineConfig({
customLogger: logger,
define: {
'import.meta.env.VITE_APP_VERSION': JSON.stringify(appVersion),
},
plugins: [
react(),
visualizer({ filename: 'dist/stats.html', open: false, gzipSize: true, brotliSize: true }),
],
resolve: {
dedupe: ['lightweight-charts'],
},
optimizeDeps: {
include: ['react', 'react-dom'],
},
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules/lightweight-charts')) return 'vendor-charts';
if (id.includes('node_modules/@xyflow')) return 'vendor-flow';
if (id.includes('node_modules/@stomp') || id.includes('node_modules/sockjs-client')) return 'vendor-stomp';
if (id.includes('node_modules/@tanstack/react-virtual')) return 'vendor-virtual';
// 차트 워크스페이스 전용 청크 — lazy import로 분리된 코드
if (
id.includes('/chart/ChartWorkspaceRoot') ||
id.includes('/chart/useChartWorkspace') ||
id.includes('/chart/ChartWorkspaceView') ||
id.includes('/utils/ChartManager')
) return 'chart-workspace';
},
},
},
},
server: {
proxy: {
// ── 모바일 APK 정보/다운로드 → exdev (로컬 QR·PC 다운로드가 exdev APK 사용) ──
'/api/mobile-app': {
target: process.env.VITE_MOBILE_APP_PROXY_TARGET || 'http://exdev.co.kr',
changeOrigin: true,
secure: true,
},
// ── GoldenChart Backend API 프록시 ───────────────────────────
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
ws: true, // STOMP SockJS (/api/ws/trading)
},
// ── REST API 프록시 (CORS 우회) ──────────────────────────────
'/upbit-api': {
target: 'https://api.upbit.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/upbit-api/, ''),
headers: { Accept: 'application/json' },
},
// ── WebSocket 프록시 (Origin 차단 우회) ──────────────────────
// 브라우저 → ws://localhost:PORT/upbit-ws → wss://api.upbit.com/websocket/v1
'/upbit-ws': {
target: 'wss://api.upbit.com',
changeOrigin: true,
ws: true,
rewrite: () => '/websocket/v1',
// ECONNRESET 은 Upbit 서버가 연결을 정상 종료할 때 발생하는 TCP 이벤트
// (페이지 새로고침/재연결 시 정상 현상) → 로그 억제
configure: (proxy) => {
proxy.on('error', (err: Error & { code?: string }) => {
if (err.code === 'ECONNRESET' || err.code === 'EPIPE') return; // 정상 종료 → 무시
console.error('[upbit-ws proxy]', err.message);
});
},
},
},
},
});