编译构建与打包全面优化之Webpack5

相关问题

关于 webpack打包构建优化,之前做过哪些?

  • 代码分割(Code Splitting):使用 Webpack 的 SplitChunksPlugin 进行代码分割,将第三方库、公共代码与业务代码分离,提高缓存利用率和加载速度。

  • Tree Shaking: 通过配置 mode:'production'或使用 TerserPlugin,移除未引用的代码,减少包体积。

  • Lazy Loading(懒加载):使用 import()动态加载模块,实现按需加载,减少初始加载时间。

  • 使用CDN:配置 externals,将常用的库如 React、Vue 等通过CDN 引入,减少打包体积。

  • 缓存优化:通过配置 output.filename 和 output.chunkFilename 中的 [contenthash],生成基于文件内容的哈希值,避免不必要的缓存失效。

  • 开启持久化缓存(Persistent Caching):配置 cache:{ type:'filesystem'},提高二次构建速度。

  • 优化 Loader:使用多进程和缓存(如 thread-loader 和 cache-loader),提升构建速度。还可以通过限制 babel-Loader 等处理范围来加速构建。

  • 优化开发体验:使用 webpack-dev-server 的HMR(热模块替換)功能,提高开发效率;或者通过配置 resolve.alias 缩短模块查找路径。

你认为 Vite 相对于 Webpack 有哪些优势?

  • 极速启动:Vite 使用原生ES模块进行开发时的依赖加载,无需像Webpack一样对整个项目进行预打包。因此,Vite 的冷启动速度非常快,尤其是在大型项目中尤为明显。

  • 即时热更新(HMR):Vite 的HMR速度更快更灵敏,因为它基于 ES 模块,仅更新受影响的模块,而不需要重新构建整个包。

  • 更少的配置:Vite 的默认配置已经足够健全,开箱即用,开发者通常不需要像使用Webpack 一样编写大量的配置文件。

  • 现代化浏览器支持:Vite 针对现代浏览器优化,默认使用ES6+语法,省去了对旧浏览器的兼容配置。

  • 插件生态:虽然 Vite 插件生态相对年轻,但其设计简单且功能强大,能够满足大多数场景的需求。

  • 构建速度快:Vite 使用esbuild 进行预构建,极大提高了依赖解析和打包的速度。此外,Vite 还使用Rollup 作为生产环境打包工具,具有较好的打包优化能力。

  • 调试友好:Vite 生成的源码更接近开发者的源码,调试体验更好,错误追踪更准确。

相关资料

Webpack 5开发构建优化详解

开发模式配置

使用 mode: 'development'

配置 devtool: 'eval-cheap-module-source-map'

启用 HMR (Hot Module Replacement)

javascript 复制代码
// webpack.dev.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devtool: 'eval-cheap-module-source-map',
  devServer: {
    hot: true,
    open: true,
    compress: true,
    port: 3000,
    historyApiFallback: true,
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
  ],
  optimization: {
    usedExports: true,
    sideEffects: false,
  }
};

模块解析优化

使用 resolve.alias

优化模块路径解析:配置 resolve.extensions

使用 resolve.modules

javascript 复制代码
// webpack.config.js
module.exports = {
  resolve: {
    // 路径别名配置
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
      '@utils': path.resolve(__dirname, 'src/utils'),
      '@assets': path.resolve(__dirname, 'src/assets'),
    },
    
    // 扩展名配置
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
    
    // 模块搜索路径
    modules: [
      path.resolve(__dirname, 'src'),
      'node_modules'
    ],
    
    // 优化 npm 包解析
    mainFields: ['browser', 'module', 'main'],
    
    // 缓存解析结果
    cache: true,
    
    // 指定解析目录
    symlinks: false,
  }
};

缓存优化

持久化缓存:启用文件系统缓存

使用 babel-loader 的 cacheDirectory 选项

javascript 复制代码
// webpack.config.js
module.exports = {
  // Webpack 5 持久化缓存
  cache: {
    type: 'filesystem',
    cacheDirectory: path.resolve(__dirname, '.webpack_cache'),
    buildDependencies: {
      config: [__filename]
    },
    version: '1.0'
  },
  
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            cacheCompression: false,
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: {
                localIdentName: '[local]--[hash:base64:5]'
              }
            }
          }
        ]
      }
    ]
  }
};

其他优化

减少监听文件

使用多进程并行构建

合理使用 DllPlugin 和 DllReferencePlugin

javascript 复制代码
// webpack.dll.js - DLL配置
const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'production',
  entry: {
    vendor: [
      'react',
      'react-dom',
      'lodash',
      'moment'
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dll'),
    filename: '[name].dll.js',
    library: '[name]_[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]_[hash]',
      path: path.resolve(__dirname, 'dll/[name].manifest.json')
    })
  ]
};

// webpack.config.js - 引用DLL
module.exports = {
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./dll/vendor.manifest.json')
    })
  ]
};

多进程构建优化

