386 lines
13 KiB
TypeScript
386 lines
13 KiB
TypeScript
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: {
|
||
// RBAC 权限管理服务代理 - http://43.139.210.35:5281
|
||
// 注意:后端 API 路径包含 /rbac 前缀,所以不需要 rewrite
|
||
'/rbac': {
|
||
target: 'http://43.139.210.35:5281',
|
||
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
|
||
);
|
||
});
|
||
},
|
||
},
|
||
// 对应 nginx: location /api/trackApi -> http://43.139.210.35:8889
|
||
'/api/trackApi': {
|
||
target: 'http://43.139.210.35:8889',
|
||
changeOrigin: true,
|
||
rewrite: (path) => path.replace(/^\/api\/trackApi/, ''),
|
||
|
||
configure: (proxy, options) => {
|
||
proxy.on('error', (err, req, res) => {
|
||
console.log('proxy error', err);
|
||
});
|
||
proxy.on('proxyReq', (proxyReq, req, res) => {
|
||
console.log('[trackApi-8889] Sending Request to the Target:', req.method, req.url);
|
||
});
|
||
proxy.on('proxyRes', (proxyRes, req, res) => {
|
||
console.log(
|
||
'[trackApi-8889] Received Response from the Target:',
|
||
proxyRes.statusCode,
|
||
req.url
|
||
);
|
||
});
|
||
},
|
||
},
|
||
// 对应 nginx: location /api -> http://43.139.210.35:8888
|
||
// 使用正则表达式匹配,排除前端路由路径
|
||
// 只代理真正的 API 请求,不代理前端路由
|
||
// 排除所有驼峰命名的前端路由(包含大写字母的路径)
|
||
'^/api/(?!upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface|downstreamInterface/newPort|appList|appList/createApplication|requestLog|callStatistics|apiDebugger|trackApi)': {
|
||
target: 'http://43.139.210.35:8888',
|
||
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,
|
||
},
|
||
},
|
||
},
|
||
});
|