Skip to content

Webpack 配置

功能强大的模块打包器配置指南,从安装到企业级配置的完整覆盖。

快速开始

安装 Webpack

基础安装

bash
# 安装 Webpack 和 CLI
npm install -D webpack webpack-cli
yarn add -D webpack webpack-cli
pnpm add -D webpack webpack-cli

# 安装常用 loader 和 plugin
npm install -D babel-loader @babel/core @babel/preset-env
npm install -D css-loader style-loader
npm install -D html-webpack-plugin

创建基础配置

javascript
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
  mode: "development",
};

基础项目结构

my-webpack-project/
├── webpack.config.js   # Webpack 配置文件
├── package.json        # 项目配置
├── src/
│   ├── index.js        # 应用入口
│   ├── index.html      # HTML 模板
│   └── components/     # 组件目录
└── dist/               # 构建输出目录

基础命令

bash
# 开发模式构建
npx webpack --mode development

# 生产模式构建
npx webpack --mode production

# 开发服务器(需要安装 webpack-dev-server)
npm install -D webpack-dev-server
npx webpack serve --mode development

兼容性要求

  • Node.js: 版本 10.13.0+
  • 浏览器: 支持 ES5 的浏览器(可通过 Babel 转译支持更老版本)

Package.json 脚本配置

json
{
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "webpack --mode production",
    "build:dev": "webpack --mode development"
  }
}

🚀 Entry & Output 配置

单入口配置

javascript
module.exports = {
  // 单入口文件
  entry: "./src/index.js",

  output: {
    // 输出目录(绝对路径)
    path: path.resolve(__dirname, "dist"),
    // 输出文件名
    filename: "bundle.js",
    // 清理输出目录
    clean: true,
  },
};

多入口配置

javascript
module.exports = {
  entry: {
    // 主应用入口
    app: "./src/app.js",
    // 管理后台入口
    admin: "./src/admin.js",
    // 第三方库入口
    vendor: ["react", "react-dom", "lodash"],
  },

  output: {
    path: path.resolve(__dirname, "dist"),
    // 使用占位符生成不同文件名
    filename: "[name].[contenthash:8].js",
    // chunk 文件命名
    chunkFilename: "[name].[contenthash:8].chunk.js",
    // 静态资源路径前缀
    publicPath:
      process.env.NODE_ENV === "production" ? "https://cdn.example.com/" : "/",
  },
};

动态入口配置

javascript
const glob = require("glob");
const path = require("path");

// 自动扫描页面入口
function getEntries() {
  const entries = {};
  const files = glob.sync("./src/pages/*/index.js");

  files.forEach((file) => {
    const name = path.basename(path.dirname(file));
    entries[name] = file;
  });

  return entries;
}

module.exports = {
  entry: getEntries(),
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name]/bundle.[contenthash:8].js",
  },
};

使用场景与注意事项:

  • 单入口:适用于 SPA 应用,配置简单
  • 多入口:适用于多页面应用或微前端架构
  • 动态入口:适用于页面较多的大型项目,自动化管理入口
  • contenthash:基于内容生成哈希,支持长期缓存
  • publicPath:生产环境建议使用 CDN 路径

🔄 Loader 配置

JavaScript/TypeScript 处理

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              [
                "@babel/preset-env",
                {
                  targets: "> 1%, last 2 versions",
                  useBuiltIns: "usage",
                  corejs: 3,
                },
              ],
              "@babel/preset-react",
            ],
            plugins: [
              "@babel/plugin-proposal-class-properties",
              "@babel/plugin-syntax-dynamic-import",
            ],
          },
        },
      },
      {
        test: /\.(ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          "babel-loader",
          {
            loader: "ts-loader",
            options: {
              transpileOnly: true, // 提升编译速度
            },
          },
        ],
      },
    ],
  },
};

CSS 样式处理

javascript
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 生产环境提取 CSS
          process.env.NODE_ENV === "production"
            ? MiniCssExtractPlugin.loader
            : "style-loader",
          {
            loader: "css-loader",
            options: {
              // CSS 模块化
              modules: {
                localIdentName: "[name]__[local]__[hash:base64:5]",
              },
              // 启用 source map
              sourceMap: true,
            },
          },
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: ["autoprefixer", "postcss-preset-env"],
              },
            },
          },
        ],
      },
    ],
  },
};

Sass/Less 预处理器

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.(scss|sass)$/,
        use: [
          process.env.NODE_ENV === "production"
            ? MiniCssExtractPlugin.loader
            : "style-loader",
          "css-loader",
          "postcss-loader",
          {
            loader: "sass-loader",
            options: {
              // 全局变量和混合宏
              additionalData: `
                @import "@/styles/variables.scss";
                @import "@/styles/mixins.scss";
              `,
              sassOptions: {
                includePaths: [path.resolve(__dirname, "src/styles")],
              },
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          process.env.NODE_ENV === "production"
            ? MiniCssExtractPlugin.loader
            : "style-loader",
          "css-loader",
          "postcss-loader",
          {
            loader: "less-loader",
            options: {
              lessOptions: {
                modifyVars: {
                  "@primary-color": "#1890ff",
                },
                javascriptEnabled: true,
              },
            },
          },
        ],
      },
    ],
  },
};

