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|react-big-calendar)[\\/]/, 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 模式下禁用 proxy,让 MSW 拦截请求 ...(isMockMode() ? {} : { proxy: { '/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': '' }, }, '/bytedesk': { target: 'https://valuefrontier.cn', // 统一使用生产环境 Nginx 代理 changeOrigin: true, secure: false, // 开发环境禁用 HTTPS 严格验证 logLevel: 'debug', ws: true, // 支持 WebSocket // 不使用 pathRewrite,保留 /bytedesk 前缀,让生产 Nginx 处理 }, }, }), }, };