javascript 复制代码
// 使用 thread-loader 进行多进程构建
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'thread-loader',
            options: {
              workers: 2,
              workerParallelJobs: 50,
              workerNodeArgs: ['--max-old-space-size=1024'],
              poolRespawn: false,
              poolTimeout: 2000,
              poolParallelJobs: 50,
              name: 'js-pool'
            }
          },
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              presets: [
                ['@babel/preset-env', { targets: 'defaults' }],
                '@babel/preset-react'
              ]
            }
          }
        ]
      }
    ]
  }
};

Webpack 5构建流程优化

构建流程图

graph TD A[开始构建] --> B[读取配置文件] B --> C[解析入口文件] C --> D[模块依赖分析] D --> E[Loader转换] E --> F[Plugin处理] F --> G{缓存检查} G -->|命中缓存| H[使用缓存结果] G -->|未命中| I[重新编译] H --> J[生成Bundle] I --> J J --> K[优化Bundle] K --> L[输出文件] L --> M[构建完成] style A fill:#e1f5fe style M fill:#c8e6c9 style G fill:#fff3e0

构建性能监控

javascript 复制代码
// webpack.config.js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
  // 其他配置...
  plugins: [
    // 分析包大小
    new BundleAnalyzerPlugin({
      analyzerMode: 'server',
      analyzerHost: '127.0.0.1',
      analyzerPort: 8888,
      openAnalyzer: true,
    }),
  ],
  
  // 性能配置
  performance: {
    hints: 'warning',
    maxEntrypointSize: 250000,
    maxAssetSize: 250000,
    assetFilter: (assetFilename) => {
      return assetFilename.endsWith('.js');
    }
  }
});

生产环境优化配置

代码压缩与优化

javascript 复制代码
// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  mode: 'production',
  devtool: 'source-map',
  
  optimization: {
    minimize: true,
    minimizer: [
      // JS压缩
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true,
          },
          format: {
            comments: false,
          },
        },
        extractComments: false,
      }),
      
      // CSS压缩
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            'default',
            {
              discardComments: { removeAll: true },
            },
          ],
        },
      }),
    ],
    
    // 代码分割配置
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10,
          reuseExistingChunk: true,
        },
        
        // 公共代码
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          priority: 5,
          reuseExistingChunk: true,
        },
        
        // React相关
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          chunks: 'all',
          priority: 20,
        },
        
        // UI库
        antd: {
          test: /[\\/]node_modules[\\/]antd[\\/]/,
          name: 'antd',
          chunks: 'all',
          priority: 15,
        },
      },
    },
    
    // Runtime chunk
    runtimeChunk: {
      name: 'runtime',
    },
  },
  
  plugins: [
    // Gzip压缩
    new CompressionPlugin({
      filename: '[path][base].gz',
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 8192,
      minRatio: 0.8,
    }),
  ],
};

代码分割策略详解

动态导入与懒加载

javascript 复制代码
// 路由懒加载
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

// 动态导入组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

// 条件加载
async function loadUtility(type) {
  let module;
  switch (type) {
    case 'chart':
      module = await import('./utils/chart');
      break;
    case 'validation':
      module = await import('./utils/validation');
      break;
    default:
      throw new Error('Unknown utility type');
  }
  return module.default;
}

// 预加载
const preloadComponent = () => {
  const componentImport = import('./components/HeavyComponent');
  return componentImport;
};

// 在合适的时机预加载
document.addEventListener('mouseover', preloadComponent, { once: true });

代码分割流程图

graph LR A[应用入口] --> B[路由分割] B --> C[组件分割] C --> D[第三方库分割] D --> E[公共代码提取] E --> F[运行时代码] B --> G[Home Chunk] B --> H[About Chunk] B --> I[Dashboard Chunk] D --> J[React Vendor] D --> K[UI Library] D --> L[Utils Library] style A fill:#ffcdd2 style G fill:#c8e6c9 style H fill:#c8e6c9 style I fill:#c8e6c9 style J fill:#e1bee7 style K fill:#e1bee7 style L fill:#e1bee7

Tree Shaking 优化

确保模块使用 ES6 模块语法

javascript 复制代码
// 确保 package.json 中的 sideEffects 配置
{
  "name": "my-project",
  "sideEffects": false,
  // 或者指定有副作用的文件
  "sideEffects": [
    "*.css",
    "*.scss",
    "./src/polyfills.js"
  ]
}

// webpack.config.js
module.exports = {
  mode: 'production',
  optimization: {
    usedExports: true,
    sideEffects: false,
  },
  
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', {
                modules: false, // 保持ES6模块语法
              }]
            ]
          }
        }
      }
    ]
  }
};

// 正确的导入方式
import { debounce } from 'lodash-es'; // ✅ 支持Tree Shaking
// import _ from 'lodash'; // ❌ 会导入整个库

// 工具函数的正确导出
// utils/index.js
export { formatDate } from './date';
export { validateEmail } from './validation';
export { debounceClick } from './events';

// 使用时按需导入
import { formatDate, validateEmail } from './utils';