静态资源处理

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024, // 8KB 以下内联
          },
        },
        generator: {
          filename: "images/[name].[hash:8][ext]",
        },
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        type: "asset/resource",
        generator: {
          filename: "fonts/[name].[hash:8][ext]",
        },
      },
      {
        test: /\.svg$/,
        oneOf: [
          // React 组件方式使用
          {
            resourceQuery: /component/,
            use: ["@svgr/webpack"],
          },
          // 普通资源方式使用
          {
            type: "asset/resource",
            generator: {
              filename: "icons/[name].[hash:8][ext]",
            },
          },
        ],
      },
    ],
  },
};

使用场景与注意事项:

  • Babel 配置:根据目标浏览器配置 preset-env,启用按需 polyfill
  • TypeScript:使用 transpileOnly 提升编译速度,类型检查交给 IDE
  • CSS 模块化:大型项目建议启用,避免样式冲突
  • 预处理器:全局变量导入避免在每个文件中重复引入
  • 静态资源:合理设置内联阈值,平衡文件数量和体积

🔌 Plugin 配置

HTML 模板插件

javascript
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  plugins: [
    // 单页面应用
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      filename: "index.html",
      chunks: ["app"], // 指定引入的 chunk
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
      },
      // 自定义变量注入
      templateParameters: {
        title: "My App",
        description: "Enterprise Application",
      },
    }),

    // 多页面应用
    new HtmlWebpackPlugin({
      template: "./src/admin.html",
      filename: "admin.html",
      chunks: ["admin"],
      minify: process.env.NODE_ENV === "production",
    }),
  ],
};

CSS 提取插件

javascript
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  plugins: [
    // CSS 提取
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css",
      chunkFilename: "css/[name].[contenthash:8].chunk.css",
      // 忽略 JS 中的 CSS 顺序警告
      ignoreOrder: true,
    }),
  ],

  optimization: {
    minimizer: [
      // CSS 压缩
      new CssMinimizerPlugin({
        parallel: true, // 并行处理
        minimizerOptions: {
          preset: [
            "default",
            {
              discardComments: { removeAll: true },
            },
          ],
        },
      }),
    ],
  },
};

环境变量插件

javascript
const webpack = require("webpack");

module.exports = {
  plugins: [
    // 定义全局常量
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
      "process.env.API_BASE_URL": JSON.stringify(process.env.API_BASE_URL),
      __VERSION__: JSON.stringify(require("./package.json").version),
      __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
    }),

    // 环境变量注入
    new webpack.EnvironmentPlugin({
      NODE_ENV: "development",
      DEBUG: false,
      API_TIMEOUT: 5000,
    }),
  ],
};

代码分析插件

javascript
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
  plugins: [
    // 包分析(开发环境)
    process.env.ANALYZE &&
      new BundleAnalyzerPlugin({
        analyzerMode: "server",
        openAnalyzer: true,
        analyzerPort: 8888,
      }),

    // Gzip 压缩(生产环境)
    process.env.NODE_ENV === "production" &&
      new CompressionPlugin({
        algorithm: "gzip",
        test: /\.(js|css|html|svg)$/,
        threshold: 8192,
        minRatio: 0.8,
      }),

    // Brotli 压缩(生产环境)
    process.env.NODE_ENV === "production" &&
      new CompressionPlugin({
        filename: "[path][base].br",
        algorithm: "brotliCompress",
        test: /\.(js|css|html|svg)$/,
        compressionOptions: {
          params: {
            [require("zlib").constants.BROTLI_PARAM_QUALITY]: 11,
          },
        },
        threshold: 8192,
        minRatio: 0.8,
      }),
  ].filter(Boolean),
};

进度和通知插件

javascript
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
const WebpackBuildNotifierPlugin = require("webpack-build-notifier");

module.exports = {
  plugins: [
    // 构建进度条
    new ProgressBarPlugin({
      format: "Building [:bar] :percent (:elapsed seconds)",
      clear: false,
      width: 60,
    }),

    // 友好的错误提示
    new FriendlyErrorsWebpackPlugin({
      compilationSuccessInfo: {
        messages: ["Your application is running here: http://localhost:3000"],
      },
      onErrors: (severity, errors) => {
        if (severity !== "error") return;
        console.error("Build failed with errors.");
      },
      clearConsole: true,
    }),

    // 构建完成通知
    new WebpackBuildNotifierPlugin({
      title: "Webpack Build",
      suppressSuccess: false,
      suppressWarning: true,
      messageFormatter: () => "Build completed successfully!",
    }),
  ],
};

使用场景与注意事项:

  • HtmlWebpackPlugin:自动生成 HTML 文件,支持模板变量注入
  • MiniCssExtractPlugin:生产环境必备,提取 CSS 到独立文件
  • DefinePlugin:编译时常量替换,支持环境变量和版本信息
  • 压缩插件:生产环境启用 Gzip/Brotli 压缩,减少传输体积
  • 分析插件:定期分析包体积,优化依赖结构

📦 代码分割与优化

SplitChunks 基础配置

