import { defineConfig, loadEnv, Plugin } from 'vite' import vue from '@vitejs/plugin-vue' import checker from 'vite-plugin-checker' import { resolve } from 'path' /** * Vite 插件:开发模式下注入公开配置到 index.html * 与生产模式的后端注入行为保持一致,消除闪烁 */ function injectPublicSettings(backendUrl: string): Plugin { return { name: 'inject-public-settings', transformIndexHtml: { order: 'pre', async handler(html) { try { const response = await fetch(`${backendUrl}/api/v1/settings/public`, { signal: AbortSignal.timeout(2000) }) if (response.ok) { const data = await response.json() if (data.code === 0 && data.data) { const script = `` return html.replace('', `${script}\n`) } } } catch (e) { console.warn('[vite] 无法获取公开配置,将回退到 API 调用:', (e as Error).message) } return html } } } } export default defineConfig(({ mode }) => { // 加载环境变量 const env = loadEnv(mode, process.cwd(), '') const backendUrl = env.VITE_DEV_PROXY_TARGET || 'http://localhost:8080' const devPort = Number(env.VITE_DEV_PORT || 3000) return { plugins: [ vue(), checker({ typescript: true, vueTsc: true }), injectPublicSettings(backendUrl) ], resolve: { alias: { '@': resolve(__dirname, 'src'), // 使用 vue-i18n 运行时版本,避免 CSP unsafe-eval 问题 'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js' } }, define: { // 启用 vue-i18n JIT 编译,在 CSP 环境下处理消息插值 // JIT 编译器生成 AST 对象而非 JS 代码,无需 unsafe-eval __INTLIFY_JIT_COMPILATION__: true }, build: { outDir: '../backend/internal/web/dist', emptyOutDir: true, rollupOptions: { output: { /** * 手动分包配置 * 分离第三方库并按功能合并应用代码,避免循环依赖 */ manualChunks(id: string) { if (id.includes('node_modules')) { // Vue 核心库 if ( id.includes('/vue/') || id.includes('/vue-router/') || id.includes('/pinia/') || id.includes('/@vue/') ) { return 'vendor-vue' } // UI 工具库(较大,单独分离) if (id.includes('/@vueuse/') || id.includes('/xlsx/')) { return 'vendor-ui' } // 图表库 if (id.includes('/chart.js/') || id.includes('/vue-chartjs/')) { return 'vendor-chart' } // 国际化 if (id.includes('/vue-i18n/') || id.includes('/@intlify/')) { return 'vendor-i18n' } // 其他小型第三方库合并 return 'vendor-misc' } // 应用代码:按入口点自动分包,不手动干预 // 这样可以避免循环依赖,同时保持合理的 chunk 数量 } } } }, server: { host: '0.0.0.0', port: devPort, proxy: { '/api': { target: backendUrl, changeOrigin: true }, '/setup': { target: backendUrl, changeOrigin: true } } } } })