front/vite.config.ts
2026-02-06 12:01:39 +08:00

378 lines
13 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 { defineConfig } from 'vite';
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({
server: {
open: true,
fs: {
strict: true,
},
historyApiFallback: true,
// 添加中间件,在代理之前拦截前端路由
middlewareMode: false,
proxy: {
// 权限(admin)43.139.210.35:5281 -> http://api.nanafa.com/admin(路径保持 /rbac/xxx -> .../admin/rbac/xxx)
'/rbac': {
target: 'http://api.nanafa.com/admin',
changeOrigin: true,
secure: false,
configure: (proxy, options) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('[RBAC-5281] Sending Request:', req.method, req.url);
console.log('[RBAC-5281] Target Path:', proxyReq.path);
});
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log(
'[RBAC-5281] Received Response:',
proxyRes.statusCode,
req.url
);
});
},
},
// 轨迹(track)43.139.210.35:8889 -> http://api.nanafa.com/track,路径与 rbac、api 一致:/Track/xxx
'/Track': {
target: 'http://api.nanafa.com',
changeOrigin: true,
rewrite: (path) => '/track' + path,
configure: (proxy, options) => {
proxy.on('error', (err, req, res) => {
console.log('proxy error', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('[Track] Sending Request:', req.method, req.url);
});
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log('[Track] Received Response:', proxyRes.statusCode, req.url);
});
},
},
// API(itfx)43.139.210.35:8888 -> http://api.nanafa.com/itfx
// 使用正则表达式匹配,排除前端路由路径
'^/api/(?!upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface|downstreamInterface/newPort|appList|appList/createApplication|requestLog|callStatistics|apiDebugger|trackApi)': {
target: 'http://api.nanafa.com/itfx',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
timeout: 30000,
proxyTimeout: 30000,
configure: (proxy, options) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('[8888] Sending Request:', req.method, req.url);
console.log('[8888] Proxy Target:', proxyReq.path);
});
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log(
'[8888] Received Response:',
proxyRes.statusCode,
req.url
);
});
},
},
},
},
plugins: [
// 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) {
// 前端路由路径列表(这些路径不应该被代理,应该走前端路由)
// 所有前端路由都使用驼峰命名,以这些前缀开头的路径都是前端路由
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',
// Channel 相关前端路由
'/channel/salesProducts',
'/channel/serviceChannels',
'/channel/blacklist',
'/channel/remoteZones',
'/channel/weightRules',
'/channel/weightRounding',
'/channel/autoTrigger',
// Order 相关前端路由
'/order/priceCheck',
'/order/placeOrder',
'/order/myOrders',
// Track 相关前端路由
'/track/order',
'/track/warningOrder',
'/track/state',
'/track/flag',
'/track/content',
'/track/pull',
'/track/push',
'/track/kpiRegion',
'/track/kpiEarlyWarningConfig',
// Operation 相关前端路由
'/operation/inboundScan',
'/operation/outboundScan',
// System 相关前端路由
'/system/users',
'/system/roles',
'/system/menus',
'/system/organizations',
// User 相关前端路由
'/user/recharge',
'/user/rechargeRecord',
];
// 直接在 configureServer 中设置中间件,确保在代理之前执行
const middleware = (req: any, res: any, next: any) => {
const url = req.url?.split('?')[0] || '';
// 检查是否是前端路由
const isFrontendRoute = frontendRoutes.some(route => {
// 精确匹配或路径以路由开头
return url === route || url.startsWith(route + '/');
});
if (isFrontendRoute) {
console.log('[Vite Middleware] Intercepting frontend route:', url);
// 标记请求,让代理中间件知道不要代理
(req as any).__skipProxy = true;
// 标记为前端路由
(req as any).__isFrontendRoute = true;
// 继续处理,让 Vite 的 SPA 路由处理(通过 historyApiFallback)
next();
return;
}
// 对于 /api/ 路径,检查是否是驼峰命名(前端路由的特征)
if (url.startsWith('/api/')) {
const pathAfterApi = url.substring(5); // 去掉 '/api/'
// 如果路径包含大写字母(驼峰命名),很可能是前端路由
if (/[A-Z]/.test(pathAfterApi)) {
console.log('[Vite Middleware] Detected camelCase route (likely frontend):', url);
(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 压缩(压缩率更高,文件数量减少一半)
// 如果需要同时支持 gzip 和 brotli,取消下面的注释
...(process.env.NODE_ENV === 'production'
? [
// 只启用 brotli 压缩(减少文件数量)
...configCompressPlugin('brotli'),
// 如果需要同时支持 gzip,取消下面的注释
// ...configCompressPlugin('gzip'),
]
: []),
],
resolve: {
alias: [
{
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',
],
// 排除 monaco-editor,因为它很大且只在特定页面使用,应该按需加载
exclude: ['monaco-editor'],
// 确保 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,
// 构建输出配置
rollupOptions: {
output: {
// 手动代码分割,优化缓存
manualChunks: (id) => {
// node_modules中的包单独打包
if (id.includes('node_modules')) {
// vue核心库和vue相关库打包在一起,确保加载顺序
if (id.includes('vue') || id.includes('vue-router') || id.includes('pinia') || id.includes('@vue')) {
return 'vue-vendor';
}
// monaco-editor单独打包(按需加载,不阻塞初始加载)
if (id.includes('monaco-editor')) {
return 'monaco-editor';
}
// echarts 相关库单独打包
if (id.includes('echarts') || id.includes('zrender')) {
return 'echarts-vendor';
}
// arco-design相关
if (id.includes('@arco-design')) {
return 'arco-vendor';
}
// vxe-table相关
if (id.includes('vxe-table') || id.includes('xe-utils')) {
return 'vxe-vendor';
}
// axios 和请求相关库
if (id.includes('axios') || id.includes('query-string')) {
return 'http-vendor';
}
// lodash 单独打包(如果使用较多)
if (id.includes('lodash')) {
return 'lodash-vendor';
}
// dayjs 单独打包
if (id.includes('dayjs')) {
return 'dayjs-vendor';
}
// handsontable 单独打包(如果使用)
if (id.includes('handsontable')) {
return 'handsontable-vendor';
}
// 其他第三方库进一步拆分
// 按大小拆分,避免单个vendor包过大
return 'vendor';
}
},
// 确保文件名包含hash,便于浏览器缓存
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.');
const ext = info[info.length - 1];
if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) {
return `assets/media/[name]-[hash].${ext}`;
}
if (/\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/.test(assetInfo.name)) {
return `assets/img/[name]-[hash].${ext}`;
}
if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) {
return `assets/fonts/[name]-[hash].${ext}`;
}
return `assets/[name]-[hash].${ext}`;
},
},
},
// 设置chunk大小警告限制(提高警告阈值,因为我们已经做了代码分割)
chunkSizeWarningLimit: 2000,
// 启用 terser 压缩以获得更好的压缩率(可选,esbuild更快但压缩率稍低)
// minify: 'terser', // 如果需要更好的压缩率,可以启用,但构建会慢一些
// 启用 rollup 的 tree-shaking
treeshake: {
moduleSideEffects: 'no-external',
},
},
css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${resolve(
'./src/assets/style/breakpoint.less'
)}";`,
},
javascriptEnabled: true,
},
},
},
});