javascript
module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all", // 对所有 chunk 进行分割
      cacheGroups: {
        // 第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all",
          priority: 10,
        },

        // 公共代码
        common: {
          name: "common",
          minChunks: 2,
          chunks: "all",
          priority: 5,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

高级分包策略

javascript
module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 20000,
      maxSize: 244000,
      cacheGroups: {
        // 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,
        },

        // 工具库
        utils: {
          test: /[\\/]node_modules[\\/](lodash|moment|dayjs)[\\/]/,
          name: "utils",
          chunks: "all",
          priority: 12,
        },

        // 其他第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor",
          chunks: "all",
          priority: 10,
          reuseExistingChunk: true,
        },

        // 业务公共代码
        common: {
          name: "common",
          minChunks: 2,
          chunks: "all",
          priority: 5,
          reuseExistingChunk: true,
          enforce: true,
        },
      },
    },

    // 运行时代码单独提取
    runtimeChunk: {
      name: "runtime",
    },
  },
};

动态导入与懒加载

javascript
// 路由级别的代码分割
const Home = React.lazy(() => import("./pages/Home"));
const About = React.lazy(() => import("./pages/About"));

// 组件级别的代码分割
const HeavyComponent = React.lazy(() =>
  import(/* webpackChunkName: "heavy-component" */ "./HeavyComponent")
);

// 条件加载
async function loadChart() {
  if (needChart) {
    const { Chart } = await import(
      /* webpackChunkName: "chart" */
      "./Chart"
    );
    return Chart;
  }
}

// 预加载
import(
  /* webpackChunkName: "utils" */
  /* webpackPreload: true */
  "./utils"
);

// 预获取
import(
  /* webpackChunkName: "feature" */
  /* webpackPrefetch: true */
  "./feature"
);

缓存优化配置

javascript
module.exports = {
  optimization: {
    // 模块 ID 稳定化
    moduleIds: "deterministic",
    chunkIds: "deterministic",

    splitChunks: {
      cacheGroups: {
        // 稳定的第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor",
          chunks: "all",
          // 确保 vendor 包内容稳定
          enforce: true,
        },
      },
    },
  },

  // 持久化缓存(Webpack 5)
  cache: {
    type: "filesystem",
    buildDependencies: {
      config: [__filename],
    },
  },
};

使用场景与注意事项:

  • 基础分包:适用于中小型项目,简单有效
  • 高级分包:大型项目必备,精确控制包大小和缓存策略
  • 动态导入:实现按需加载,提升首屏性能
  • 缓存优化:确保文件哈希稳定,最大化缓存效果
  • 预加载策略:合理使用 preload/prefetch 提升用户体验

🔗 路径别名配置

基础别名配置

javascript
const path = require("path");

module.exports = {
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
      "@components": path.resolve(__dirname, "src/components"),
      "@pages": path.resolve(__dirname, "src/pages"),
      "@utils": path.resolve(__dirname, "src/utils"),
      "@assets": path.resolve(__dirname, "src/assets"),
      "@styles": path.resolve(__dirname, "src/styles"),
    },

    // 文件扩展名自动解析
    extensions: [".js", ".jsx", ".ts", ".tsx", ".json", ".css", ".scss"],
  },
};

企业级别名管理

javascript
const path = require("path");

// 别名配置函数
function createAlias(rootPath) {
  const srcPath = path.resolve(rootPath, "src");

  return {
    // 基础路径
    "@": srcPath,
    "~": srcPath,

    // 业务模块
    "@/components": path.resolve(srcPath, "components"),
    "@/pages": path.resolve(srcPath, "pages"),
    "@/layouts": path.resolve(srcPath, "layouts"),
    "@/hooks": path.resolve(srcPath, "hooks"),
    "@/store": path.resolve(srcPath, "store"),
    "@/router": path.resolve(srcPath, "router"),

    // 资源文件
    "@/assets": path.resolve(srcPath, "assets"),
    "@/images": path.resolve(srcPath, "assets/images"),
    "@/styles": path.resolve(srcPath, "assets/styles"),
    "@/fonts": path.resolve(srcPath, "assets/fonts"),

    // 工具和配置
    "@/utils": path.resolve(srcPath, "utils"),
    "@/api": path.resolve(srcPath, "api"),
    "@/config": path.resolve(srcPath, "config"),
    "@/constants": path.resolve(srcPath, "constants"),
    "@/types": path.resolve(srcPath, "types"),

    // 第三方库别名
    react: path.resolve(__dirname, "node_modules/react"),
    "react-dom": path.resolve(__dirname, "node_modules/react-dom"),
  };
}

module.exports = {
  resolve: {
    alias: createAlias(__dirname),
    extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],

    // 模块解析优先级
    modules: [path.resolve(__dirname, "src"), "node_modules"],
  },
};

TypeScript 支持

