front/vite.config.ts
zzh d3f300d8b1
All checks were successful
Build and Deploy Front / deploy (push) Successful in 12m44s
feat: 将静态官网迁入 Vue 首页,并优化顾问导航与登录态
EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 20:50:57 +08:00

881 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(
/<link rel="modulepreload"[^>]*(?: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;
});