返回首页

Webpack 知识体系补充扩展

问题解析

除了核心概念外,Webpack 还涉及许多重要的工程化实践和高级特性。本文对 Webpack 知识体系进行补充扩展。

核心概念扩展

Module Federation(模块联邦)

Webpack 5 引入的革命性特性,实现微前端架构:

// 远程应用配置(remote-app/webpack.config.js)
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
        './Header': './src/components/Header'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

// 宿主应用配置(host-app/webpack.config.js)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

// 使用远程模块
const RemoteButton = React.lazy(() => import('remoteApp/Button'));

Webpack 5 新特性

module.exports = {
  // 1. 持久化缓存
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  },

  // 2. 资源模块(替代 file-loader/url-loader/raw-loader)
  module: {
    rules: [
      {
        test: /\.png$/,
        type: 'asset/resource'  // 对应 file-loader
      },
      {
        test: /\.svg$/,
        type: 'asset/inline'    // 对应 url-loader
      },
      {
        test: /\.txt$/,
        type: 'asset/source'    // 对应 raw-loader
      },
      {
        test: /\.jpg$/,
        type: 'asset',          // 根据大小自动选择
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024  // 4KB
          }
        }
      }
    ]
  },

  // 3. 改进的 Tree Shaking
  optimization: {
    usedExports: true,
    sideEffects: false,
    innerGraph: true  // 分析嵌套导出
  },

  // 4. 顶层 await
  experiments: {
    topLevelAwait: true
  }
};

热模块替换(HMR)

// webpack.config.js
module.exports = {
  devServer: {
    hot: true  // 开启 HMR
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
};

// 在应用代码中处理 HMR
if (module.hot) {
  module.hot.accept('./print.js', function() {
    console.log('Accepting the updated printMe module!');
    printMe();
  });

  // Vue/React 框架有专门的 HMR 支持
  // Vue: vue-loader 自动处理
  // React: react-refresh-webpack-plugin
}

详细解答

Source Map 配置

module.exports = {
  // source map 类型选择
  devtool: process.env.NODE_ENV === 'production'
    ? 'source-map'           // 生产环境:独立 map 文件
    : 'eval-cheap-module-source-map',  // 开发环境:快速重建

  /* 常用 devtool 选项:
   *
   * 开发环境(快):
   * - eval: 最快,但不准确
   * - eval-cheap-source-map: 较快,行映射
   * - eval-cheap-module-source-map: 推荐,模块级映射
   *
   * 生产环境(安全):
   * - source-map: 独立文件,最完整
   * - hidden-source-map: 不暴露 source map 链接
   * - nosources-source-map: 不暴露源代码
   */
};

环境变量配置

// 方法 1:使用 DefinePlugin
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production'),
      'process.env.API_URL': JSON.stringify('https://api.example.com'),
      __VERSION__: JSON.stringify(require('./package.json').version)
    })
  ]
};

// 方法 2:使用 dotenv
const Dotenv = require('dotenv-webpack');

module.exports = {
  plugins: [
    new Dotenv({
      path: './.env.production'
    })
  ]
};

// .env 文件
NODE_ENV=production
API_URL=https://api.example.com

多环境配置管理

// webpack.common.js - 通用配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ]
};

// webpack.dev.js - 开发配置
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  devServer: {
    port: 3000,
    hot: true,
    open: true
  }
});

// webpack.prod.js - 生产配置
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  devtool: 'source-map',
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true
      })
    ]
  }
});

性能预算

module.exports = {
  performance: {
    // 性能提示类型:error | warning | false
    hints: 'warning',

    // 入口点大小限制
    maxEntrypointSize: 250000,  // 250KB

    // 资源文件大小限制
    maxAssetSize: 250000,

    // 只检查特定类型的资源
    assetFilter: function(assetFilename) {
      return assetFilename.endsWith('.js');
    }
  }
};

深入理解

Webpack 构建流程详解

1. 初始化参数
   - 合并命令行参数、配置文件、默认配置
   - 创建 Compiler 实例

2. 开始编译(run)
   - 触发 beforeRun、run 钩子
   - 创建 Compilation 实例

3. 确定入口(make)
   - 从 entry 开始解析
   - 触发 beforeCompile、compile、make 钩子

4. 编译模块(buildModule)
   - 使用 loader 转换模块
   - 解析模块依赖(require/import)
   - 递归处理所有依赖

5. 完成模块编译(finishMake)
   - 触发 finishMake 钩子

6. 优化阶段(seal)
   - 触发 seal 钩子
   - 优化 chunk、module
   - Tree Shaking、Scope Hoisting

7. 输出资源(emit)
   - 生成资源内容
   - 触发 emit 钩子
   - 写入输出目录

8. 完成(done)
   - 触发 done 钩子
   - 输出统计信息

依赖图构建

// Webpack 如何构建依赖图

// 入口文件
import './a.js';
import './b.js';

// a.js
import './c.js';

// b.js
import './c.js';

// 构建的依赖图
/*
        entry
       /     \
      a       b
       \     /
          c

步骤:
1. 从 entry 开始
2. 解析 import './a.js',添加 a.js 到依赖图
3. 解析 import './b.js',添加 b.js 到依赖图
4. 解析 a.js 中的 import './c.js',添加 c.js
5. 解析 b.js 中的 import './c.js',复用已存在的 c.js
6. 所有模块解析完成
*/