json
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@/components/*": ["src/components/*"],
      "@/pages/*": ["src/pages/*"],
      "@/utils/*": ["src/utils/*"],
      "@/assets/*": ["src/assets/*"],
      "@/styles/*": ["src/assets/styles/*"]
    }
  }
}

使用场景与注意事项:

  • 简化导入:避免复杂的相对路径 ../../../
  • 提升维护性:重构时只需修改别名配置
  • 团队协作:统一的路径规范,提高代码可读性
  • TypeScript 同步:确保 tsconfig.json 与 webpack 配置一致
  • 构建优化:合理配置 modules 提升模块解析速度

🌐 DevServer 与代理配置

基础开发服务器配置

javascript
module.exports = {
  devServer: {
    // 服务器配置
    host: "0.0.0.0", // 允许外部访问
    port: 3000,
    open: true, // 自动打开浏览器

    // 热更新配置
    hot: true,
    liveReload: true,

    // 静态文件服务
    static: {
      directory: path.join(__dirname, "public"),
      publicPath: "/static",
    },

    // 历史路由支持
    historyApiFallback: {
      index: "/index.html",
      rewrites: [
        { from: /^\/admin/, to: "/admin.html" },
        { from: /./, to: "/index.html" },
      ],
    },

    // 压缩
    compress: true,

    // HTTPS 配置
    https: false, // 或者提供证书配置

    // 客户端日志级别
    client: {
      logging: "info",
      overlay: {
        errors: true,
        warnings: false,
      },
    },
  },
};

代理配置

javascript
module.exports = {
  devServer: {
    proxy: {
      // 基础代理
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true,
        pathRewrite: {
          "^/api": "", // 重写路径
        },
      },

      // 多环境代理
      "/api/v1": {
        target: process.env.API_BASE_URL || "http://localhost:8080",
        changeOrigin: true,
        secure: false, // 忽略 HTTPS 证书验证
        logLevel: "debug",
      },

      // WebSocket 代理
      "/ws": {
        target: "ws://localhost:8080",
        ws: true,
        changeOrigin: true,
      },

      // 条件代理
      "/upload": {
        target: "http://localhost:8080",
        changeOrigin: true,
        bypass: function (req, res, proxyOptions) {
          // 开发环境跳过某些请求
          if (req.headers.accept && req.headers.accept.indexOf("html") !== -1) {
            return "/index.html";
          }
        },
      },
    },
  },
};

高级代理配置

javascript
const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = {
  devServer: {
    setupMiddlewares: (middlewares, devServer) => {
      // 自定义中间件
      devServer.app.use(
        "/api",
        createProxyMiddleware({
          target: "http://localhost:8080",
          changeOrigin: true,
          pathRewrite: {
            "^/api": "",
          },
          onProxyReq: (proxyReq, req, res) => {
            // 添加自定义请求头
            proxyReq.setHeader("X-Forwarded-Host", req.headers.host);
            console.log("Proxying request:", req.url);
          },
          onProxyRes: (proxyRes, req, res) => {
            // 处理响应
            console.log("Proxy response:", proxyRes.statusCode);
          },
          onError: (err, req, res) => {
            console.error("Proxy error:", err);
          },
        })
      );

      return middlewares;
    },
  },
};

多环境配置

javascript
// webpack.dev.js
const getProxyConfig = () => {
  const env = process.env.NODE_ENV || "development";

  const proxyConfigs = {
    development: {
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true,
      },
    },
    test: {
      "/api": {
        target: "https://test-api.example.com",
        changeOrigin: true,
        secure: true,
      },
    },
    staging: {
      "/api": {
        target: "https://staging-api.example.com",
        changeOrigin: true,
        secure: true,
      },
    },
  };

  return proxyConfigs[env] || proxyConfigs.development;
};

module.exports = {
  devServer: {
    proxy: getProxyConfig(),

    // 环境特定配置
    port: process.env.PORT || 3000,
    host: process.env.HOST || "localhost",
  },
};

使用场景与注意事项:

  • 本地开发:配置热更新和自动刷新提升开发体验
  • 接口代理:解决开发环境跨域问题,支持多后端服务
  • 路由支持:SPA 应用需要配置 historyApiFallback
  • HTTPS 调试:本地开发时可能需要 HTTPS 环境
  • 性能优化:合理配置日志级别和覆盖层显示

🐛 Source Map 配置

开发环境 Source Map

javascript
module.exports = {
  mode: "development",

  // 开发环境推荐配置
  devtool: "eval-cheap-module-source-map",

  // 或者根据需求选择
  // devtool: 'eval-source-map', // 最高质量,较慢
  // devtool: 'cheap-module-source-map', // 较快,行级别映射
};

生产环境 Source Map

javascript
module.exports = {
  mode: "production",

  // 生产环境配置选项
  devtool: process.env.GENERATE_SOURCEMAP === "true" ? "source-map" : false,

  // 或者隐藏源码的配置
  // devtool: 'hidden-source-map', // 不在 bundle 中引用
  // devtool: 'nosources-source-map', // 不包含源码内容
};

条件化 Source Map 配置

javascript
const getDevtool = () => {
  const env = process.env.NODE_ENV;
  const enableSourceMap = process.env.GENERATE_SOURCEMAP !== "false";

  if (env === "development") {
    return "eval-cheap-module-source-map";
  }

  if (env === "production") {
    if (!enableSourceMap) return false;

    // 根据部署环境决定
    if (process.env.DEPLOY_ENV === "staging") {
      return "source-map"; // 测试环境保留完整映射
    }

    return "hidden-source-map"; // 生产环境隐藏映射
  }

  return false;
};

module.exports = {
  devtool: getDevtool(),

  // CSS Source Map
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              sourceMap: process.env.NODE_ENV === "development",
            },
          },
        ],
      },
    ],
  },
};

Source Map 类型对比

javascript
// Source Map 配置对比表
const sourceMapOptions = {
  // 开发环境推荐
  "eval-cheap-module-source-map": {
    quality: "较高",
    speed: "快",
    description: "每个模块使用 eval() 执行,行级别映射",
  },

  "eval-source-map": {
    quality: "最高",
    speed: "较慢",
    description: "每个模块使用 eval() 执行,完整映射",
  },

  // 生产环境选项
  "source-map": {
    quality: "最高",
    speed: "最慢",
    description: "生成独立的 .map 文件",
  },

  "hidden-source-map": {
    quality: "最高",
    speed: "最慢",
    description: "生成 .map 文件但不在 bundle 中引用",
  },

  "nosources-source-map": {
    quality: "高",
    speed: "较慢",
    description: "不包含源码内容,只有映射信息",
  },
};

使用场景与注意事项:

  • 开发环境:优先考虑构建速度,推荐 eval-cheap-module-source-map
  • 生产环境:根据安全需求选择,可使用 hidden-source-map 隐藏源码
  • 调试需求:完整的源码映射有助于生产环境问题排查
  • 构建性能:Source Map 会显著影响构建时间,需要权衡
  • 文件大小:Source Map 文件通常很大,考虑存储和传输成本

⚡ 构建优化策略

构建速度优化

javascript
const path = require("path");

module.exports = {
  // 缓存配置(Webpack 5)
  cache: {
    type: "filesystem",
    cacheDirectory: path.resolve(__dirname, ".webpack_cache"),
    buildDependencies: {
      config: [__filename],
    },
  },

  // 并行处理
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "thread-loader",
            options: {
              workers: require("os").cpus().length - 1,
            },
          },
          "babel-loader",
        ],
      },
    ],
  },

  // 解析优化
  resolve: {
    // 减少解析步骤
    modules: [path.resolve(__dirname, "src"), "node_modules"],

    // 明确扩展名顺序
    extensions: [".js", ".jsx", ".ts", ".tsx"],

    // 别名配置
    alias: {
      "@": path.resolve(__dirname, "src"),
    },

    // 优化第三方库解析
    mainFields: ["browser", "module", "main"],
  },

  // 排除不必要的解析
  module: {
    noParse: /jquery|lodash/,
  },
};

Tree Shaking 配置

javascript
module.exports = {
  mode: "production",

  optimization: {
    // 启用 Tree Shaking
    usedExports: true,
    sideEffects: false, // 或者在 package.json 中配置

    // 压缩配置
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // 移除 console
            drop_debugger: true, // 移除 debugger
            pure_funcs: ["console.log"], // 移除特定函数调用
          },
        },
      }),
    ],
  },

  // package.json 中的 sideEffects 配置示例
  /*
  {
    "sideEffects": [
      "*.css",
      "*.scss",
      "./src/polyfills.js"
    ]
  }
  */
};

