* feature_2025/251117_pref: (159 commits) feat: UI调整 feat: 将滚动事件移东到组件内部 feat: 去掉背景组件 feat: 拆分左侧栏、中间聊天区、右侧栏组件, Hooks 提取 feat: 简化主组件 index.js - 使用组件组合方式重构 feat: 创建 ChatArea 组件(含 MessageRenderer、ExecutionStepsDisplay 子组件) feat:拆分工具函数 feat: 拆分BackgroundEffects 背景渐变装饰层 feat: RightSidebar (~420 行) - 模型/工具/统计 Tab 面板(单文件) feat: LeftSidebar (~280 行) - 对话历史列表 + 用户信息卡片 feat: 修复bug pref:移除黑夜模式 feat: 修复警告 feat: 提取常量配置 feat: 修复ts报错 feat: StockChartModal.tsx 替换 KLine 实现 update pay function update pay function update pay function update pay function ...
317 lines
12 KiB
JavaScript
317 lines
12 KiB
JavaScript
const path = require('path');
|
||
const webpack = require('webpack');
|
||
const { BundleAnalyzerPlugin } = process.env.ANALYZE ? require('webpack-bundle-analyzer') : { BundleAnalyzerPlugin: null };
|
||
|
||
// 检查是否为 Mock 模式(与 src/utils/apiConfig.js 保持一致)
|
||
const isMockMode = () => process.env.REACT_APP_ENABLE_MOCK === 'true';
|
||
|
||
module.exports = {
|
||
webpack: {
|
||
configure: (webpackConfig, { env, paths }) => {
|
||
// ============== 持久化缓存配置 ==============
|
||
// 大幅提升二次构建速度(可提升 50-80%)
|
||
webpackConfig.cache = {
|
||
type: 'filesystem',
|
||
cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack'),
|
||
buildDependencies: {
|
||
config: [__filename],
|
||
},
|
||
// 增加缓存有效性检查
|
||
name: env === 'production' ? 'production' : 'development',
|
||
compression: env === 'production' ? 'gzip' : false,
|
||
};
|
||
|
||
// ============== 生产环境优化 ==============
|
||
if (env === 'production') {
|
||
// 高级代码分割策略
|
||
webpackConfig.optimization = {
|
||
...webpackConfig.optimization,
|
||
splitChunks: {
|
||
chunks: 'all',
|
||
maxInitialRequests: 30,
|
||
minSize: 20000,
|
||
maxSize: 512000, // 限制单个 chunk 最大大小(512KB,与 performance.maxAssetSize 一致)
|
||
cacheGroups: {
|
||
// React 核心库单独分离
|
||
react: {
|
||
test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/,
|
||
name: 'react-vendor',
|
||
priority: 30,
|
||
reuseExistingChunk: true,
|
||
},
|
||
// TradingView Lightweight Charts 单独分离(避免被压缩破坏)
|
||
lightweightCharts: {
|
||
test: /[\\/]node_modules[\\/]lightweight-charts[\\/]/,
|
||
name: 'lightweight-charts',
|
||
priority: 26,
|
||
reuseExistingChunk: true,
|
||
},
|
||
// 大型图表库分离(echarts, d3, apexcharts 等)
|
||
charts: {
|
||
test: /[\\/]node_modules[\\/](echarts|echarts-for-react|apexcharts|react-apexcharts|recharts|d3|d3-.*)[\\/]/,
|
||
name: 'charts-lib',
|
||
priority: 25,
|
||
reuseExistingChunk: true,
|
||
},
|
||
// Chakra UI 框架
|
||
chakraUI: {
|
||
test: /[\\/]node_modules[\\/](@chakra-ui|@emotion)[\\/]/,
|
||
name: 'chakra-ui',
|
||
priority: 23, // 从 22 改为 23,避免与 antd 优先级冲突
|
||
reuseExistingChunk: true,
|
||
},
|
||
// Ant Design
|
||
antd: {
|
||
test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
|
||
name: 'antd-lib',
|
||
priority: 22,
|
||
reuseExistingChunk: true,
|
||
},
|
||
// 3D 库(three.js)
|
||
three: {
|
||
test: /[\\/]node_modules[\\/](three|@react-three)[\\/]/,
|
||
name: 'three-lib',
|
||
priority: 20,
|
||
reuseExistingChunk: true,
|
||
},
|
||
// 日期/日历库
|
||
calendar: {
|
||
test: /[\\/]node_modules[\\/](dayjs|date-fns|@fullcalendar)[\\/]/,
|
||
name: 'calendar-lib',
|
||
priority: 18,
|
||
reuseExistingChunk: true,
|
||
},
|
||
// 其他第三方库
|
||
vendor: {
|
||
test: /[\\/]node_modules[\\/]/,
|
||
name: 'vendors',
|
||
priority: 10,
|
||
reuseExistingChunk: true,
|
||
},
|
||
// 公共代码
|
||
common: {
|
||
minChunks: 2,
|
||
priority: 5,
|
||
reuseExistingChunk: true,
|
||
name: 'common',
|
||
},
|
||
},
|
||
},
|
||
// 优化运行时代码
|
||
runtimeChunk: 'single',
|
||
// 使用确定性的模块 ID
|
||
moduleIds: 'deterministic',
|
||
// 最小化配置
|
||
minimize: true,
|
||
minimizer: [
|
||
...webpackConfig.optimization.minimizer,
|
||
],
|
||
};
|
||
|
||
// 配置 Terser 插件,保留 lightweight-charts 的方法名
|
||
const TerserPlugin = require('terser-webpack-plugin');
|
||
webpackConfig.optimization.minimizer = webpackConfig.optimization.minimizer.map(plugin => {
|
||
if (plugin.constructor.name === 'TerserPlugin') {
|
||
const originalOptions = plugin.options || {};
|
||
const originalTerserOptions = originalOptions.terserOptions || {};
|
||
const originalMangle = originalTerserOptions.mangle || {};
|
||
|
||
// 只保留 TerserPlugin 有效的配置项
|
||
const validOptions = {
|
||
test: originalOptions.test,
|
||
include: originalOptions.include,
|
||
exclude: originalOptions.exclude,
|
||
extractComments: originalOptions.extractComments,
|
||
parallel: originalOptions.parallel,
|
||
minify: originalOptions.minify,
|
||
terserOptions: {
|
||
...originalTerserOptions,
|
||
keep_classnames: /^(IChartApi|ISeriesApi|Re)$/, // 保留 lightweight-charts 的类名
|
||
keep_fnames: /^(createChart|addLineSeries|addSeries)$/, // 保留关键方法名
|
||
mangle: {
|
||
...originalMangle,
|
||
reserved: ['createChart', 'addLineSeries', 'addSeries', 'IChartApi', 'ISeriesApi'],
|
||
},
|
||
},
|
||
};
|
||
|
||
return new TerserPlugin(validOptions);
|
||
}
|
||
return plugin;
|
||
});
|
||
|
||
// 生产环境禁用 source map 以加快构建(可节省 40-60% 时间)
|
||
webpackConfig.devtool = false;
|
||
} else {
|
||
// 开发环境使用更快的 source map
|
||
webpackConfig.devtool = 'eval-cheap-module-source-map';
|
||
}
|
||
|
||
// ============== 模块解析优化 ==============
|
||
webpackConfig.resolve = {
|
||
...webpackConfig.resolve,
|
||
alias: {
|
||
...webpackConfig.resolve.alias,
|
||
// 根目录别名
|
||
'@': path.resolve(__dirname, 'src'),
|
||
|
||
// 功能模块别名(按字母顺序)
|
||
'@assets': path.resolve(__dirname, 'src/assets'),
|
||
'@components': path.resolve(__dirname, 'src/components'),
|
||
'@constants': path.resolve(__dirname, 'src/constants'),
|
||
'@contexts': path.resolve(__dirname, 'src/contexts'),
|
||
'@data': path.resolve(__dirname, 'src/data'),
|
||
'@hooks': path.resolve(__dirname, 'src/hooks'),
|
||
'@layouts': path.resolve(__dirname, 'src/layouts'),
|
||
'@lib': path.resolve(__dirname, 'src/lib'),
|
||
'@mocks': path.resolve(__dirname, 'src/mocks'),
|
||
'@providers': path.resolve(__dirname, 'src/providers'),
|
||
'@routes': path.resolve(__dirname, 'src/routes'),
|
||
'@services': path.resolve(__dirname, 'src/services'),
|
||
'@store': path.resolve(__dirname, 'src/store'),
|
||
'@styles': path.resolve(__dirname, 'src/styles'),
|
||
'@theme': path.resolve(__dirname, 'src/theme'),
|
||
'@utils': path.resolve(__dirname, 'src/utils'),
|
||
'@variables': path.resolve(__dirname, 'src/variables'),
|
||
'@views': path.resolve(__dirname, 'src/views'),
|
||
},
|
||
// 减少文件扩展名搜索(优先 TypeScript)
|
||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
|
||
// 优化模块查找路径
|
||
modules: [
|
||
path.resolve(__dirname, 'src'),
|
||
'node_modules'
|
||
],
|
||
// 优化符号链接解析
|
||
symlinks: false,
|
||
};
|
||
|
||
// ============== 插件优化 ==============
|
||
// 移除 ESLint 插件以提升构建速度(可提升 20-30%)
|
||
webpackConfig.plugins = webpackConfig.plugins.filter(
|
||
plugin => plugin.constructor.name !== 'ESLintWebpackPlugin'
|
||
);
|
||
|
||
// 添加打包分析工具(通过 ANALYZE=true 启用)
|
||
if (env === 'production' && process.env.ANALYZE && BundleAnalyzerPlugin) {
|
||
webpackConfig.plugins.push(
|
||
new BundleAnalyzerPlugin({
|
||
analyzerMode: 'static',
|
||
openAnalyzer: true,
|
||
reportFilename: 'bundle-report.html',
|
||
})
|
||
);
|
||
}
|
||
|
||
// Day.js 的语言包非常小(每个约 0.5KB),所以不需要特别忽略
|
||
// 如果需要优化,可以只导入需要的语言包
|
||
|
||
// ============== Loader 优化 ==============
|
||
const babelLoaderRule = webpackConfig.module.rules.find(
|
||
rule => rule.oneOf
|
||
);
|
||
|
||
if (babelLoaderRule && babelLoaderRule.oneOf) {
|
||
babelLoaderRule.oneOf.forEach(rule => {
|
||
// 优化 Babel Loader
|
||
if (rule.loader && rule.loader.includes('babel-loader')) {
|
||
rule.options = {
|
||
...rule.options,
|
||
cacheDirectory: true,
|
||
cacheCompression: false,
|
||
compact: env === 'production',
|
||
};
|
||
// 限制 Babel 处理范围
|
||
rule.include = path.resolve(__dirname, 'src');
|
||
rule.exclude = /node_modules/;
|
||
}
|
||
|
||
// 优化 CSS Loader
|
||
if (rule.use && Array.isArray(rule.use)) {
|
||
rule.use.forEach(loader => {
|
||
if (loader.loader && loader.loader.includes('css-loader') && loader.options) {
|
||
loader.options.sourceMap = env !== 'production';
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// ============== 性能提示配置 ==============
|
||
webpackConfig.performance = {
|
||
hints: env === 'production' ? 'warning' : false,
|
||
maxEntrypointSize: 512000, // 512KB
|
||
maxAssetSize: 512000,
|
||
};
|
||
|
||
return webpackConfig;
|
||
},
|
||
},
|
||
|
||
// ============== Babel 配置优化 ==============
|
||
babel: {
|
||
plugins: [
|
||
// 运行时辅助函数复用
|
||
['@babel/plugin-transform-runtime', {
|
||
regenerator: true,
|
||
useESModules: true,
|
||
}],
|
||
],
|
||
loaderOptions: {
|
||
cacheDirectory: true,
|
||
cacheCompression: false,
|
||
},
|
||
},
|
||
|
||
// ============== 开发服务器配置 ==============
|
||
devServer: {
|
||
hot: true,
|
||
port: 3000,
|
||
compress: true,
|
||
client: {
|
||
overlay: false,
|
||
progress: true,
|
||
},
|
||
// 优化开发服务器性能
|
||
devMiddleware: {
|
||
writeToDisk: false,
|
||
},
|
||
|
||
// 调试日志
|
||
onListening: (devServer) => {
|
||
console.log(`[CRACO] Mock Mode: ${isMockMode() ? 'Enabled ✅' : 'Disabled ❌'}`);
|
||
console.log(`[CRACO] Proxy: ${isMockMode() ? 'Disabled (MSW intercepts)' : 'Enabled (forwarding to backend)'}`);
|
||
},
|
||
|
||
// 代理配置:将 /api 请求代理到后端服务器
|
||
// 注意:Mock 模式下禁用 /api 和 /concept-api,让 MSW 拦截请求
|
||
// 但 /bytedesk 始终启用(客服系统不走 Mock)
|
||
proxy: {
|
||
'/bytedesk': {
|
||
target: 'https://valuefrontier.cn', // 统一使用生产环境 Nginx 代理
|
||
changeOrigin: true,
|
||
secure: false, // 开发环境禁用 HTTPS 严格验证
|
||
logLevel: 'debug',
|
||
ws: true, // 支持 WebSocket
|
||
// 不使用 pathRewrite,保留 /bytedesk 前缀,让生产 Nginx 处理
|
||
},
|
||
// Mock 模式下禁用其他代理
|
||
...(isMockMode() ? {} : {
|
||
'/api': {
|
||
target: 'http://49.232.185.254:5001',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
logLevel: 'debug',
|
||
},
|
||
'/concept-api': {
|
||
target: 'http://49.232.185.254:6801',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
logLevel: 'debug',
|
||
pathRewrite: { '^/concept-api': '' },
|
||
},
|
||
}),
|
||
},
|
||
},
|
||
};
|