模块解析算法

module.exports = {
  resolve: {
    // 解析规则
    modules: ['node_modules'],  // 查找目录
    extensions: ['.js', '.jsx', '.json'],  // 自动扩展名
    mainFields: ['browser', 'module', 'main'],  // 包入口字段
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'react': path.resolve(__dirname, './node_modules/react')
    },

    // 条件导出(package.json exports 字段)
    conditionNames: ['import', 'require', 'node', 'default']
  }
};

// 解析过程示例
// import 'lodash/debounce'
// 1. 检查是否是绝对路径或相对路径
// 2. 检查 alias 配置
// 3. 在 node_modules 中查找
// 4. 解析 package.json 中的入口
// 5. 添加扩展名

最佳实践

完整的生产环境配置

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  mode: 'production',

  entry: {
    main: './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: {
    modules: [path.resolve(__dirname, 'node_modules')],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
      '@utils': path.resolve(__dirname, 'src/utils')
    },
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json']
  },

  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        include: path.resolve(__dirname, 'src'),
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              cacheCompression: false
            }
          }
        ]
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1
            }
          },
          'postcss-loader'
        ]
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024
          }
        },
        generator: {
          filename: 'images/[name].[hash:8][ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)$/,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[name].[hash:8][ext]'
        }
      }
    ]
  },

  plugins: [
    new CleanWebpackPlugin(),

    new HtmlWebpackPlugin({
      template: './public/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true
      }
    }),

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

    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),

    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 8192,
      minRatio: 0.8
    })
  ],

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

    splitChunks: {
      chunks: 'all',
      minSize: 20000,
      maxSize: 244000,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 20
        },
        common: {
          minChunks: 2,
          chunks: 'all',
          enforce: true,
          priority: 10
        }
      }
    },

    runtimeChunk: {
      name: 'runtime'
    },

    usedExports: true,
    sideEffects: false,
    concatenateModules: true
  },

  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  },

  performance: {
    hints: 'warning',
    maxEntrypointSize: 250000,
    maxAssetSize: 250000
  }
};

常用 Loader 速查

Loader 用途 示例
babel-loader 转译 ES6+ use: 'babel-loader'
ts-loader 编译 TypeScript use: 'ts-loader'
css-loader 处理 CSS 导入 use: ['style-loader', 'css-loader']
style-loader 注入 CSS 到 DOM 与 css-loader 配合使用
sass-loader 编译 Sass/SCSS use: ['css-loader', 'sass-loader']
less-loader 编译 Less use: ['css-loader', 'less-loader']
postcss-loader PostCSS 处理 use: ['css-loader', 'postcss-loader']
file-loader 处理文件 use: 'file-loader'
url-loader 小文件转 base64 use: { loader: 'url-loader', options: { limit: 8192 } }
vue-loader 编译 Vue 单文件 use: 'vue-loader'
eslint-loader ESLint 检查 enforce: 'pre', use: 'eslint-loader'
thread-loader 多线程编译 use: ['thread-loader', 'babel-loader']
cache-loader 缓存结果 use: ['cache-loader', 'babel-loader']

常用 Plugin 速查

Plugin 用途 安装
HtmlWebpackPlugin 生成 HTML 文件 html-webpack-plugin
CleanWebpackPlugin 清理输出目录 clean-webpack-plugin
MiniCssExtractPlugin 提取 CSS mini-css-extract-plugin
CopyWebpackPlugin 复制静态文件 copy-webpack-plugin
DefinePlugin 定义全局常量 内置
ProvidePlugin 自动加载模块 内置
DllPlugin 动态链接库 内置
HotModuleReplacementPlugin 热更新 内置
CompressionPlugin Gzip 压缩 compression-webpack-plugin
BundleAnalyzerPlugin 可视化分析 webpack-bundle-analyzer
SpeedMeasurePlugin 构建速度分析 speed-measure-webpack-plugin

面试要点

  1. Webpack 5 新特性

    • 持久化缓存(filesystem cache)
    • 资源模块(asset modules)
    • Module Federation
    • 改进的 Tree Shaking
    • 顶层 await
  2. Module Federation:实现微前端架构,允许在运行时加载远程模块

  3. HMR 原理:建立 WebSocket 连接,模块更新时增量替换,不刷新页面

  4. Source Map 类型

    • 开发环境:eval-cheap-module-source-map
    • 生产环境:source-maphidden-source-map
  5. 构建流程:初始化参数 -> 开始编译 -> 确定入口 -> 编译模块 -> 完成编译 -> 优化 -> 输出资源 -> 完成

  6. 性能优化方向

    • 构建速度:缓存、多进程、缩小作用域
    • 运行性能:代码分割、Tree Shaking、压缩、CDN
  7. Loader vs Plugin

    • Loader:文件转换,处理单个文件
    • Plugin:生命周期扩展,处理整个构建过程
  8. Tree Shaking 条件

    • 使用 ES6 模块
    • 配置 sideEffects
    • 使用 usedExports
  9. 代码分割方式

    • Entry 分割
    • 动态导入(import())
    • SplitChunksPlugin
  10. 常用工具

    • webpack-bundle-analyzer:可视化分析包大小
    • speed-measure-webpack-plugin:分析构建速度
    • webpackbar:显示构建进度