Webpack 高级配置

深入理解 Webpack 的高级配置:代码分割、Tree Shaking、运行时优化、Module Federation 微前端架构。


代码分割(Code Splitting)

概念

代码分割是将 bundle 拆分成多个小块,按需加载的技术。

┌──────────────────────────────────────────────────────────────┐
│                   代码分割示意图                                │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   分割前                                                     │
│   ┌─────────────────────────┐                               │
│   │     main.bundle.js      │                               │
│   │   (包含所有代码,体积大)  │                               │
│   └─────────────────────────┘                               │
│                                                              │
│   分割后                                                     │
│   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│   │  main.js    │  │  vendor.js  │  │  lazy.js    │        │
│   │ (主代码)    │  │ (第三方库)  │  │ (按需加载)  │        │
│   └─────────────┘  └─────────────┘  └─────────────┘        │
│                                                              │
└──────────────────────────────────────────────────────────────┘

入口分割

// webpack.config.js
module.exports = {
  entry: {
    main: './src/index.js',
    vendor: './src/vendor.js',
    analytics: './src/analytics.js'  // 独立打包
  },

  output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js'
  }
};

动态导入(推荐)

// 使用 import() 自动代码分割
const getComponent = () => {
  return import('./Component')
    .then(module => {
      const Component = module.default;
      return new Component();
    });
};

// React 中使用 React.lazy
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

// Vue 中使用
const HeavyComponent = () => import('./HeavyComponent.vue');

// 使用命名导入
const module = await import('./utils.js');
const { helper, format } = await import('./utils.js');

配置 optimization.splitChunks

module.exports = {
  optimization: {
    splitChunks: {
      // 'all' | 'async' | 'initial'
      chunks: 'all',

      // 最小块大小(字节)
      minSize: 20000,

      // 最大块大小
      maxSize: 244000,

      // 最小块被引用次数
      minChunks: 1,

      // 最大请求数(并行加载)
      maxAsyncRequests: 30,

      // 最大入口数
      maxInitialRequests: 30,

      // 自动生成名字的分隔符
      automaticNameDelimiter: '~',

      // 缓存组
      cacheGroups: {
        // 默认组
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        },

        // vendors 组
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10
        },

        // common 组
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'initial',
          priority: 5,
          reuseExistingChunk: true
        },

        // 按需加载的组
        async: {
          test: /[\\/]src[\\/]views[\\/]/,
          name: 'async-views',
          chunks: 'async',
          priority: 10
        }
      }
    },

    // 运行时 chunk
    runtimeChunk: 'single',

    // 模块 ID
    moduleIds: 'deterministic'
  }
};

Tree Shaking

概念与原理

Tree Shaking 是通过静态分析移除未使用的代码(dead code)。

// webpack.config.js
module.exports = {
  mode: 'production',  // 生产模式自动启用
  // 或者手动配置
  optimization: {
    usedExports: true,  // 标记未使用的导出
    sideEffects: true    // 启用副作用优化
  }
};

sideEffects 配置

// package.json
{
  "sideEffects": [
    "*.css",
    "*.scss"
  ]
}
/* button.css - 被标记为有副作用,不能被删除 */
.button {
  background: blue;
}
// util.js - 没有副作用,未使用的可以删除
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;

// 如果只用了 add,multiply 会被删除
import { add } from './util.js';

手动标记

// webpack.config.js
module.exports = {
  optimization: {
    sideEffects: {
      // 可以在此处指定哪些文件有副作用
      // 默认会读取 package.json 的 sideEffects
    }
  }
};

CSS Tree Shaking

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

module.exports = {
  plugins: [
    new PurgeCSSPlugin({
      paths: glob.sync(path.join(__dirname, 'src/**/*.js')),
      // 白名单
      whitelist: ['ignored-class', 'hover-state']
    })
  ]
};

运行时优化

持久化缓存

module.exports = {
  cache: {
    type: 'filesystem',  // 使用文件系统缓存
    buildDependencies: {
      config: [__filename]  // 配置变化时清除缓存
    },
    compression: 'gzip'   // 压缩缓存
  }
};

并行构建

// 使用 thread-loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: [
          {
            loader: 'thread-loader',
            options: {
              workers: require('os').cpus().length - 1,
              poolTimeout: Infinity  // 保持 worker
            }
          },
          'babel-loader'
        ]
      }
    ]
  }
};

// 或者使用 esbuild-loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'esbuild-loader',
        options: {
          target: 'es2015'
        }
      }
    ]
  }
};

输出控制

module.exports = {
  output: {
    // 清理输出目录
    clean: true,  // 或 cleanOnceBeforeBuildPatterns

    // 资源模块(Asset Modules)
    assetModuleFilename: 'assets/[hash:8][ext][query]',

    // 唯一哈希
    hashFunction: 'xxhash64'
  }
};