持久化缓存策略

javascript
module.exports = {
  output: {
    // 稳定的文件名哈希
    filename: "[name].[contenthash:8].js",
    chunkFilename: "[name].[contenthash:8].chunk.js",
  },

  optimization: {
    // 稳定的模块 ID
    moduleIds: "deterministic",
    chunkIds: "deterministic",

    // 运行时代码分离
    runtimeChunk: {
      name: "runtime",
    },

    splitChunks: {
      cacheGroups: {
        // 稳定的第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor",
          chunks: "all",
          enforce: true,
        },
      },
    },
  },
};

包体积优化

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

module.exports = {
  plugins: [
    // 包分析
    process.env.ANALYZE && new BundleAnalyzerPlugin(),
  ],

  optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 20000,
      maxSize: 244000, // 限制包大小

      cacheGroups: {
        // 按需分包
        lodash: {
          test: /[\\/]node_modules[\\/]lodash[\\/]/,
          name: "lodash",
          chunks: "all",
        },
      },
    },
  },

  // 外部化大型库
  externals: {
    react: "React",
    "react-dom": "ReactDOM",
    lodash: "_",
  },
};

使用场景与注意事项:

  • 缓存策略:Webpack 5 的持久化缓存显著提升重复构建速度
  • 并行处理:thread-loader 适用于耗时的 loader,如 babel-loader
  • Tree Shaking:确保第三方库支持 ES modules,正确配置 sideEffects
  • 包体积控制:定期分析包体积,识别和优化大型依赖
  • 外部化依赖:CDN 场景下外部化常用库,减少包体积

📁 静态资源处理

资源文件命名策略

javascript
module.exports = {
  output: {
    // 基础文件命名
    filename: "[name].[contenthash:8].js",
    chunkFilename: "[name].[contenthash:8].chunk.js",

    // 静态资源命名
    assetModuleFilename: (pathData) => {
      const filepath = path.dirname(pathData.filename).split("/").slice(1);

      // 根据文件类型分类
      if (/\.(png|jpe?g|gif|svg|webp)$/i.test(pathData.filename)) {
        return "images/[name].[hash:8][ext]";
      }

      if (/\.(woff|woff2|eot|ttf|otf)$/i.test(pathData.filename)) {
        return "fonts/[name].[hash:8][ext]";
      }

      if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)$/i.test(pathData.filename)) {
        return "media/[name].[hash:8][ext]";
      }

      return "assets/[name].[hash:8][ext]";
    },
  },
};