高级优化技巧

Module Federation(模块联邦)

javascript 复制代码
// webpack.config.js - 主应用
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        mfApp: 'mfApp@http://localhost:3001/remoteEntry.js',
      },
    }),
  ],
};

// webpack.config.js - 微前端应用
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'mfApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/Button',
        './Header': './src/Header',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};

资源内联优化

javascript 复制代码
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true,
      },
      // 内联小于 8kb 的资源
      inlineSource: '.(js|css)$',
    }),
  ],
  
  module: {
    rules: [
      // 小图片内联为 base64
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 8192,
            fallback: 'file-loader',
          }
        }
      },
      
      // 小字体文件内联
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
          }
        }
      }
    ]
  }
};

性能监控与分析

构建分析工具集成

javascript 复制代码
// build-analysis.js
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');

const config = {
  // 基础配置...
  plugins: [
    // 重复包检查
    new DuplicatePackageCheckerPlugin({
      verbose: true,
      emitError: false,
      showHelp: false,
      strict: false,
    }),
    
    // 包大小分析
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
      openAnalyzer: false,
    }),
  ],
};

// 性能测量
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap(config);

实时性能监控

javascript 复制代码
// performance-monitor.js
class PerformanceMonitor {
  constructor() {
    this.metrics = {
      buildTime: 0,
      bundleSize: 0,
      chunkCount: 0,
    };
  }
  
  measureBuildTime() {
    const start = Date.now();
    return () => {
      this.metrics.buildTime = Date.now() - start;
      console.log(`构建时间: ${this.metrics.buildTime}ms`);
    };
  }
  
  analyzeBundleSize(stats) {
    const assets = stats.compilation.assets;
    this.metrics.bundleSize = Object.keys(assets)
      .reduce((total, name) => total + assets[name].size(), 0);
    
    console.log(`Bundle大小: ${(this.metrics.bundleSize / 1024).toFixed(2)}KB`);
  }
  
  generateReport() {
    return {
      timestamp: new Date().toISOString(),
      metrics: this.metrics,
      recommendations: this.getRecommendations(),
    };
  }
  
  getRecommendations() {
    const recommendations = [];
    
    if (this.metrics.buildTime > 30000) {
      recommendations.push('考虑启用持久化缓存');
    }
    
    if (this.metrics.bundleSize > 1024 * 1024) {
      recommendations.push('考虑进一步代码分割');
    }
    
    return recommendations;
  }
}

// webpack 插件集成
class PerformancePlugin {
  apply(compiler) {
    const monitor = new PerformanceMonitor();
    
    compiler.hooks.compile.tap('PerformancePlugin', () => {
      monitor.measureBuildTime();
    });
    
    compiler.hooks.done.tap('PerformancePlugin', (stats) => {
      monitor.analyzeBundleSize(stats);
      const report = monitor.generateReport();
      console.log('性能报告:', report);
    });
  }
}

module.exports = PerformancePlugin;

实战优化案例

大型项目优化实践

javascript 复制代码
// 完整的生产环境配置
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const PerformancePlugin = require('./plugins/PerformancePlugin');

module.exports = {
  mode: 'production',
  entry: {
    app: './src/index.js',
  },
  
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js',
    chunkFilename: 'js/[name].[contenthash:8].chunk.js',
    publicPath: '/',
    clean: true,
  },
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
    modules: ['node_modules', path.resolve(__dirname, 'src')],
  },
  
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename],
    },
  },
  
  optimization: {
    minimize: true,
    splitChunks: {
      chunks: 'all',
      minSize: 20000,
      maxSize: 244000,
      cacheGroups: {
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: -10,
          chunks: 'all',
        },
      },
    },
    runtimeChunk: 'single',
  },
  
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'thread-loader',
            options: { workers: 2 },
          },
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
            },
          },
        ],
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
        ],
      },
    ],
  },
  
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
      },
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[id].[contenthash:8].css',
    }),
    new PerformancePlugin(),
  ],
};

优化效果对比

优化项目 优化前 优化后 提升
首次构建时间 45s 12s 73%
增量构建时间 8s 2s 75%
Bundle大小 2.1MB 980KB 53%
首屏加载时间 3.2s 1.8s 44%

总结

通过以上Webpack 5优化策略的实施,我们可以显著提升开发和构建体验:

  1. 开发效率提升:通过HMR、持久化缓存等技术,大幅缩短开发反馈周期
  2. 构建性能优化:利用多进程、缓存机制,显著减少构建时间
  3. 生产包优化:通过代码分割、Tree Shaking等技术,优化最终产物
  4. 性能监控:建立完善的性能监控体系,持续优化构建流程

这些优化措施需要根据项目实际情况进行调整和组合使用,以达到最佳的优化效果。

Webpack 5产物构建优化详解

生产模式配置

使用 mode: 'production'

生产模式会自动启用多种优化选项,包括代码压缩、Tree Shaking、作用域提升等。

