import { resolve } from 'path'; import type { Plugin, UserConfig } from 'vite'; import { defineConfig, loadEnv } from 'vite'; import visualizer from 'rollup-plugin-visualizer'; import vue from '@vitejs/plugin-vue'; import vueJsx from '@vitejs/plugin-vue-jsx'; import svgLoader from 'vite-svg-loader'; import AutoImport from 'unplugin-auto-import/vite'; import Components from 'unplugin-vue-components/vite'; import UnoCSS from 'unocss/vite'; import configArcoStyleImportPlugin from './config/plugin/arcoStyleImport'; import configCompressPlugin from './config/plugin/compress'; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ''); // 开发默认打测试环境;可用 VITE_API_ORIGIN / VITE_OMS_ADMIN_URL 覆盖 const omsAdminTarget = ( env.VITE_OMS_ADMIN_URL || env.VITE_API_ADMIN_URL || 'https://api.test.ideship.com/admin' ).replace(/\/$/, ''); // API 根(itfx/track):优先 VITE_API_ORIGIN,否则从 Admin URL 去掉 /admin const apiOrigin = ( env.VITE_API_ORIGIN || omsAdminTarget.replace(/\/admin\/?$/, '') || 'https://api.test.ideship.com' ).replace(/\/$/, ''); // ideship:PATHBASE=/track → /track/Track/Order/... // nanafa:网关仍吃 /track/Track/Track/Order/...(前端历史双 Track) // 下列服务挂在 /track 根下(不是 /Track/xxx 控制器),前端却写成 Track/xxx,需剥掉网关前缀 const trackUseSingleSegment = String(env.VITE_DEPLOY_TARGET || '').toLowerCase() === 'ideship' || /ideship\.com/i.test(apiOrigin); const TRACK_SPA_SEGMENTS = [ 'guide', 'order', 'warningOrder', 'state', 'flag', 'content', 'pull', 'push', 'kpiRegion', 'kpiEarlyWarningConfig', 'query', ]; /** 后端挂在 PATHBASE 根下的服务(非 /Track/Order 这类) */ const TRACK_ROOT_SERVICES = ['Warning', 'ProviderService', 'Dictionary']; const isTrackSpaPath = (path: string) => { if (/^\/track\/?$/i.test(path)) return true; return TRACK_SPA_SEGMENTS.some( (seg) => new RegExp(`^/track/${seg}(/|$)`, 'i').test(path), ); }; /** /Track/Warning|ProviderService|Dictionary → /track/Warning|... */ const rewriteTrackRootServicePath = (path: string) => { for (const name of TRACK_ROOT_SERVICES) { const prefix = `/Track/${name}`; if (path === prefix || path.startsWith(`${prefix}/`)) { return `/track${path.slice('/Track'.length)}`; } } return null; }; const rewriteTrackProxyPath = (path: string) => { // 小写 /track SPA 不应进代理;若误入则原样返回,避免变成 /track/track/... if (path === '/track' || path.startsWith('/track/')) { return path; } const rootService = rewriteTrackRootServicePath(path); if (rootService) return rootService; if (trackUseSingleSegment) { if (path.startsWith('/Track/Track/')) { return `/track/Track/${path.slice('/Track/Track/'.length)}`; } if (path.startsWith('/Track/') || path === '/Track') { return `/track${path}`; } } return `/track${path}`; }; return { server: { open: true, fs: { strict: true, }, // 添加中间件,在代理之前拦截前端路由 middlewareMode: false, proxy: { // Admin wwwroot 静态资源:/uploads/... → {admin}/uploads/... '/uploads': { target: omsAdminTarget, changeOrigin: true, secure: false, }, // 上传静态资源:/api/upload/xxx -> {apiOrigin}/upload/xxx(Admin 多为 /admin/uploads) // 必须放在通用 /api 规则前,避免被转发到 /itfx/upload 导致 404 '/api/upload': { target: apiOrigin, changeOrigin: true, secure: false, rewrite: (path) => path.replace(/^\/api/, ''), timeout: 30000, proxyTimeout: 30000, }, // Admin API:目标由 VITE_OMS_ADMIN_URL 决定(默认 https://api.test.ideship.com/admin) '/rbac': { target: omsAdminTarget, changeOrigin: true, secure: false, }, // OMS 订单等 admin 接口:/order/list、/order/detail(与 Swagger OMS-Order 一致) '/order': { target: omsAdminTarget, changeOrigin: true, secure: false, bypass(req) { const path = req.url?.split('?')[0] ?? ''; const orderSpaPrefixes = [ '/order/guide', '/order/priceCheck', '/order/placeOrder', '/order/myOrders', '/order/fulfillOrders', '/order/waybillManage', '/order/pickupOrder', '/order/customs9610Order', '/order/batchJobs', '/order/templateMapping', '/order/platformStore', '/order/platformStoreOrder', ]; if (orderSpaPrefixes.some((p) => path === p || path.startsWith(`${p}/`))) { return '/index.html'; } }, }, // OMS 仓储 admin 接口:/warehouse/sku/list 等(与 Swagger OMS-Warehouse 一致) '/warehouse': { target: omsAdminTarget, changeOrigin: true, secure: false, bypass(req) { const path = req.url?.split('?')[0] ?? ''; const warehouseSpaExact = [ '/warehouse/parcel', '/warehouse/inventory', '/warehouse/sku', '/warehouse/binLocation', ]; if (warehouseSpaExact.some((p) => path === p || path === `${p}/`)) { return '/index.html'; } // inboundNotice 页面无更深 API 子路径冲突;入库 API 为 /warehouse/inbound/* if (path === '/warehouse/inboundNotice' || path === '/warehouse/inboundNotice/') { return '/index.html'; } }, }, '/plugin': { target: omsAdminTarget, changeOrigin: true, secure: false, bypass(req) { const path = req.url?.split('?')[0] ?? ''; if (path === '/Plugin/AddPackage' || path.startsWith('/Plugin/AddPackage/')) { return '/index.html'; } }, }, '/Plugin': { target: omsAdminTarget, changeOrigin: true, secure: false, bypass(req) { const path = req.url?.split('?')[0] ?? ''; if (path === '/Plugin/AddPackage' || path.startsWith('/Plugin/AddPackage/')) { return '/index.html'; } }, }, '/monitor': { target: omsAdminTarget, changeOrigin: true, secure: false, }, // 知识库:API /kb/admin/*、/kb/portal/*;页面 /kb/category、/kb/article '/kb': { target: omsAdminTarget, changeOrigin: true, secure: false, bypass(req) { const path = req.url?.split('?')[0] ?? ''; const kbSpaPages = ['/kb/category', '/kb/article']; if (kbSpaPages.some((p) => path === p || path.startsWith(`${p}/`))) { return '/index.html'; } }, }, '/customer': { target: omsAdminTarget, changeOrigin: true, secure: false, }, '/wechat': { target: omsAdminTarget, changeOrigin: true, secure: false, // H5 页面路由与 API 同前缀:刷新 /wechat/register、/wechat/bind 须回退 index.html bypass(req) { const path = req.url?.split('?')[0] ?? ''; const wechatSpaPages = ['/wechat/register', '/wechat/bind']; const isSpaPage = wechatSpaPages.some( (p) => path === p || path.startsWith(`${p}/`), ); if (isSpaPage) { return '/index.html'; } }, }, '/recharge': { target: omsAdminTarget, changeOrigin: true, secure: false, }, // OMS 财务 admin 接口:/finance/receivable-payable/list 等 '/finance': { target: omsAdminTarget, changeOrigin: true, secure: false, bypass(req) { const path = req.url?.split('?')[0] ?? ''; const financeSpaExact = [ '/finance/rechargeRecord', '/finance/accountFlow', '/finance/receivablePayable', ]; if (financeSpaExact.some((p) => path === p || path === `${p}/`)) { return '/index.html'; } }, }, '/channel': { target: omsAdminTarget, changeOrigin: true, secure: false, // 页面与 API 同前缀:刷新/直链必须 bypass,否则会打到后端 404 bypass(req) { const path = req.url?.split('?')[0] ?? ''; const channelSpaPrefixes = [ '/channel/salesProducts', '/channel/serviceChannels', '/channel/priceArea', '/channel/blacklist', '/channel/remoteZones', '/channel/weightRules', '/channel/weightRounding', '/channel/autoTrigger', '/channel/issueTemplate', '/channel/labelDesign', '/channel/guide', ]; if (channelSpaPrefixes.some((p) => path === p || path.startsWith(`${p}/`))) { return '/index.html'; } }, }, '/file-storage': { target: omsAdminTarget, changeOrigin: true, secure: false, }, // 开发环境面单 PDF 预览:同源代理 COS 签名链接(dev 后端桶可能与 printUrl 不一致) '/__cos__': { target: 'https://cos.ideship.com', changeOrigin: true, secure: false, xfwd: false, timeout: 30000, proxyTimeout: 30000, router: (req) => { const raw = String(req.originalUrl || req.url || '').split('?')[0]; const host = raw.replace(/^\/__cos__\//, '').split('/')[0]; return host ? `https://${host}` : 'https://cos.ideship.com'; }, rewrite: (path) => path.replace(/^\/__cos__\/[^/]+/, '') || '/', configure: (proxy) => { proxy.on('proxyReq', (proxyReq, req) => { proxyReq.removeHeader('authorization'); proxyReq.removeHeader('cookie'); proxyReq.removeHeader('origin'); proxyReq.removeHeader('referer'); const raw = String(req.originalUrl || req.url || '').split('?')[0]; const host = raw.replace(/^\/__cos__\//, '').split('/')[0]; if (host) { proxyReq.setHeader('Host', host); } }); }, }, '/marketing': { target: omsAdminTarget, changeOrigin: true, secure: false, bypass(req) { const path = req.url?.split('?')[0] ?? ''; const marketingSpaPrefixes = [ '/marketing/memberLevel', '/marketing/pointsRule', '/marketing/pointsFlow', '/marketing/couponTemplate', '/marketing/couponGrant', '/marketing/commissionRule', '/marketing/commissionDetail', '/marketing/commissionWithdraw', '/marketing/referralTree', ]; if (marketingSpaPrefixes.some((p) => path === p || path.startsWith(`${p}/`))) { return '/index.html'; } }, }, '/operation': { target: omsAdminTarget, changeOrigin: true, secure: false, // 与页面同前缀的 API 代理:必须在代理层 bypass,否则刷新会先于/绕过 unshift 中间件仍被转发到后端 404 bypass(req) { const path = req.url?.split('?')[0] ?? ''; const operationSpaPrefixes = [ '/operation/inboundScan', '/operation/outboundScan', '/operation/printLabel', '/operation/parcelArrival', '/operation/fulfillPack', ]; const isSpaPage = operationSpaPrefixes.some( (p) => path === p || path.startsWith(`${p}/`), ); if (isSpaPage) { return '/index.html'; } }, }, '/system': { target: omsAdminTarget, changeOrigin: true, secure: false, bypass(req) { const path = req.url?.split('?')[0] ?? ''; const systemSpaExact = ['/system/dict', '/system/warehouseAddress', '/system/announcement']; if (systemSpaExact.some((p) => path === p || path === `${p}/`)) { return '/index.html'; } const systemSpaPrefixes = [ '/system/users', '/system/roles', '/system/menus', '/system/organizations', '/system/batchEmail', '/system/otherConfig', '/system/aiAssistant', '/system/aiSkills', '/system/mcpTools', '/system/dataPerm', ]; if (systemSpaPrefixes.some((p) => path === p || path.startsWith(`${p}/`))) { return '/index.html'; } }, }, // 小红书 OMS-Xhs 接口(开发默认走本机 Admin API,与桌面 Agent 一致) '/xhs': { target: omsAdminTarget, changeOrigin: true, secure: false, }, // MCP 工具 / AI Agent(须与 nginx /mcp/ 反代一致) '/mcp': { target: omsAdminTarget, changeOrigin: true, secure: false, }, // 兼容旧 Agent 路径 /ai/agent/chat '/ai': { target: omsAdminTarget, changeOrigin: true, secure: false, }, // 微信公众号服务器回调(兼容 WMS 路径 /api/apiwechat/verification) '/api/apiwechat': { target: omsAdminTarget, changeOrigin: true, secure: false, rewrite: (path) => path.replace(/^\/api/, ''), }, // 轨迹(track):{apiOrigin}/track/... // 注意:Windows 下 /Track 可能误匹配小写 SPA `/track/pull`,必须 bypass 到 index.html '/Track': { target: apiOrigin, changeOrigin: true, secure: false, bypass(req) { const path = (req.url || '').split('?')[0] || ''; // 大写 /Track/... 是轨迹 API if (path.startsWith('/Track')) return undefined; // 小写 /track/... 是前端页面 —— 必须回 index.html,返回原 path 会找实体文件 404 if (isTrackSpaPath(path)) return '/index.html'; return undefined; }, rewrite: rewriteTrackProxyPath, configure: (proxy) => { proxy.on('error', (err, req, res) => { console.error('proxy error', err); }); }, }, // trackApi/*(另一套生成客户端):开发环境同样代理到 /track/* '/trackApi': { target: apiOrigin, changeOrigin: true, secure: false, rewrite: (path) => { const rest = path.replace(/^\/trackApi\/?/, ''); if (trackUseSingleSegment && rest.startsWith('Track/')) { return `/track/${rest}`; } // Warning / ProviderService / Dictionary 已是根服务 if ( rest.startsWith('Warning') || rest.startsWith('ProviderService') || rest.startsWith('Dictionary') ) { return `/track/${rest}`; } return `/track/${rest}`; }, }, // API(itfx):{apiOrigin}/itfx // 使用正则表达式匹配,排除前端路由路径 '^/api/(?!upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface|downstreamInterface/newPort|appList|appList/createApplication|requestLog|callStatistics|apiDebugger|apiTest|openApi|trackApi)': { target: `${apiOrigin}/itfx`, changeOrigin: true, secure: false, rewrite: (path) => path.replace(/^\/api/, ''), timeout: 30000, proxyTimeout: 30000, }, // 智谱 AI(开发环境代理,避免浏览器 CORS) '/zhipu-api': { target: 'https://open.bigmodel.cn', changeOrigin: true, secure: true, rewrite: (path) => path.replace(/^\/zhipu-api/, '/api/paas/v4'), timeout: 120000, proxyTimeout: 120000, }, // 外部域名(用于 ApiTest 调试,避免浏览器 CORS) // 示例:把 https://lsp.globex.cn/rs/xxx 代理到 /external/rs/xxx '/external': { target: 'https://lsp.globex.cn', changeOrigin: true, secure: false, rewrite: (path) => path.replace(/^\/external/, ''), timeout: 30000, proxyTimeout: 30000, }, // API 网关整站代理(ApiTest 调试,路径名历史遗留):/nanafa/itfx/xxx → {api}/itfx/xxx '/nanafa': { target: apiOrigin, changeOrigin: true, secure: false, rewrite: (path) => path.replace(/^\/nanafa/, ''), timeout: 60000, proxyTimeout: 60000, }, }, }, plugins: [ { name: 'filter-known-build-warnings', configResolved(config) { const rawWarn = config.logger.warn; config.logger.warn = (msg, options) => { if ( msg.includes('[baseline-browser-mapping]') || msg.includes('~@/assets/logo.png referenced in ~@/assets/logo.png') || msg.includes('new URL("../assets/hi.png", import.meta.url) doesn\'t exist at build time') ) { return; } rawWarn(msg, options); }; }, }, // 登录首屏不预取业务重型库(即使被错误打进依赖图也不 preload) { name: 'omit-heavy-modulepreload', transformIndexHtml(html) { return html.replace( /]*(?:vendor-handsontable|vendor-echarts|vendor-codemirror|vendor-vxe|vendor-element-plus|vendor-mermaid|xlsx-|cytoscape|katex)[^>]*>\s*/g, '', ); }, }, // eslint({ // cache: false, // include: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'], // exclude: ['node_modules'], // }), vue(), vueJsx(), svgLoader({ svgoConfig: {} }), configArcoStyleImportPlugin(), UnoCSS(), AutoImport({ imports: ['vue', 'vue-router', 'vue-i18n', 'pinia', '@vueuse/core'], dts: 'src/auto-imports.d.ts', dirs: ['src/composables', 'src/store'], vueTemplate: true, eslintrc: { enabled: true, }, }), Components({ extensions: ['vue'], include: [/\.vue$/, /\.vue\?vue/], dts: 'src/components.d.ts', dirs: ['src/components'], resolvers: [], // directoryAsNamespace: true, }), // 自定义插件:在代理之前拦截前端路由 { name: 'exclude-frontend-routes-from-proxy', configureServer(server) { // 仅精确匹配的 SPA 路径(页面 URL 与后端 API 前缀相同,不能用「前缀匹配」,否则 /warehouse/parcel/list 会返回 index.html) const frontendRoutesExact = [ '/system/dict', '/system/warehouseAddress', '/system/announcement', // 仓储页面路径与 API 同段,必须精确匹配(/warehouse/parcel/list 走代理) '/warehouse/parcel', '/warehouse/inventory', '/warehouse/sku', '/warehouse/inboundNotice', '/warehouse/binLocation', '/Plugin/AddPackage', ]; // 前端路由路径列表(这些路径不应该被代理,应该走前端路由) // 所有前端路由都使用驼峰命名,以这些前缀开头的路径都是前端路由 const frontendRoutes = [ // API 相关前端路由 '/api/upstreamInterface', '/api/upstreamInterfaceConfig', '/api/upstreamInterfaceConfigEdit', '/api/downstreamInterface', '/api/downstreamInterface/newPort', '/api/appList', '/api/appList/createApplication', '/api/requestLog', '/api/callStatistics', '/api/apiDebugger', '/api/apiTest', '/api/openApi', // Channel 相关前端路由 '/channel/salesProducts', '/channel/serviceChannels', '/channel/priceArea', '/channel/blacklist', '/channel/remoteZones', '/channel/weightRules', '/channel/weightRounding', '/channel/autoTrigger', '/channel/issueTemplate', '/channel/labelDesign', '/channel/guide', // Marketing 相关前端路由(API 为 kebab-case,如 /marketing/member-level/list) '/marketing/memberLevel', '/marketing/pointsRule', '/marketing/pointsFlow', '/marketing/couponTemplate', '/marketing/couponGrant', '/marketing/commissionRule', '/marketing/commissionDetail', '/marketing/commissionWithdraw', '/marketing/referralTree', // Order 相关前端路由 '/order/guide', '/order/priceCheck', '/order/placeOrder', '/order/myOrders', '/order/fulfillOrders', '/order/waybillManage', '/order/pickupOrder', '/order/customs9610Order', '/order/batchJobs', '/order/templateMapping', '/order/platformStore', '/order/platformStoreOrder', // Xhs 相关前端路由(勿用 /xhs 前缀,否则 API 会被误判为 SPA) '/xhs/startService', '/xhs/analysis', '/xhs/creation', '/xhs/publishHistory', '/xhs/skills', '/xhs/humanSettings', '/xhs/agentPair', '/track/guide', '/track/order', '/track/warningOrder', '/track/state', '/track/flag', '/track/content', '/track/pull', '/track/push', '/track/kpiRegion', '/track/kpiEarlyWarningConfig', '/track/query', // Operation 相关前端路由 '/operation/inboundScan', '/operation/outboundScan', '/operation/printLabel', '/operation/parcelArrival', '/operation/fulfillPack', // System 相关前端路由 '/system/users', '/system/roles', '/system/menus', '/system/organizations', '/system/batchEmail', '/system/aiAssistant', '/system/aiSkills', '/system/mcpTools', '/system/otherConfig', '/system/dataPerm', // User 相关前端路由 '/user/recharge', '/user/marketing/invite', '/user/marketing/member', '/user/marketing/coupons', '/user/marketing/commission', // Knowledge 前端路由(API 为 /kb/admin/*、/kb/portal/*) '/kb/category', '/kb/article', // Finance 相关前端路由 '/finance/rechargeRecord', '/finance/accountFlow', '/finance/receivablePayable', ]; const middleware = (req: any, res: any, next: any) => { const url = req.url?.split('?')[0] || ''; const matchesSpaExact = frontendRoutesExact.some( (route) => url === route || url === `${route}/`, ); // 检查是否是前端路由(前缀匹配,勿把与 API 同前缀的页面放在此列表,应放入 frontendRoutesExact) const isFrontendRoute = matchesSpaExact || frontendRoutes.some((route) => url === route || url.startsWith(`${route}/`)); if (isFrontendRoute) { // 标记请求,让代理中间件知道不要代理 (req as any).__skipProxy = true; (req as any).__isFrontendRoute = true; // /api/* 与后端同前缀的 SPA 必须显式回退;/track/* 也回退,防止被 /Track 代理误伤 if (url.startsWith('/api/') || url.toLowerCase().startsWith('/track')) { req.url = '/index.html'; } next(); return; } // 对于 /api/ 路径,检查是否是驼峰命名(前端路由的特征) if (url.startsWith('/api/')) { const pathAfterApi = url.substring(5); // 去掉 '/api/' // 如果路径包含大写字母(驼峰命名),很可能是前端路由 if (/[A-Z]/.test(pathAfterApi)) { (req as any).__skipProxy = true; (req as any).__isFrontendRoute = true; next(); return; } } next(); }; // 使用 unshift 将中间件插入到最前面,确保优先执行 const stack = (server.middlewares as any).stack; if (Array.isArray(stack)) { stack.unshift({ route: '', handle: middleware }); } else { server.middlewares.use(middleware); } }, }, // 默认不在构建期做 brotli(会扫几百个 chunk,拖慢 CI/本地 build)。 // Nginx/CDN 在线压缩即可。需要预生成 .br 时:VITE_BUILD_COMPRESS=brotli pnpm build ...(process.env.VITE_BUILD_COMPRESS === 'brotli' ? ((): Plugin[] => { const p = configCompressPlugin('brotli'); return Array.isArray(p) ? p : [p]; })() : []), ...(process.env.REPORT === 'true' ? [ visualizer({ filename: './node_modules/.cache/visualizer/stats.html', open: true, gzipSize: true, brotliSize: true, }), ] : []), ], resolve: { alias: [ // 统一走 lodash-es,避免与 lodash CJS 整包重复打入 { find: 'lodash', replacement: 'lodash-es' }, { find: '@', replacement: resolve(__dirname, './src'), }, { find: 'assets', replacement: resolve(__dirname, './src/assets'), }, { find: 'vue-i18n', replacement: 'vue-i18n/dist/vue-i18n.cjs.js', // Resolve the i18n warning issue }, { find: 'vue', replacement: 'vue/dist/vue.esm-bundler.js', // compile template }, { find: 'xe-utils', replacement: resolve( __dirname, './node_modules/.pnpm/xe-utils@3.7.5/node_modules/xe-utils' ), // Add xe-utils alias }, ], extensions: ['.ts', '.js'], }, define: { 'process.env': {}, __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false', __VUE_OPTIONS_API__: 'true', __VUE_PROD_DEVTOOLS__: 'false', }, optimizeDeps: { // 预构建常用依赖,加速开发体验 include: [ 'vue', 'vue-router', 'pinia', '@arco-design/web-vue', 'axios', 'dayjs', 'lodash-es', ], // 确保 Vue 相关依赖的预构建顺序 esbuildOptions: { target: 'es2017', // 启用 tree-shaking treeShaking: true, }, }, build: { // 启用压缩(使用esbuild,Vite默认,性能更好) minify: 'esbuild', // 生产环境移除 console 和 debugger esbuild: { drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [], }, // 启用CSS代码分割 cssCodeSplit: true, // 生成 source map(默认关闭,避免线上包体积变大;需要排查问题时可临时打开) sourcemap: false, // 构建输出配置:使用 Vite 默认的代码分割策略,避免过度自定义导致库初始化顺序问题 rollupOptions: { onwarn(warning, warn) { const message = warning.message || ''; if ( message.includes('~@/assets/logo.png referenced in ~@/assets/logo.png') || message.includes('new URL("../assets/hi.png", import.meta.url) doesn\'t exist at build time') || message.includes('Use of eval in "node_modules/vg-print/dist/vg-print.es.js"') || message.includes('dynamically imported by') && message.includes('but also statically imported by') ) { return; } warn(warning); }, output: { manualChunks(id) { if (!id.includes('node_modules')) return; if (id.includes('handsontable') || id.includes('@handsontable')) { return 'vendor-handsontable'; } if (id.includes('codemirror') || id.includes('@codemirror')) { return 'vendor-codemirror'; } if (id.includes('element-plus')) { return 'vendor-element-plus'; } if (id.includes('@arco-design')) { return 'vendor-arco'; } if (id.includes('vxe-table') || id.includes('xe-utils')) { return 'vendor-vxe'; } if (id.includes('echarts')) { return 'vendor-echarts'; } // mermaid 仅 MarkdownViewer 动态加载;单独分包避免污染主图 if ( id.includes('node_modules/mermaid') || id.includes('node_modules/cytoscape') || id.includes('node_modules/katex') || id.includes('node_modules/dagre') || id.includes('node_modules/khroma') ) { return 'vendor-mermaid'; } if (id.includes('lodash')) { return 'vendor-lodash'; } }, // 确保文件名包含 hash,便于浏览器缓存 chunkFileNames: 'assets/js/[name]-[hash].js', entryFileNames: 'assets/js/[name]-[hash].js', assetFileNames: (assetInfo) => { const name = assetInfo.name ?? 'asset'; const info = name.split('.'); const ext = info[info.length - 1] ?? 'bin'; if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(name)) { return `assets/media/[name]-[hash].${ext}`; } if (/\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/.test(name)) { return `assets/img/[name]-[hash].${ext}`; } if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(name)) { return `assets/fonts/[name]-[hash].${ext}`; } return `assets/[name]-[hash].${ext}`; }, }, }, // 设置chunk大小警告限制(提高警告阈值,因为我们已经做了代码分割) chunkSizeWarningLimit: 2000, // 启用 terser 压缩以获得更好的压缩率(可选,esbuild更快但压缩率稍低) // minify: 'terser', // 如果需要更好的压缩率,可以启用,但构建会慢一些 }, css: { preprocessorOptions: { less: { modifyVars: { hack: `true; @import (reference) "${resolve( './src/assets/style/breakpoint.less' )}";`, }, javascriptEnabled: true, }, }, }, } as UserConfig; });