Module Federation(微前端)

概念

Module Federation 允许独立构建的 Webpack bundles 互相共享模块。

┌──────────────────────────────────────────────────────────────┐
│                 Module Federation 架构                        │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   ┌─────────────┐         ┌─────────────┐                   │
│   │   Host App  │         │   Remote    │                   │
│   │  (主应用)   │ ───────→│   App       │                   │
│   │             │  共享模块 │   (远程)    │                   │
│   └─────────────┘         └─────────────┘                   │
│         │                         │                          │
│         │                         │                          │
│         └─────────────┬───────────┘                          │
│                       ▼                                      │
│              ┌─────────────────┐                             │
│              │   Shared        │                            │
│              │   Dependencies  │                            │
│              │ (React, Redux)  │                             │
│              └─────────────────┘                             │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Host(主机)配置

// webpack.host.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;

module.exports = {
  mode: 'production',
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        // 定义远程应用
        remoteApp: 'remote@http://localhost:3001/remoteEntry.js'
      },
      shared: {
        // 共享的依赖(单例模式)
        react: { singleton: true, requiredVersion: deps.react },
        'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
        redux: { singleton: true }
      }
    })
  ]
};

Remote(远程)配置

// webpack.remote.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;

module.exports = {
  mode: 'production',
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote',
      filename: 'remoteEntry.js',  // 远程入口文件名

      // 暴露的模块
      exposes: {
        './Button': './src/components/Button',
        './Modal': './src/components/Modal'
      },

      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        'react-dom': { singleton: true, requiredVersion: deps['react-dom'] }
      }
    })
  ]
};

在 Host 中使用 Remote

// App.jsx
import React, { Suspense } from 'react';

// 动态导入远程模块
const RemoteButton = React.lazy(() => import('remote/Button'));

function App() {
  return (
    <div>
      <h1>Host Application</h1>
      <Suspense fallback={<div>Loading Button...</div>}>
        <RemoteButton />
      </Suspense>
    </div>
  );
}

全局变量方式(不推荐但简单)

// Remote 端
new ModuleFederationPlugin({
  name: 'remote',
  filename: 'remoteEntry.js',
  exposes: {
    './Button': './src/components/Button'
  }
});

// Host 端在 HTML 中引入
// <script src="http://localhost:3001/remoteEntry.js"></script>

// 然后直接使用
const RemoteButton = window.remote.Button;

性能优化进阶

分析工具

// webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
      openAnalyzer: false
    })
  ]
};

预取/预加载

// 在代码中添加注释引导 Webpack
import(/* webpackPrefetch: true */ './Component');
import(/* webpackPreload: true */ './EarlyComponent');

外部扩展(Externals)

module.exports = {
  externals: {
    // 外部 CDN
    react: 'React',
    'react-dom': 'ReactDOM',

    // 外部文件
    lodash: '_'
  }
};

// CDN 引入
// <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>

DLL Plugin(已过时,但了解历史)

// webpack.dll.config.js
const webpack = require('webpack');

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

配置示例:完整生产配置

// webpack.prod.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
  mode: 'production',

  // 输入
  entry: {
    main: './src/index.js'
  },

  // 输出
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
    assetModuleFilename: 'assets/[hash:8][ext][query]',
    clean: true,
    publicPath: '/'
  },

  // 模块解析
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components')
    }
  },

  // 模块规则
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(png|jpe?g|gif|svg|webp)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024
          }
        }
      }
    ]
  },

  // 插件
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      }
    }),

    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css',
      chunkFilename: '[name].[contenthash:8].chunk.css'
    }),

    // 仅在分析时启用
    // new BundleAnalyzerPlugin()
  ],

  // 优化
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true
          }
        }
      }),
      new CssMinimizerPlugin()
    ],

    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    },

    runtimeChunk: 'single',
    moduleIds: 'deterministic'
  },

  // 性能
  performance: {
    hints: 'warning',
    maxEntrypointSize: 512000,
    maxAssetSize: 512000
  },

  // 统计信息
  stats: {
    assets: true,
    chunks: false,
    modules: false
  }
};

这一章想说的

Webpack 高级配置:

  1. 代码分割:入口分割、动态 import、splitChunks 配置
  2. Tree Shaking:移除未使用代码,需要正确配置 sideEffects
  3. 运行时优化:持久化缓存、并行构建、输出控制
  4. Module Federation:微前端架构的核心,让多个独立应用共享模块
  5. 性能分析:使用 bundle-analyzer 分析包体积

掌握这些高级特性才能构建高效的前端工程。


延展阅读