javascript 复制代码
// webpack.prod.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  mode: 'production', // 启用生产模式优化
  entry: './src/index.js',
  
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js',
    chunkFilename: 'js/[name].[contenthash:8].chunk.js',
    publicPath: '/',
    clean: true, // 清理输出目录
  },
  
  // 生产环境推荐的 devtool
  devtool: 'source-map',
  
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[name].[contenthash:8].chunk.css',
    }),
  ],
};

使用 optimization.splitChunks

通过代码分割优化缓存策略和首屏加载性能。

javascript 复制代码
// webpack.prod.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // 对所有类型的chunk进行分割
      minSize: 20000, // 最小分割大小
      maxSize: 244000, // 最大分割大小
      minChunks: 1, // 最小引用次数
      maxAsyncRequests: 30, // 最大异步请求数
      maxInitialRequests: 30, // 最大初始请求数
      
      cacheGroups: {
        // 默认分组
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
        
        // 第三方库分组
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: -10,
          chunks: 'all',
          reuseExistingChunk: true,
        },
        
        // React 相关库单独分割
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react-vendor',
          chunks: 'all',
          priority: 20,
        },
        
        // UI 组件库分割
        antd: {
          test: /[\\/]node_modules[\\/]antd[\\/]/,
          name: 'antd-vendor',
          chunks: 'all',
          priority: 15,
        },
        
        // 工具库分割
        utils: {
          test: /[\\/]node_modules[\\/](lodash|moment|dayjs)[\\/]/,
          name: 'utils-vendor',
          chunks: 'all',
          priority: 10,
        },
        
        // 公共业务组件
        common: {
          test: /[\\/]src[\\/]components[\\/]/,
          name: 'common',
          chunks: 'all',
          minChunks: 2,
          priority: 5,
        },
      },
    },
    
    // 运行时代码单独提取
    runtimeChunk: {
      name: 'runtime',
    },
  },
};

启用 optimization.minimize

配置代码压缩和优化选项。

javascript 复制代码
// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true, // 启用代码压缩
    minimizer: [
      // JavaScript 压缩
      new TerserPlugin({
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
            drop_console: true, // 移除 console
            drop_debugger: true, // 移除 debugger
            pure_funcs: ['console.log'], // 移除指定函数
          },
          mangle: {
            safari10: true,
          },
          format: {
            ecma: 5,
            comments: false, // 移除注释
            ascii_only: true,
          },
        },
        parallel: true, // 多进程压缩
        extractComments: false, // 不提取注释
      }),
      
      // CSS 压缩
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            'default',
            {
              discardComments: { removeAll: true },
              normalizeUnicode: false,
            },
          ],
        },
        parallel: true,
      }),
    ],
  },
};

Tree Shaking

确保使用 ES6 模块

Tree Shaking 依赖 ES6 模块的静态分析能力。

javascript 复制代码
// webpack.config.js
module.exports = {
  mode: 'production',
  
  // 确保模块类型正确
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  modules: false, // 保持 ES6 模块格式,不转换为 CommonJS
                  useBuiltIns: 'usage',
                  corejs: 3,
                  targets: {
                    browsers: ['> 1%', 'last 2 versions'],
                  },
                },
              ],
              '@babel/preset-react',
            ],
            plugins: [
              '@babel/plugin-proposal-class-properties',
              '@babel/plugin-syntax-dynamic-import',
            ],
          },
        },
      },
    ],
  },
  
  optimization: {
    usedExports: true, // 标记未使用的导出
    sideEffects: false, // 标记模块无副作用
  },
};

清理无用代码:配置 sideEffects

package.json 中正确配置 sideEffects

javascript 复制代码
// package.json
{
  "name": "my-app",
  "version": "1.0.0",
  "sideEffects": false, // 表示所有模块都没有副作用
  
  // 或者指定有副作用的文件
  "sideEffects": [
    "*.css",
    "*.scss",
    "*.less",
    "./src/polyfills.js",
    "./src/global.js"
  ]
}

// 工具函数的正确导出方式
// utils/index.js
export { formatDate } from './date';
export { validateEmail } from './validation';
export { debounce } from './debounce';
export { throttle } from './throttle';

// 正确的导入方式
import { formatDate, validateEmail } from './utils';

// 针对第三方库的优化导入
import { debounce } from 'lodash-es'; // ✅ 支持 Tree Shaking
// import _ from 'lodash'; // ❌ 会导入整个库

// 使用 babel-plugin-import 优化 UI 库导入
// .babelrc
{
  "plugins": [
    [
      "import",
      {
        "libraryName": "antd",
        "libraryDirectory": "es",
        "style": "css"
      }
    ]
  ]
}

图片和资源优化

使用 image-webpack-loader 压缩图片

配置图片压缩以减少资源体积。

