329 lines
12 KiB
TypeScript
329 lines
12 KiB
TypeScript
import { resolve } from 'path';
|
||
import type { Plugin, UserConfig } from 'vite';
|
||
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,
|
||
},
|
||
// 添加中间件,在代理之前拦截前端路由
|
||
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;
|
||
// 关键:将 URL 重写为 /index.html,否则以 /api 开头的路径在 Vite 中不会走 SPA 回退,会直接 404
|
||
// /track/* 等非 /api 路径会正常走 SPA 回退,所以只有 /api/* 前端路由需要显式重写
|
||
req.url = '/index.html';
|
||
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'
|
||
? ((): Plugin[] => {
|
||
const p = configCompressPlugin('brotli');
|
||
return Array.isArray(p) ? p : [p];
|
||
})()
|
||
: []),
|
||
],
|
||
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,
|
||
// 构建输出配置:使用 Vite 默认的代码分割策略,避免过度自定义导致库初始化顺序问题
|
||
rollupOptions: {
|
||
output: {
|
||
// 确保文件名包含 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);
|