CDN 配置

javascript
const isProd = process.env.NODE_ENV === "production";
const CDN_URL = "https://cdn.example.com/";

module.exports = {
  output: {
    // 生产环境使用 CDN
    publicPath: isProd ? CDN_URL : "/",

    // 跨域资源加载
    crossOriginLoading: "anonymous",
  },

  // 外部化 CDN 资源
  externals: isProd
    ? {
        react: "React",
        "react-dom": "ReactDOM",
        antd: "antd",
        moment: "moment",
      }
    : {},

  plugins: [
    // HTML 模板中注入 CDN 链接
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      cdnUrls: isProd
        ? [
            "https://unpkg.com/react@17/umd/react.production.min.js",
            "https://unpkg.com/react-dom@17/umd/react-dom.production.min.js",
          ]
        : [],
    }),
  ],
};

资源优化配置

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024, // 8KB 以下内联
          },
        },
        use: [
          {
            loader: "image-webpack-loader",
            options: {
              mozjpeg: {
                progressive: true,
                quality: 80,
              },
              optipng: {
                enabled: false,
              },
              pngquant: {
                quality: [0.65, 0.9],
                speed: 4,
              },
              gifsicle: {
                interlaced: false,
              },
              webp: {
                quality: 80,
              },
            },
          },
        ],
      },
    ],
  },
};

多环境资源配置

javascript
const getAssetConfig = (env) => {
  const configs = {
    development: {
      publicPath: "/",
      optimization: false,
    },
    test: {
      publicPath: "https://test-cdn.example.com/",
      optimization: true,
    },
    production: {
      publicPath: "https://cdn.example.com/",
      optimization: true,
    },
  };

  return configs[env] || configs.development;
};

const assetConfig = getAssetConfig(process.env.NODE_ENV);

module.exports = {
  output: {
    publicPath: assetConfig.publicPath,
  },

  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/,
        use: assetConfig.optimization
          ? ["file-loader", "image-webpack-loader"]
          : ["file-loader"],
      },
    ],
  },
};

使用场景与注意事项:

  • 文件命名:使用 contenthash 确保缓存失效,按类型分目录管理
  • CDN 部署:生产环境使用 CDN 加速,注意跨域配置
  • 资源优化:图片压缩和格式转换,平衡质量和体积
  • 多环境管理:不同环境使用不同的资源策略
  • 缓存策略:合理设置缓存头,最大化缓存效果

🏢 企业级配置模板

基础项目模板

javascript
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const isDev = process.env.NODE_ENV === "development";

module.exports = {
  mode: isDev ? "development" : "production",

  entry: "./src/index.js",

  output: {
    path: path.resolve(__dirname, "dist"),
    filename: isDev ? "[name].js" : "[name].[contenthash:8].js",
    clean: true,
  },

  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
      {
        test: /\.css$/,
        use: [
          isDev ? "style-loader" : MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024,
          },
        },
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),

    !isDev &&
      new MiniCssExtractPlugin({
        filename: "css/[name].[contenthash:8].css",
      }),
  ].filter(Boolean),

  devServer: isDev
    ? {
        port: 3000,
        hot: true,
        historyApiFallback: true,
      }
    : undefined,

  devtool: isDev ? "eval-cheap-module-source-map" : false,
};

React 企业级模板

javascript
// webpack.config.js
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = (env, argv) => {
  const isDev = argv.mode === "development";
  const isProd = argv.mode === "production";

  return {
    entry: {
      app: "./src/index.js",
    },

    output: {
      path: path.resolve(__dirname, "dist"),
      filename: isDev ? "[name].js" : "[name].[contenthash:8].js",
      chunkFilename: isDev
        ? "[name].chunk.js"
        : "[name].[contenthash:8].chunk.js",
      publicPath: "/",
      clean: true,
    },

    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"],
    },

    module: {
      rules: [
        {
          test: /\.(js|jsx|ts|tsx)$/,
          exclude: /node_modules/,
          use: [
            {
              loader: "babel-loader",
              options: {
                presets: [
                  ["@babel/preset-env", { useBuiltIns: "usage", corejs: 3 }],
                  "@babel/preset-react",
                  "@babel/preset-typescript",
                ],
                plugins: [
                  "@babel/plugin-proposal-class-properties",
                  isDev && "react-refresh/babel",
                ].filter(Boolean),
              },
            },
          ],
        },

        {
          test: /\.(css|scss)$/,
          use: [
            isDev ? "style-loader" : MiniCssExtractPlugin.loader,
            {
              loader: "css-loader",
              options: {
                modules: {
                  auto: true,
                  localIdentName: isDev
                    ? "[name]__[local]__[hash:base64:5]"
                    : "[hash:base64:5]",
                },
              },
            },
            "postcss-loader",
            "sass-loader",
          ],
        },

        {
          test: /\.(png|jpe?g|gif|svg|webp)$/,
          type: "asset",
          parser: {
            dataUrlCondition: {
              maxSize: 8 * 1024,
            },
          },
          generator: {
            filename: "images/[name].[hash:8][ext]",
          },
        },
      ],
    },

    plugins: [
      new HtmlWebpackPlugin({
        template: "./public/index.html",
        minify: isProd,
      }),

      new webpack.DefinePlugin({
        "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
        "process.env.REACT_APP_API_URL": JSON.stringify(
          process.env.REACT_APP_API_URL
        ),
      }),

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

      process.env.ANALYZE && new BundleAnalyzerPlugin(),
    ].filter(Boolean),

    optimization: {
      splitChunks: {
        chunks: "all",
        cacheGroups: {
          react: {
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
            name: "react",
            chunks: "all",
          },
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: "vendor",
            chunks: "all",
            priority: 10,
          },
        },
      },
      runtimeChunk: {
        name: "runtime",
      },
    },

    devServer: isDev
      ? {
          port: 3000,
          hot: true,
          historyApiFallback: true,
          proxy: {
            "/api": {
              target: "http://localhost:8080",
              changeOrigin: true,
            },
          },
        }
      : undefined,

    devtool: isDev ? "eval-cheap-module-source-map" : "source-map",
  };
};