javascript 复制代码
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: 'images/[name].[contenthash:8].[ext]',
              publicPath: '/',
            },
          },
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: {
                progressive: true,
                quality: 75, // 图片质量
              },
              optipng: {
                enabled: false,
              },
              pngquant: {
                quality: [0.6, 0.8],
              },
              gifsicle: {
                interlaced: false,
              },
              webp: {
                quality: 80, // WebP 质量
                enabled: true,
              },
              svgo: {
                plugins: [
                  { name: 'removeViewBox', active: false },
                  { name: 'removeEmptyAttrs', active: false },
                ],
              },
            },
          },
        ],
      },
      
      // 针对不同格式的特殊处理
      {
        test: /\.svg$/,
        use: [
          {
            loader: '@svgr/webpack',
            options: {
              prettier: false,
              svgo: true,
              svgoConfig: {
                plugins: [
                  {
                    name: 'preset-default',
                    params: {
                      overrides: {
                        removeViewBox: false,
                      },
                    },
                  },
                ],
              },
              titleProp: true,
            },
          },
        ],
      },
    ],
  },
};

使用 url-loader 和 file-loader

根据文件大小决定是否内联资源。

javascript 复制代码
// webpack.config.js
module.exports = {
  module: {
    rules: [
      // 小图片内联为 base64
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            limit: 8192, // 8KB 以下的图片内联
            fallback: {
              loader: 'file-loader',
              options: {
                name: 'images/[name].[contenthash:8].[ext]',
                publicPath: '/',
              },
            },
          },
        },
      },
      
      // 字体文件处理
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10240, // 10KB 以下内联
            fallback: {
              loader: 'file-loader',
              options: {
                name: 'fonts/[name].[contenthash:8].[ext]',
                publicPath: '/',
              },
            },
          },
        },
      },
      
      // 音视频文件
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/i,
        use: {
          loader: 'file-loader',
          options: {
            name: 'media/[name].[contenthash:8].[ext]',
            publicPath: '/',
          },
        },
      },
    ],
  },
};

代码分割和懒加载

使用 import() 动态导入实现代码懒加载

通过动态导入实现按需加载,提升首屏性能。

javascript 复制代码
// 路由级别的懒加载
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import LoadingSpinner from './components/LoadingSpinner';

// 懒加载页面组件
const HomePage = lazy(() => import('./pages/HomePage'));
const ProductPage = lazy(() => import('./pages/ProductPage'));
const UserDashboard = lazy(() => import('./pages/UserDashboard'));
const AdminPanel = lazy(() => 
  import('./pages/AdminPanel').then(module => ({
    default: module.AdminPanel
  }))
);

function App() {
  return (
    <Router>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/product/:id" element={<ProductPage />} />
          <Route path="/dashboard" element={<UserDashboard />} />
          <Route path="/admin" element={<AdminPanel />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

// 组件级别的懒加载
function ProductList() {
  const [showChart, setShowChart] = useState(false);
  const [ChartComponent, setChartComponent] = useState(null);
  
  const loadChart = async () => {
    if (!ChartComponent) {
      const module = await import('./components/Chart');
      setChartComponent(() => module.default);
    }
    setShowChart(true);
  };
  
  return (
    <div>
      <button onClick={loadChart}>显示图表</button>
      {showChart && ChartComponent && <ChartComponent />}
    </div>
  );
}

// 条件加载
async function loadFeature(featureType) {
  let module;
  
  switch (featureType) {
    case 'chart':
      module = await import('./features/chart');
      break;
    case 'editor':
      module = await import('./features/editor');
      break;
    case 'calendar':
      module = await import('./features/calendar');
      break;
    default:
      throw new Error(`Unknown feature: ${featureType}`);
  }
  
  return module.default;
}

// 预加载策略
class FeaturePreloader {
  constructor() {
    this.preloadedModules = new Map();
  }
  
  // 预加载重要功能
  preloadCriticalFeatures() {
    const criticalFeatures = [
      import('./features/user-profile'),
      import('./features/search'),
      import('./features/navigation')
    ];
    
    return Promise.all(criticalFeatures);
  }
  
  // 空闲时预加载
  preloadOnIdle() {
    if ('requestIdleCallback' in window) {
      requestIdleCallback(() => {
        import('./features/analytics');
        import('./features/feedback');
      });
    }
  }
  
  // 基于用户交互预加载
  preloadOnHover(featureName) {
    if (!this.preloadedModules.has(featureName)) {
      const promise = import(`./features/${featureName}`);
      this.preloadedModules.set(featureName, promise);
    }
  }
}

const preloader = new FeaturePreloader();

// 在应用启动时预加载关键功能
preloader.preloadCriticalFeatures();
preloader.preloadOnIdle();

通过 splitChunks 进行代码分割

优化代码分割策略以获得最佳的缓存效果。

javascript 复制代码
// webpack.config.js - 高级代码分割配置
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 20000,
      maxSize: 244000,
      
      cacheGroups: {
        // 基础框架库
        framework: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'framework',
          chunks: 'all',
          priority: 40,
          enforce: true,
        },
        
        // UI 组件库
        ui: {
          test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
          name: 'ui-vendor',
          chunks: 'all',
          priority: 30,
        },
        
        // 工具库
        lib: {
          test: /[\\/]node_modules[\\/](lodash|moment|dayjs|date-fns)[\\/]/,
          name: 'lib',
          chunks: 'all',
          priority: 20,
        },
        
        // 其他第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10,
          reuseExistingChunk: true,
        },
        
        // 公共业务代码
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          priority: 5,
          reuseExistingChunk: true,
          enforce: true,
        },
        
        // 异步组件
        async: {
          test: /[\\/]src[\\/]components[\\/]async[\\/]/,
          name: 'async-components',
          chunks: 'async',
          priority: 15,
        },
      },
    },
  },
};

