Webpack 5 生产环境打包优化

231 阅读2分钟

Webpack 5 生产环境打包优化

Webpack 5 提供了许多优化生产环境打包的方法。以下是一些关键的优化策略和详细代码实现:

1. 分离配置文件

首先,建议将开发环境和生产环境的配置分开:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
};
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  devtool: 'source-map',
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin(),
      new CssMinimizerPlugin(),
    ],
  },
});

2. 代码分割 (Code Splitting)

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
// ... 其他引入

module.exports = merge(common, {
  mode: 'production',
  // ... 其他配置
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 20000,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            // 获取第三方库的名称
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    },
    minimizer: [
      new TerserPlugin(),
      new CssMinimizerPlugin(),
    ],
  },
});

3. 压缩 CSS 和 JS

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  // ... 其他配置
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // 移除 console
          },
          format: {
            comments: false, // 移除注释
          },
        },
        extractComments: false,
      }),
      new CssMinimizerPlugin(),
    ],
  },
});

4. 使用 Tree Shaking

Webpack 5 默认在生产模式下启用 Tree Shaking,但你可以进一步优化:

{
  "name": "your-project",
  "sideEffects": [
    "*.css",
    "*.scss"
  ]
}

5. 资源模块优化

// ... 其他引入

module.exports = merge(common, {
  // ... 其他配置
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024 // 8kb 以下转为 inline 资源
          }
        },
        generator: {
          filename: 'images/[name].[hash][ext]'
        }
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[name].[hash][ext]'
        }
      }
    ]
  }
});

6. 使用缓存

module.exports = {
  // ... 其他配置
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  // ... 其他配置
};

7. 使用 Compression Plugin

const CompressionPlugin = require('compression-webpack-plugin');

module.exports = merge(common, {
  // ... 其他配置
  plugins: [
    // ... 其他插件
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 10240, // 只有大于 10kb 的资源才会被处理
      minRatio: 0.8 // 只有压缩率小于这个值的资源才会被处理
    }),
  ],
});

8. 使用 Bundle Analyzer

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = merge(common, {
  // ... 其他配置
  plugins: [
    // ... 其他插件
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'bundle-report.html'
    }),
  ],
});

9. 优化图片

const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');

module.exports = merge(common, {
  // ... 其他配置
  optimization: {
    // ... 其他优化
    minimizer: [
      // ... 其他 minimizer
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminMinify,
          options: {
            plugins: [
              ['gifsicle', { interlaced: true }],
              ['jpegtran', { progressive: true }],
              ['optipng', { optimizationLevel: 5 }],
              ['svgo', { plugins: [{ removeViewBox: false }] }],
            ],
          },
        },
      }),
    ],
  },
});

Webpack 5 生产环境打包优化(续)

10. 使用 Module Federation (微前端)

Module Federation 是 Webpack 5 的一个重要新特性,允许多个独立的构建共享代码:

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = merge(common, {
  // ... 其他配置
  plugins: [
    // ... 其他插件
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      exposes: {
        './Component': './src/Component',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
});

11. 预加载和预获取

通过在 HTML 中添加 <link rel="preload"><link rel="prefetch"> 标签来优化资源加载:

const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');

module.exports = merge(common, {
  // ... 其他配置
  plugins: [
    // ... 其他插件
    new PreloadWebpackPlugin({
      rel: 'preload',
      include: 'initial',
      fileBlacklist: [/\.map$/, /hot-update\.js$/],
    }),
    new PreloadWebpackPlugin({
      rel: 'prefetch',
      include: 'asyncChunks',
    }),
  ],
});

12. 持久化缓存

Webpack 5 引入了持久化缓存功能,可以显著提高重复构建的速度:

module.exports = merge(common, {
  // ... 其他配置
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename],
    },
    name: 'production-cache',
  },
});

13. 优化 CSS

使用 PurgeCSS 移除未使用的 CSS:

const PurgeCSSPlugin = require('purgecss-webpack-plugin');
const glob = require('glob');
const path = require('path');

module.exports = merge(common, {
  // ... 其他配置
  plugins: [
    // ... 其他插件
    new PurgeCSSPlugin({
      paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, { nodir: true }),
      safelist: ['body', 'html'],
    }),
  ],
});

14. 使用 DLL Plugin 进一步优化

虽然 Webpack 5 的缓存机制已经很好,但对于特别大的项目,DLL 仍然有用:

const path = require('path');
const webpack = require('webpack');

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

然后在主配置中引用:

const webpack = require('webpack');

module.exports = merge(common, {
  // ... 其他配置
  plugins: [
    // ... 其他插件
    new webpack.DllReferencePlugin({
      manifest: require('./dist/dll/vendor-manifest.json'),
    }),
  ],
});

15. 使用 SplitChunksPlugin 的高级配置

module.exports = merge(common, {
  // ... 其他配置
  optimization: {
    // ... 其他优化
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: 30,
      minSize: 20000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        framework: {
          test: /[\\/]node_modules[\\/](react|react-dom|redux|react-redux)[\\/]/,
          name: 'framework',
          priority: 40,
          reuseExistingChunk: true,
        },
        ui: {
          test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
          name: 'ui',
          priority: 30,
          reuseExistingChunk: true,
        },
        utils: {
          test: /[\\/]node_modules[\\/](lodash|moment|axios)[\\/]/,
          name: 'utils',
          priority: 20,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
});

16. 使用 Webpack 5 的资产模块类型

module.exports = merge(common, {
  // ... 其他配置
  module: {
    rules: [
      // ... 其他规则
      {
        test: /\.svg$/,
        type: 'asset/inline', // 内联为 Data URL
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/i,
        type: 'asset', // 自动选择 inline 或 resource
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024, // 4kb
          },
        },
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource', // 输出单独文件
      },
    ],
  },
});

通过以上这些优化措施,你可以显著提高 Webpack 5 在生产环境中的打包效率和输出质量。根据项目的具体需求,可以选择性地应用这些优化策略。