多页面应用模板

javascript
// webpack.config.js
const path = require("path");
const glob = require("glob");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// 自动生成入口和 HTML 插件
function generateEntries() {
  const entries = {};
  const htmlPlugins = [];

  const pages = glob.sync("./src/pages/*/index.js");

  pages.forEach((page) => {
    const pageName = path.basename(path.dirname(page));
    entries[pageName] = page;

    htmlPlugins.push(
      new HtmlWebpackPlugin({
        template: `./src/pages/${pageName}/index.html`,
        filename: `${pageName}.html`,
        chunks: ["common", "vendor", pageName],
        minify: process.env.NODE_ENV === "production",
      })
    );
  });

  return { entries, htmlPlugins };
}

const { entries, htmlPlugins } = generateEntries();

module.exports = {
  entry: entries,

  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name]/bundle.[contenthash:8].js",
    clean: true,
  },

  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
      "@/shared": path.resolve(__dirname, "src/shared"),
    },
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
    ],
  },

  plugins: [
    ...htmlPlugins,

    new MiniCssExtractPlugin({
      filename: "[name]/styles.[contenthash:8].css",
    }),
  ],

  optimization: {
    splitChunks: {
      cacheGroups: {
        common: {
          name: "common",
          chunks: "all",
          minChunks: 2,
          priority: 5,
        },
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor",
          chunks: "all",
          priority: 10,
        },
      },
    },
  },
};

环境配置管理

javascript
// build/webpack.base.js
const path = require("path");

module.exports = {
  entry: "./src/index.js",

  resolve: {
    alias: {
      "@": path.resolve(__dirname, "../src"),
    },
    extensions: [".js", ".jsx", ".ts", ".tsx"],
  },

  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
    ],
  },
};

// build/webpack.dev.js
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.base");

module.exports = merge(baseConfig, {
  mode: "development",

  devtool: "eval-cheap-module-source-map",

  devServer: {
    port: 3000,
    hot: true,
    historyApiFallback: true,
  },

  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
});

// build/webpack.prod.js
const { merge } = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const baseConfig = require("./webpack.base");

module.exports = merge(baseConfig, {
  mode: "production",

  output: {
    filename: "[name].[contenthash:8].js",
    clean: true,
  },

  plugins: [
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css",
    }),
  ],

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },

  optimization: {
    splitChunks: {
      chunks: "all",
    },
  },
});

企业级最佳实践:

  • 模块化配置:按环境拆分配置文件,便于维护和扩展
  • 自动化入口:大型项目使用脚本自动生成入口和插件配置
  • 统一规范:团队内统一 Webpack 配置模板和最佳实践
  • 性能监控:集成包分析工具,定期优化构建产物
  • 环境隔离:不同环境使用不同的构建策略和优化方案

🚨 常见问题与解决方案

构建性能问题

问题:构建速度慢

javascript
// 解决方案:启用缓存和并行处理
module.exports = {
  cache: {
    type: "filesystem",
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          "thread-loader", // 并行处理
          "babel-loader",
        ],
      },
    ],
  },
};

问题:内存溢出

bash
# 解决方案:增加 Node.js 内存限制
node --max-old-space-size=4096 ./node_modules/webpack/bin/webpack.js

# 或在 package.json 中配置
{
  "scripts": {
    "build": "node --max-old-space-size=4096 webpack"
  }
}

开发环境问题

问题:热更新不生效

javascript
// 解决方案:检查 HMR 配置
module.exports = {
  devServer: {
    hot: true,
    watchFiles: ["src/**/*"], // 监听文件变化
  },

  plugins: [new webpack.HotModuleReplacementPlugin()],
};

问题:代理不工作

javascript
// 解决方案:详细的代理配置
module.exports = {
  devServer: {
    proxy: {
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true,
        logLevel: "debug", // 开启调试日志
        onError: (err) => {
          console.log("Proxy Error:", err);
        },
      },
    },
  },
};