代码分割流程图

graph TD A[应用入口] --> B{分析依赖} B --> C[框架代码] B --> D[第三方库] B --> E[业务代码] B --> F[异步组件] C --> G[React Chunk] D --> H[UI库 Chunk] D --> I[工具库 Chunk] D --> J[其他vendor Chunk] E --> K[公共代码 Chunk] E --> L[页面代码 Chunk] F --> M[懒加载 Chunk] G --> N[浏览器缓存] H --> N I --> N J --> N K --> O[条件缓存] L --> P[页面级缓存] M --> Q[按需加载] style A fill:#e3f2fd style G fill:#c8e6c9 style H fill:#fff3e0 style I fill:#fff3e0 style J fill:#fff3e0 style M fill:#fce4ec

输出产物分析【重要】

安装 webpack-bundle-analyzer

javascript 复制代码
// 安装命令
npm install --save-dev webpack-bundle-analyzer

// 或使用 yarn
yarn add -D webpack-bundle-analyzer

在 Webpack 配置中使用

将分析器集成到构建流程中。

javascript 复制代码
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  // 其他配置...
  
  plugins: [
    // 开发环境使用交互式分析
    new BundleAnalyzerPlugin({
      analyzerMode: 'server', // 启动分析服务器
      analyzerHost: '127.0.0.1',
      analyzerPort: 8888,
      openAnalyzer: true,
      generateStatsFile: true,
      statsFilename: 'stats.json',
      statsOptions: null,
      logLevel: 'info',
    }),
    
    // 生产环境生成静态报告
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
      openAnalyzer: false,
      generateStatsFile: true,
      statsFilename: 'bundle-stats.json',
    }),
  ],
};

作为 Webpack 插件使用

创建条件化的分析配置。

javascript 复制代码
// webpack.analyze.js
const { merge } = require('webpack-merge');
const prodConfig = require('./webpack.prod.js');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = merge(prodConfig, {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
      openAnalyzer: true,
      generateStatsFile: true,
      statsFilename: 'stats.json',
      excludeAssets: /\.map$/,
    }),
  ],
});

// package.json 脚本配置
{
  "scripts": {
    "build": "webpack --config webpack.prod.js",
    "analyze": "webpack --config webpack.analyze.js",
    "analyze:server": "webpack-bundle-analyzer dist/stats.json"
  }
}

配置参数说明

详细的配置选项和最佳实践。

javascript 复制代码
// 完整的分析器配置
const analyzerConfig = {
  // 分析模式
  analyzerMode: 'static', // 'server' | 'static' | 'disabled'
  
  // 静态模式配置
  reportFilename: 'bundle-report.html',
  reportTitle: 'Bundle Analysis Report',
  defaultSizes: 'parsed', // 'stat' | 'parsed' | 'gzip'
  
  // 服务器模式配置
  analyzerHost: '127.0.0.1',
  analyzerPort: 'auto', // 自动选择端口
  openAnalyzer: true,
  
  // 统计文件配置
  generateStatsFile: true,
  statsFilename: 'stats.json',
  statsOptions: {
    source: false,
    modules: false,
    chunks: true,
    chunkModules: true,
    optimizationBailout: false,
  },
  
  // 排除特定资源
  excludeAssets: null, // 正则表达式或函数
  
  // 日志级别
  logLevel: 'info', // 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'
};

// 环境特定配置
const getAnalyzerConfig = (env) => {
  const baseConfig = {
    generateStatsFile: true,
    statsFilename: 'stats.json',
    excludeAssets: /\.(map|txt|LICENSE)$/,
  };
  
  if (env === 'development') {
    return {
      ...baseConfig,
      analyzerMode: 'server',
      openAnalyzer: true,
      analyzerPort: 8888,
    };
  }
  
  return {
    ...baseConfig,
    analyzerMode: 'static',
    reportFilename: 'bundle-report.html',
    openAnalyzer: false,
  };
};

通过 CLI 命令使用

直接使用命令行工具进行分析。

javascript 复制代码
// package.json
{
  "scripts": {
    "build:stats": "webpack --config webpack.prod.js --json > stats.json",
    "analyze:cli": "npx webpack-bundle-analyzer stats.json",
    "analyze:size": "npx webpack-bundle-analyzer stats.json --mode static --report bundle-size-report.html",
    "analyze:gzip": "npx webpack-bundle-analyzer stats.json --default-sizes gzip"
  }
}