生产环境问题

问题:打包后文件过大

javascript
// 解决方案:代码分割和外部化
module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all",
      maxSize: 244000, // 限制包大小
    },
  },

  externals: {
    lodash: "_",
    moment: "moment",
  },
};

问题:CSS 样式丢失

javascript
// 解决方案:正确配置 CSS 提取
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          process.env.NODE_ENV === "production"
            ? MiniCssExtractPlugin.loader
            : "style-loader",
          "css-loader",
        ],
      },
    ],
  },
};

12. 配置验证与快速参考

12.1 配置验证清单

javascript
// webpack.config.js - 配置验证示例
const path = require("path");
const { validate } = require("schema-utils");

// 配置验证 schema
const configSchema = {
  type: "object",
  properties: {
    entry: { type: ["string", "object", "array"] },
    output: {
      type: "object",
      properties: {
        path: { type: "string" },
        filename: { type: "string" },
        publicPath: { type: "string" },
      },
      required: ["path", "filename"],
    },
    mode: {
      type: "string",
      enum: ["development", "production", "none"],
    },
  },
  required: ["entry", "output", "mode"],
};

module.exports = (env, argv) => {
  const config = {
    // ... 你的配置
  };

  // 验证配置
  validate(configSchema, config, {
    name: "Webpack Config",
    baseDataPath: "configuration",
  });

  return config;
};

12.2 性能检查清单

javascript
// 性能监控配置
module.exports = {
  // 性能预算
  performance: {
    maxAssetSize: 250000, // 单个资源最大 250KB
    maxEntrypointSize: 400000, // 入口点最大 400KB
    hints: "warning", // 超出时显示警告
    assetFilter: (assetFilename) => {
      // 只检查 JS 和 CSS 文件
      return /\.(js|css)$/.test(assetFilename);
    },
  },

  // 统计信息配置
  stats: {
    assets: true,
    chunks: false,
    modules: false,
    colors: true,
    timings: true,
    performance: true,
    warnings: true,
    errors: true,
  },
};

12.3 快速配置模板

基础项目快速启动

bash
# 1. 初始化项目
npm init -y
npm install webpack webpack-cli --save-dev

# 2. 创建基础配置
touch webpack.config.js

# 3. 安装常用 loader 和 plugin
npm install --save-dev \
  babel-loader @babel/core @babel/preset-env \
  css-loader style-loader \
  html-webpack-plugin \
  mini-css-extract-plugin

React 项目快速配置

javascript
// webpack.react.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.jsx",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js",
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"],
          },
        },
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),
  ],
  resolve: {
    extensions: [".js", ".jsx"],
  },
  devServer: {
    port: 3000,
    hot: true,
  },
};

12.4 常用命令速查

bash
# 开发环境构建
npx webpack --mode development

# 生产环境构建
npx webpack --mode production

# 启动开发服务器
npx webpack serve --mode development

# 分析构建结果
npx webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json

# 监听文件变化
npx webpack --watch

# 清理输出目录
npx webpack --clean

# 显示详细构建信息
npx webpack --stats verbose

12.5 配置文件组织最佳实践

project/
├── webpack/
│   ├── webpack.common.js      # 通用配置
│   ├── webpack.dev.js         # 开发环境
│   ├── webpack.prod.js        # 生产环境
│   ├── webpack.analyze.js     # 分析配置
│   └── utils/
│       ├── paths.js           # 路径配置
│       └── plugins.js         # 插件配置
├── src/
├── public/
└── package.json
javascript
// webpack/utils/paths.js
const path = require("path");

const rootPath = path.resolve(__dirname, "../..");

module.exports = {
  root: rootPath,
  src: path.resolve(rootPath, "src"),
  build: path.resolve(rootPath, "dist"),
  public: path.resolve(rootPath, "public"),
  nodeModules: path.resolve(rootPath, "node_modules"),
};

12.6 团队协作配置规范

javascript
// .webpack.config.js - 团队标准配置
module.exports = {
  // 强制配置项
  mode: process.env.NODE_ENV || "development",

  // 统一的文件命名规范
  output: {
    filename:
      process.env.NODE_ENV === "production"
        ? "[name].[contenthash:8].js"
        : "[name].js",
    chunkFilename:
      process.env.NODE_ENV === "production"
        ? "[name].[contenthash:8].chunk.js"
        : "[name].chunk.js",
  },

  // 统一的优化配置
  optimization: {
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          priority: 10,
        },
      },
    },
  },

  // 统一的开发工具配置
  devtool:
    process.env.NODE_ENV === "production"
      ? "source-map"
      : "eval-cheap-module-source-map",
};

总结

Webpack 企业级配置指南 提供了:

  • 📋 完整的配置模块:从入口输出到高级优化的全方位覆盖
  • 🔧 实用的代码示例:每个配置都包含可直接使用的代码片段
  • 🚀 性能优化策略:针对企业级项目的构建速度和包体积优化
  • 🛠️ 故障排除指南:常见问题的解决方案和最佳实践
  • 📦 模板化配置:适用于不同项目类型的标准化模板
  • ✅ 验证和规范:确保配置质量和团队协作的标准