// 自定义分析脚本
// scripts/analyze.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

class BundleAnalyzer {
  constructor(options = {}) {
    this.options = {
      outputDir: 'dist',
      statsFile: 'stats.json',
      reportFile: 'bundle-report.html',
      ...options,
    };
  }
  
  // 生成构建统计
  generateStats() {
    console.log('🔄 生成构建统计...');
    
    try {
      execSync(
        `webpack --config webpack.prod.js --json > ${this.options.statsFile}`,
        { stdio: 'inherit' }
      );
      console.log('✅ 统计文件生成成功');
    } catch (error) {
      console.error('❌ 统计文件生成失败:', error.message);
      process.exit(1);
    }
  }
  
  // 分析包大小
  analyzeBundle() {
    console.log('📊 分析包大小...');
    
    const statsPath = path.resolve(this.options.statsFile);
    if (!fs.existsSync(statsPath)) {
      console.error('❌ 统计文件不存在,请先运行构建');
      return;
    }
    
    try {
      execSync(
        `npx webpack-bundle-analyzer ${statsPath} --mode static --report ${this.options.reportFile}`,
        { stdio: 'inherit' }
      );
      console.log('✅ 分析报告生成成功');
    } catch (error) {
      console.error('❌ 分析失败:', error.message);
    }
  }
  
  // 检查包大小警告
  checkBundleSize() {
    const statsPath = path.resolve(this.options.statsFile);
    const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
    
    const warnings = [];
    const limits = {
      entrypoint: 250 * 1024, // 250KB
      asset: 100 * 1024,      // 100KB
    };
    
    // 检查入口点大小
    Object.entries(stats.entrypoints).forEach(([name, entry]) => {
      const size = entry.assets.reduce((total, asset) => {
        const assetInfo = stats.assets.find(a => a.name === asset);
        return total + (assetInfo ? assetInfo.size : 0);
      }, 0);
      
      if (size > limits.entrypoint) {
        warnings.push(`入口点 "${name}" 大小 ${(size / 1024).toFixed(2)}KB 超过建议值`);
      }
    });
    
    // 检查资源大小
    stats.assets.forEach(asset => {
      if (asset.size > limits.asset && !asset.name.includes('vendor')) {
        warnings.push(`资源 "${asset.name}" 大小 ${(asset.size / 1024).toFixed(2)}KB 超过建议值`);
      }
    });
    
    if (warnings.length > 0) {
      console.warn('⚠️  包大小警告:');
      warnings.forEach(warning => console.warn(`  - ${warning}`));
    } else {
      console.log('✅ 包大小检查通过');
    }
  }
  
  // 运行完整分析
  run() {
    this.generateStats();
    this.analyzeBundle();
    this.checkBundleSize();
  }
}

// 使用分析器
const analyzer = new BundleAnalyzer();
analyzer.run();

使用 webpack-bundle-analyzer 分析 Bundle

分析结果解读和优化建议。

javascript 复制代码
// 分析结果处理工具
class BundleOptimizer {
  constructor(statsFile) {
    this.stats = JSON.parse(fs.readFileSync(statsFile, 'utf8'));
  }
  
  // 分析重复依赖
  findDuplicateDependencies() {
    const modules = this.stats.modules || [];
    const dependencies = new Map();
    
    modules.forEach(module => {
      if (module.name && module.name.includes('node_modules')) {
        const match = module.name.match(/node_modules\/(.*?)[\\/]/);
        if (match) {
          const packageName = match[1];
          if (!dependencies.has(packageName)) {
            dependencies.set(packageName, []);
          }
          dependencies.get(packageName).push(module);
        }
      }
    });
    
    const duplicates = Array.from(dependencies.entries())
      .filter(([, modules]) => modules.length > 1)
      .map(([packageName, modules]) => ({
        package: packageName,
        count: modules.length,
        totalSize: modules.reduce((sum, mod) => sum + (mod.size || 0), 0),
      }));
    
    return duplicates;
  }
  
  // 分析大文件
  findLargeAssets(threshold = 100 * 1024) {
    return this.stats.assets
      .filter(asset => asset.size > threshold)
      .sort((a, b) => b.size - a.size)
      .map(asset => ({
        name: asset.name,
        size: `${(asset.size / 1024).toFixed(2)}KB`,
        sizeBytes: asset.size,
      }));
  }
  
  // 生成优化建议
  generateRecommendations() {
    const recommendations = [];
    const duplicates = this.findDuplicateDependencies();
    const largeAssets = this.findLargeAssets();
    
    if (duplicates.length > 0) {
      recommendations.push({
        type: 'duplicate-dependencies',
        title: '发现重复依赖',
        description: '以下包存在多个版本,考虑使用 webpack 的 resolve.alias 或升级依赖版本',
        items: duplicates,
      });
    }
    
    if (largeAssets.length > 0) {
      recommendations.push({
        type: 'large-assets',
        title: '大文件警告',
        description: '以下文件较大,考虑进行代码分割或压缩优化',
        items: largeAssets,
      });
    }
    
    return recommendations;
  }
  
  // 生成报告
  generateReport() {
    const recommendations = this.generateRecommendations();
    const totalSize = this.stats.assets.reduce((sum, asset) => sum + asset.size, 0);
    
    return {
      summary: {
        totalSize: `${(totalSize / 1024).toFixed(2)}KB`,
        assetsCount: this.stats.assets.length,
        chunksCount: this.stats.chunks.length,
      },
      recommendations,
    };
  }
}

产物分析流程图

graph LR A[构建完成] --> B[生成stats.json] B --> C{分析模式} C -->|静态分析| D[生成HTML报告] C -->|服务器模式| E[启动分析服务器] C -->|CLI分析| F[命令行输出] D --> G[查看包组成] E --> G F --> G G --> H[识别问题] H --> I[重复依赖] H --> J[大文件] H --> K[无用代码] I --> L[优化策略] J --> L K --> L L --> M[代码分割] L --> N[Tree Shaking] L --> O[依赖优化] M --> P[重新构建] N --> P O --> P P --> Q[验证优化效果] style A fill:#e3f2fd style G fill:#fff3e0 style L fill:#e8f5e8 style Q fill:#c8e6c9

性能监控和持续优化

构建性能指标监控

javascript 复制代码
// 性能监控插件
class BuildPerformancePlugin {
  constructor(options = {}) {
    this.options = {
      outputFile: 'build-performance.json',
      enableDetailedMetrics: true,
      ...options,
    };
    this.metrics = {
      startTime: 0,
      endTime: 0,
      moduleCount: 0,
      chunkCount: 0,
      assetSize: 0,
    };
  }
  
  apply(compiler) {
    compiler.hooks.compile.tap('BuildPerformancePlugin', () => {
      this.metrics.startTime = Date.now();
    });
    
    compiler.hooks.done.tap('BuildPerformancePlugin', (stats) => {
      this.metrics.endTime = Date.now();
      this.metrics.buildTime = this.metrics.endTime - this.metrics.startTime;
      this.metrics.moduleCount = stats.compilation.modules.size;
      this.metrics.chunkCount = stats.compilation.chunks.size;
      this.metrics.assetSize = Array.from(stats.compilation.assets.keys())
        .reduce((total, name) => {
          return total + stats.compilation.assets[name].size();
        }, 0);
      
      this.generateReport(stats);
    });
  }
  
  generateReport(stats) {
    const report = {
      timestamp: new Date().toISOString(),
      metrics: this.metrics,
      recommendations: this.getOptimizationSuggestions(),
      comparison: this.compareWithPrevious(),
    };
    
    if (this.options.outputFile) {
      fs.writeFileSync(
        this.options.outputFile,
        JSON.stringify(report, null, 2)
      );
    }
    
    this.logMetrics();
  }
  
  getOptimizationSuggestions() {
    const suggestions = [];
    
    if (this.metrics.buildTime > 30000) {
      suggestions.push('构建时间过长,考虑启用缓存或使用多进程构建');
    }
    
    if (this.metrics.assetSize > 5 * 1024 * 1024) {
      suggestions.push('产物体积较大,考虑进一步代码分割和压缩');
    }
    
    if (this.metrics.chunkCount > 50) {
      suggestions.push('代码块数量过多,可能影响HTTP/1.1性能');
    }
    
    return suggestions;
  }
  
  logMetrics() {
    console.log('\n📊 构建性能报告:');
    console.log(`⏱️  构建时间: ${this.metrics.buildTime}ms`);
    console.log(`📦 模块数量: ${this.metrics.moduleCount}`);
    console.log(`🎯 代码块数量: ${this.metrics.chunkCount}`);
    console.log(`📊 总体积: ${(this.metrics.assetSize / 1024).toFixed(2)}KB`);
  }
}

module.exports = BuildPerformancePlugin;

通过以上全面的产物构建优化策略,我们可以显著提升应用的加载性能和用户体验。这些优化措施需要根据具体项目特点和性能目标进行调整和组合使用。

相关推荐
小码哥_常1 分钟前
Android新航标:Navigation 3为何成为变革先锋?
前端
SuperEugene2 分钟前
Vue状态管理扫盲篇:状态管理中的常见坑 | 循环依赖、状态污染与调试技巧
前端·vue.js·面试
骑着小黑马3 分钟前
从 Electron 到 Tauri 2:我用 3.5MB 做了个音乐播放器
前端·vue.js·typescript
aykon3 分钟前
DataSource详解以及优势
前端
Mintopia4 分钟前
戴了 30 天智能手环后,我才发现自己一直低估了“睡眠”
前端
leolee184 分钟前
react redux 简单使用
前端·react.js·redux
仰望星空的小猴子5 分钟前
常用的Hooks
前端
天才熊猫君5 分钟前
Vue Fragment 锚点机制
前端
米丘6 分钟前
Git 常用操作命令
前端
星_离9 分钟前
SSE—实时信息推送
前端