Webpack 5 打包流程详解与实战


一、Webpack 简介

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器。它将各种资源(JavaScript、CSS、图片、字体等)视为模块,然后将它们打包成一个或多个 bundle 文件。

1.1 核心优势

  • 模块化支持:支持 CommonJS、AMD、ES6 Modules 等多种模块化规范
  • Loader 机制:通过 loader 处理非 JS 文件(CSS、图片、TypeScript 等)
  • 插件系统:丰富的插件生态,支持代码压缩、分割、热更新等
  • 代码分割:支持按需加载,优化首屏加载速度
  • Tree Shaking:自动移除未使用的代码

二、核心概念

2.1 入口 (Entry)

Webpack 开始构建的起点,指示从哪个模块开始构建依赖图。

// 单入口
module.exports = {
    entry: "./src/index.js",
};

// 多入口
module.exports = {
    entry: {
        main: "./src/index.js",
        admin: "./src/admin.js",
    },
};

2.2 输出 (Output)

告诉 Webpack 在哪里输出 bundle 文件以及如何命名。

const path = require("path");

module.exports = {
    output: {
        path: path.resolve(__dirname, "dist"), // 输出目录
        filename: "[name].[contenthash:8].js", // 文件名模板
        chunkFilename: "[name].[contenthash:8].chunk.js", // 代码分割文件名
        clean: true, // 自动清理输出目录
        publicPath: "/", // 公共资源路径
    },
};

文件名占位符说明:

占位符 含义
[name] 入口名称
[hash] 每次构建唯一的 hash
[chunkhash] 每个 chunk 的 hash
[contenthash] 文件内容的 hash(推荐用于缓存)
[id] 模块 id

2.3 加载器 (Loader)

Loader 让 Webpack 能够处理非 JavaScript 文件,将它们转换为有效模块。

基本配置示例:

module.exports = {
    module: {
        rules: [
            // JavaScript/JSX 处理
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: "babel-loader",
            },
            // CSS 处理
            {
                test: /\.css$/,
                use: ["style-loader", "css-loader", "postcss-loader"],
            },
            // Less 处理
            {
                test: /\.less$/,
                use: ["style-loader", "css-loader", "less-loader"],
            },
            // 图片资源(Webpack 5 内置 Asset Modules)
            {
                test: /\.(png|jpg|gif|svg)$/,
                type: "asset",
            },
            // 字体文件
            {
                test: /\.(woff2?|eot|ttf|otf)$/,
                type: "asset/resource",
            },
        ],
    },
};

常用 Loader 列表:

Loader 用途
babel-loader 转译 ES6+/JSX 代码
ts-loader 编译 TypeScript
css-loader 解析 CSS 文件
style-loader 将 CSS 注入 DOM
less-loader/sass-loader 编译 Less/Sass
postcss-loader PostCSS 处理( autoprefixer 等)
vue-loader 编译 Vue 单文件组件
eslint-loader ESLint 代码检查

2.4 插件 (Plugins)

插件用于执行范围更广的任务,如打包优化、资源管理、环境变量注入等。

基本配置示例:

const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
    plugins: [
        // 自动生成 HTML 文件
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            filename: "index.html",
        }),
        // 提取 CSS 到单独文件
        new MiniCssExtractPlugin({
            filename: "css/[name].[contenthash:8].css",
        }),
        // 清理输出目录
        new CleanWebpackPlugin(),
    ],
};

常用插件列表:

插件 用途
HtmlWebpackPlugin 自动生成 HTML 文件
MiniCssExtractPlugin 提取 CSS 到单独文件
CleanWebpackPlugin 清理输出目录
DefinePlugin 定义全局常量
CopyWebpackPlugin 复制静态文件
TerserPlugin JS 代码压缩
CssMinimizerPlugin CSS 代码压缩
BundleAnalyzerPlugin 分析包体积

2.5 模式 (Mode)

Mode 选项告诉 Webpack 使用相应的内置优化。

module.exports = {
    mode: "production", // 'development' | 'production' | 'none'
};
模式 特点
development 启用开发工具,不压缩代码,更快的构建速度
production 启用代码压缩、Tree Shaking、Scope Hoisting 等优化
none 不启用任何默认优化

2.6 Resolve 配置

配置模块解析规则。

module.exports = {
    resolve: {
        // 自动解析的扩展名
        extensions: [".js", ".jsx", ".ts", ".tsx", ".json"],
        // 路径别名
        alias: {
            "@": path.resolve(__dirname, "src"),
            "@components": path.resolve(__dirname, "src/components"),
            "@utils": path.resolve(__dirname, "src/utils"),
        },
        // 模块搜索目录
        modules: [path.resolve(__dirname, "node_modules")],
    },
};

三、Webpack 打包流程详解

3.1 整体流程

Webpack 的打包流程分为以下几个阶段:

初始化 → 编译 → 构建模块 → 完成编译 → 输出资源

3.2 详细步骤

3.2.1 初始化阶段

命令行输入 webpack → 读取 webpack.config.js → 合并配置参数 → 创建 Compiler 对象
  • 读取配置文件和命令行参数
  • 创建 Compiler 实例(核心编译器)
  • 加载配置的插件

3.2.2 编译阶段

执行 compiler.run() → 创建 Compilation 对象 → 触发 compile 事件
  • 创建 Compilation 对象(单次编译的上下文)
  • 触发 compile 事件,开始编译

3.2.3 构建模块阶段

从 Entry 开始 → 解析模块依赖 → 使用 Loader 转换 → 递归构建依赖树

具体步骤:

  1. 解析入口:从 entry 配置的入口文件开始
  2. 解析依赖:使用 acorn 解析代码,生成 AST(抽象语法树)
  3. 识别依赖:遍历 AST,识别 import/require 等依赖语句
  4. Loader 转换:根据配置使用对应的 loader 处理文件
  5. 递归构建:对每个依赖模块重复上述过程,构建完整的依赖图

3.2.4 优化阶段

触发 seal 事件 → 优化模块 → 生成 chunks → 优化 chunks
  • Tree Shaking:标记并移除未使用的代码
  • 代码分割:根据配置将代码分割成多个 chunks
  • 作用域提升:将模块合并到同一作用域,减少闭包

3.2.5 输出阶段

生成 assets → 写入文件系统 → 触发 done 事件
  • 将 chunks 转换成最终的 bundle 文件
  • 应用插件进行最后处理
  • 将文件写入 output 指定的目录

3.3 核心事件节点

Webpack 使用 Tapable 实现事件流,关键事件节点:

事件 说明
entry-option 初始化配置
run 开始编译
compile 开始编译(创建 Compilation)
make 从入口分析模块依赖
build-module 构建模块(Loader 处理)
normal-module-loader 生成 AST,收集依赖
seal 封装构建结果,优化 chunks
emit 输出到文件系统
done 完成编译

四、完整项目案例

📌 说明:本章节提供完整的、可直接使用的配置文件。前面章节中的配置示例均为简化版本,用于讲解概念和原理。实际项目中,请参考本章节的完整配置。

4.1 项目结构

webpack-demo/
├── src/
│   ├── assets/
│   │   ├── images/
│   │   └── fonts/
│   ├── components/
│   │   ├── Header.js
│   │   └── Footer.js
│   ├── styles/
│   │   ├── index.css
│   │   └── variables.less
│   ├── utils/
│   │   └── helper.js
│   ├── index.html
│   ├── index.js
│   └── about.js
├── dist/                  # 输出目录
├── webpack.config.js      # 主配置
├── webpack.dev.js         # 开发配置
├── webpack.prod.js        # 生产配置
├── babel.config.js        # Babel 配置
└── package.json

4.2 安装依赖

# 初始化项目
npm init -y

# Webpack 核心
npm install webpack webpack-cli webpack-dev-server webpack-merge --save-dev

# Babel
npm install @babel/core @babel/preset-env @babel/preset-react babel-loader --save-dev

# CSS 处理
npm install css-loader style-loader less less-loader postcss postcss-loader autoprefixer mini-css-extract-plugin css-minimizer-webpack-plugin --save-dev

# HTML 处理
npm install html-webpack-plugin --save-dev

# 代码压缩
npm install terser-webpack-plugin --save-dev

# 其他插件
npm install clean-webpack-plugin copy-webpack-plugin --save-dev

# React(可选)
npm install react react-dom

4.3 配置文件

4.3.1 babel.config.js

module.exports = {
    presets: [
        [
            "@babel/preset-env",
            {
                targets: {
                    browsers: ["> 1%", "last 2 versions"],
                },
                modules: false, // 保留 ES6 模块语法,利于 Tree Shaking
            },
        ],
        "@babel/preset-react",
    ],
};

4.3.2 postcss.config.js

module.exports = {
    plugins: [require("autoprefixer")],
};

4.3.3 webpack.common.js(公共配置)

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

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

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

    module: {
        rules: [
            // JavaScript/JSX 处理
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: ["@babel/preset-env", "@babel/preset-react"],
                        cacheDirectory: true, // 开启缓存
                    },
                },
            },

            // CSS
            {
                test: /\.css$/,
                use: ["style-loader", "css-loader", "postcss-loader"],
            },

            // Less
            {
                test: /\.less$/,
                use: ["style-loader", "css-loader", "postcss-loader", "less-loader"],
            },

            // 图片资源(Webpack 5 内置 Asset Modules)
            {
                test: /\.(png|jpg|jpeg|gif|svg)$/i,
                type: "asset",
                parser: {
                    dataUrlCondition: {
                        maxSize: 8 * 1024, // 8KB 以下转 base64
                    },
                },
                generator: {
                    filename: "images/[name].[hash:8][ext][query]",
                },
            },

            // 字体文件
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: "asset/resource",
                generator: {
                    filename: "fonts/[name].[hash:8][ext][query]",
                },
            },
        ],
    },

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

    plugins: [
        // 生成 index.html
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            filename: "index.html",
            chunks: ["main"],
            minify: {
                collapseWhitespace: true,
                removeComments: true,
            },
        }),

        // 生成 about.html
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            filename: "about.html",
            chunks: ["about"],
            minify: {
                collapseWhitespace: true,
                removeComments: true,
            },
        }),
        new CleanWebpackPlugin(),
    ],
};

4.3.4 webpack.dev.js(开发配置)

const { merge } = require("webpack-merge");
const common = require("./webpack.common");

module.exports = merge(common, {
    mode: "development",
    devtool: "eval-cheap-module-source-map",
    devServer: {
        hot: true,
        open: true,
        port: 3000,
        compress: true,
        historyApiFallback: true,
        static: {
            directory: "./dist",
        },
    },
    cache: {
        type: "filesystem",
    },
});

4.3.5 webpack.prod.js(生产配置)

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

module.exports = merge(common, {
    mode: "production",
    devtool: "source-map",

    module: {
        rules: [
            // 覆盖 CSS 处理,使用 MiniCssExtractPlugin
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
            },
            {
                test: /\.less$/,
                use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"],
            },
        ],
    },

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

    optimization: {
        minimize: true,
        usedExports: true,
        minimizer: [
            new TerserPlugin({
                parallel: true,
                terserOptions: {
                    compress: {
                        drop_console: true,
                        drop_debugger: true,
                    },
                },
            }),
            new CssMinimizerPlugin(),
        ],
        splitChunks: {
            chunks: "all",
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    name: "vendors",
                    priority: -10,
                    reuseExistingChunk: true,
                },
                common: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        },
        runtimeChunk: {
            name: "runtime",
        },
    },
});

4.4 源码文件

4.4.1 src/index.html

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Webpack Demo</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
</html>

4.4.2 src/index.js

import React from "react";
import ReactDOM from "react-dom/client";
import Header from "@components/Header";
import Footer from "@components/Footer";
import "./styles/index.css";

const App = () => {
    return (
        <div className="app">
            <Header title="首页" />
            <main>
                <h1>欢迎使用 Webpack 5</h1>
                <p>这是一个完整的 Webpack 配置示例</p>
                <button onClick={() => loadModal()}>加载弹窗模块</button>
            </main>
            <Footer />
        </div>
    );
};

// 动态加载模块
async function loadModal() {
    const { showModal } = await import(/* webpackChunkName: "modal" */ "./utils/helper");
    showModal("Hello from dynamic import!");
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

4.4.3 src/about.js

import React from "react";
import ReactDOM from "react-dom/client";
import Header from "@components/Header";
import Footer from "@components/Footer";
import "./styles/index.css";

const About = () => {
    return (
        <div className="app">
            <Header title="关于我们" />
            <main>
                <h1>关于页面</h1>
                <p>这是关于页面的内容</p>
            </main>
            <Footer />
        </div>
    );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<About />);

4.4.4 src/components/Header.js

import React from "react";

const Header = ({ title }) => {
    return (
        <header className="header">
            <nav>
                <a href="/">首页</a>
                <a href="/about.html">关于</a>
            </nav>
            <h1>{title}</h1>
        </header>
    );
};

export default Header;
import React from "react";

const Footer = () => {
    return (
        <footer className="footer">
            <p>&copy; 2024 Webpack Demo. All rights reserved.</p>
        </footer>
    );
};

export default Footer;

4.4.6 src/utils/helper.js

export const showModal = message => {
    alert(message);
};

export const formatDate = date => {
    return new Date(date).toLocaleDateString();
};

4.4.7 src/styles/index.css

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    line-height: 1.6;
    color: #333;
}

.app {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

.header {
    background: #333;
    color: white;
    padding: 1rem 2rem;
}

.header nav a {
    color: white;
    margin-right: 1rem;
    text-decoration: none;
}

.header h1 {
    margin-top: 0.5rem;
}

main {
    flex: 1;
    padding: 2rem;
    max-width: 1200px;
    margin: 0 auto;
    width: 100%;
}

button {
    padding: 0.5rem 1rem;
    background: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    margin-top: 1rem;
}

button:hover {
    background: #0056b3;
}

.footer {
    background: #f5f5f5;
    padding: 1rem 2rem;
    text-align: center;
}

4.5 package.json 脚本

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "scripts": {
        "dev": "webpack serve --config webpack.dev.js",
        "build": "webpack --config webpack.prod.js",
        "analyze": "webpack --config webpack.prod.js --analyze"
    },
    "devDependencies": {
        "@babel/core": "^7.22.0",
        "@babel/preset-env": "^7.22.0",
        "@babel/preset-react": "^7.22.0",
        "autoprefixer": "^10.4.0",
        "babel-loader": "^9.1.0",
        "clean-webpack-plugin": "^4.0.0",
        "css-loader": "^6.8.0",
        "css-minimizer-webpack-plugin": "^5.0.0",
        "html-webpack-plugin": "^5.5.0",
        "less": "^4.1.0",
        "less-loader": "^11.1.0",
        "mini-css-extract-plugin": "^2.7.0",
        "postcss": "^8.4.0",
        "postcss-loader": "^7.3.0",
        "style-loader": "^3.3.0",
        "terser-webpack-plugin": "^5.3.0",
        "webpack": "^5.88.0",
        "webpack-cli": "^5.1.0",
        "webpack-dev-server": "^4.15.0",
        "webpack-merge": "^5.9.0"
    },
    "dependencies": {
        "react": "^18.2.0",
        "react-dom": "^18.2.0"
    },
    "sideEffects": ["*.css", "*.less"]
}

4.6 运行项目

# 开发模式
npm run dev

# 生产构建
npm run build

# 分析包体积
npm run analyze

五、常见问题与解决方案

5.1 构建速度慢

问题 解决方案
首次构建慢 启用 cache: { type: 'filesystem' }
Loader 处理慢 使用 thread-loader 开启多进程
模块解析慢 配置 resolve.modulesresolve.alias
第三方库重复编译 使用 DllPlugin 预编译

5.2 Bundle 体积过大

问题 解决方案
未使用代码 启用 Tree Shaking,配置 sideEffects
重复代码 配置 splitChunks 提取公共代码
大依赖库 使用动态导入 import() 按需加载
未压缩 启用 mode: 'production'

5.3 缓存失效

问题 解决方案
文件名不变 使用 [contenthash] 替代 [hash]
vendor 缓存失效 分离 runtimeChunk,固定 vendor hash
模块 id 变化 配置 optimization.moduleIds: 'deterministic'

5.4 开发环境问题

问题 解决方案
HMR 不生效 检查 devServer.hottarget 配置
Source Map 不准确 使用 devtool: 'eval-cheap-module-source-map'
跨域问题 配置 devServer.proxy

六、Webpack 5 高级特性

6.1 模块联邦 (Module Federation)

模块联邦是 Webpack 5 最强大的特性之一,用于实现微前端架构:

// 主应用配置
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: "host",
            remotes: {
                app1: "app1@http://localhost:3001/remoteEntry.js",
                app2: "app2@http://localhost:3002/remoteEntry.js",
            },
            shared: {
                react: { singleton: true, requiredVersion: "^18.0.0" },
                "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
            },
        }),
    ],
};

// 子应用配置
module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: "app1",
            filename: "remoteEntry.js",
            exposes: {
                "./Button": "./src/components/Button",
                "./Header": "./src/components/Header",
            },
            shared: {
                react: { singleton: true, requiredVersion: "^18.0.0" },
                "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
            },
        }),
    ],
};

6.2 持久化缓存详解

Webpack 5 的持久化缓存机制:

module.exports = {
    cache: {
        type: "filesystem",
        buildDependencies: {
            config: [__filename],
        },
        name: "my-project-cache",
        version: "1.0",
        cacheDirectory: path.resolve(__dirname, ".webpack-cache"),
    },
};

6.3 资源模块 (Asset Modules) 深度配置

module.exports = {
    module: {
        rules: [
            // 图片资源
            {
                test: /\.(png|jpg|jpeg|gif|svg)$/i,
                type: "asset",
                parser: {
                    dataUrlCondition: {
                        maxSize: 8192, // 8kb
                    },
                },
                generator: {
                    filename: "images/[name].[hash:8][ext][query]",
                },
            },
            // 字体文件
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: "asset/resource",
                generator: {
                    filename: "fonts/[name].[hash:8][ext][query]",
                },
            },
            // 文本文件
            {
                test: /\.txt$/i,
                type: "asset/source",
            },
        ],
    },
};

七、性能优化

7.1 构建速度优化

7.1.1 缩小文件搜索范围

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: "babel-loader",
                include: path.resolve(__dirname, "src"), // 只在 src 目录查找
                exclude: /node_modules/, // 排除 node_modules
            },
        ],
        // 不解析无依赖的库
        noParse: /jquery|lodash/,
    },
    resolve: {
        // 指定模块搜索目录,避免层层查找
        modules: [path.resolve(__dirname, "node_modules")],
        // 减少后缀尝试
        extensions: [".js", ".jsx"],
        // 使用别名,避免库内解析
        alias: {
            react: path.resolve(__dirname, "./node_modules/react/dist/react.min.js"),
        },
    },
};

7.1.2 使用 DllPlugin 预编译第三方库

基本配置:

// webpack.dll.js
const path = require("path");
const webpack = require("webpack");

module.exports = {
    entry: {
        vendor: ["react", "react-dom", "lodash"], // 第三方库
    },
    output: {
        path: path.resolve(__dirname, "dll"),
        filename: "[name].dll.js",
        library: "[name]_library", // 暴露为全局变量
    },
    plugins: [
        new webpack.DllPlugin({
            name: "[name]_library",
            path: path.join(__dirname, "dll", "[name]-manifest.json"),
        }),
    ],
};

在主配置中引用:

// webpack.config.js
module.exports = {
    plugins: [
        new webpack.DllReferencePlugin({
            manifest: require("./dll/vendor-manifest.json"),
        }),
    ],
};

7.1.3 使用多进程构建

// 使用 thread-loader
module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: "thread-loader",
                        options: {
                            workers: 4, // 开启 4 个线程
                        },
                    },
                    "babel-loader",
                ],
            },
        ],
    },
};

7.1.4 使用缓存

module.exports = {
    // Webpack 5 持久化缓存,避免重复构建
    cache: {
        type: "filesystem",
        buildDependencies: {
            config: [__filename],
        },
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: "babel-loader?cacheDirectory=true", // Babel 缓存
            },
        ],
    },
};

7.2 输出质量优化

7.2.1 代码分割 (Code Splitting)

基本配置:

module.exports = {
    optimization: {
        splitChunks: {
            chunks: "all", // 对所有模块生效
            cacheGroups: {
                // 提取第三方库
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    name: "vendors",
                    priority: -10,
                    reuseExistingChunk: true,
                },
                // 提取公共代码
                common: {
                    minChunks: 2, // 至少被引用 2 次
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        },
        // 提取 webpack 运行时代码
        runtimeChunk: {
            name: "runtime",
        },
    },
};

动态导入(懒加载):

// 方式 1:动态 import
button.addEventListener("click", () => {
    import(/* webpackChunkName: "modal" */ "./modal").then(module => {
        module.showModal();
    });
});

// 方式 2:React 懒加载
const LazyComponent = React.lazy(() => import("./LazyComponent"));

7.2.2 Tree Shaking(摇树优化)

基本配置:

// webpack.config.js
module.exports = {
    mode: "production",
    optimization: {
        usedExports: true, // 标记未使用的导出
        sideEffects: false, // 检查 package.json 的 sideEffects
    },
};
// package.json
{
    "sideEffects": ["*.css", "*.less"]
}

7.2.3 代码压缩

基本配置:

const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
    optimization: {
        minimize: true,
        minimizer: [
            // JS 压缩
            new TerserPlugin({
                terserOptions: {
                    compress: {
                        drop_console: true, // 移除 console
                        drop_debugger: true, // 移除 debugger
                    },
                },
            }),
            // CSS 压缩
            new CssMinimizerPlugin(),
        ],
    },
};

7.2.4 浏览器缓存优化

module.exports = {
    output: {
        // 使用 contenthash,内容变化才变化
        filename: "js/[name].[contenthash:8].js",
        chunkFilename: "js/[name].[contenthash:8].chunk.js",
    },
};

7.3 开发体验优化

7.3.1 热模块替换 (HMR)

module.exports = {
    devServer: {
        hot: true, // 开启 HMR
        open: true, // 自动打开浏览器
        port: 3000, // 端口
        compress: true, // 启用 gzip
    },
    devtool: "eval-cheap-module-source-map", // 快速 source map
};

7.3.2 Source Map 配置

devtool 构建速度 重新构建速度 生产环境 质量
eval 生成后的代码
eval-cheap-source-map 转换后的代码
eval-cheap-module-source-map 原始源代码(推荐开发)
source-map 原始源代码
hidden-source-map 原始源代码(不暴露)

八、高级特性

8.1 模块联邦 (Module Federation)

模块联邦允许一个 Webpack 构建的 bundle 共享模块给另一个构建。

// webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

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

8.2 持久化缓存详解

module.exports = {
    cache: {
        type: "filesystem",
        buildDependencies: {
            config: [__filename],
        },
        name: "my-project-cache",
        version: "1.0",
        cacheDirectory: path.resolve(__dirname, ".webpack-cache"),
    },
};

8.3 资源模块 (Asset Modules) 深度配置

module.exports = {
    module: {
        rules: [
            // 图片资源
            {
                test: /\.(png|jpg|jpeg|gif|svg)$/i,
                type: "asset",
                parser: {
                    dataUrlCondition: {
                        maxSize: 8192,
                    },
                },
                generator: {
                    filename: "images/[name].[hash:8][ext][query]",
                },
            },
            // 字体文件
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: "asset/resource",
                generator: {
                    filename: "fonts/[name].[hash:8][ext][query]",
                },
            },
            // 文本文件
            {
                test: /\.txt$/i,
                type: "asset/source",
            },
        ],
    },
};

九、性能分析与监控

9.1 使用 webpack-bundle-analyzer

npm install --save-dev webpack-bundle-analyzer
// webpack.prod.js
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

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

9.2 构建速度分析

npm install --save-dev speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
    // webpack 配置
});

十、实际项目配置模式

10.1 多环境配置

// webpack.base.js - 基础配置
// webpack.dev.js - 开发环境
// webpack.prod.js - 生产环境
// webpack.stage.js - 预发布环境

10.2 条件配置

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

module.exports = {
    mode: isDev ? "development" : "production",
    devtool: isDev ? "eval-cheap-module-source-map" : "source-map",
};

十一、调试技巧

11.1 开发环境调试

  1. Source Map 配置:使用 eval-cheap-module-source-map 获得最快的构建速度和较好的源码映射
  2. 网络请求调试:使用 Chrome DevTools 的 Network 面板查看资源加载
  3. 模块依赖分析:使用 webpack --profile --json > stats.json 生成依赖分析文件

11.2 生产环境调试

  1. Source Map 配置:使用 hidden-source-map 生成 source map 但不暴露
  2. 错误监控:集成 Sentry 等错误监控工具
  3. 性能监控:使用 Lighthouse 分析页面性能

十二、最佳实践总结

12.1 配置最佳实践

  1. 分离配置文件:按环境分离配置,使用 webpack-merge 合并
  2. 合理使用缓存:启用持久化缓存和 Loader 缓存
  3. 优化构建速度:使用 DllPlugin、多进程、缩小搜索范围
  4. 优化输出质量:代码分割、Tree Shaking、压缩
  5. 合理的文件名策略:使用 [contenthash] 确保缓存有效性

12.2 开发工作流

  1. 本地开发:使用 webpack-dev-server 配合 HMR
  2. 代码质量:集成 ESLint、Prettier
  3. CI/CD:在持续集成中使用 webpack 构建
  4. 部署策略:静态资源 CDN 加速,HTML 服务器托管

12.3 常见坑点避免

  1. 缓存失效:确保使用 contenthashruntimeChunk
  2. Tree Shaking 失效:正确配置 sideEffects 和 ES6 模块
  3. 构建速度慢:避免不必要的 Loader 和 Plugin
  4. Bundle 体积过大:合理使用代码分割和动态导入

十三、与其他构建工具的对比

工具 适用场景 优势 劣势
Webpack 复杂应用、需要代码分割 功能全面、生态丰富 配置复杂
Vite 快速开发、现代项目 开发速度快、配置简单 生产构建依赖 Rollup
Rollup 库开发、组件库 输出体积小、Tree Shaking 强 代码分割支持有限
Parcel 快速原型、小型项目 零配置、开箱即用 灵活性差
Gulp 构建流程、任务自动化 灵活、可定制性强 不处理模块依赖

十四、未来发展趋势

  1. ES 模块原生支持:浏览器对 ES 模块的支持越来越好
  2. 构建工具集成:如 Vite 等新一代构建工具的兴起
  3. WebAssembly 支持:Webpack 对 WebAssembly 的更好支持
  4. 智能优化:更智能的代码分割和优化策略
  5. 微前端生态:模块联邦的广泛应用

十五、总结

Webpack 5 是一个功能强大的模块打包器,本文从以下几个方面进行了详细讲解:

  1. 核心概念:Entry、Output、Loader、Plugin、Mode 的基本用法
  2. 打包流程:初始化 → 编译 → 构建模块 → 优化 → 输出的完整流程
  3. 性能优化:构建速度优化、输出质量优化、开发体验优化
  4. 实战案例:提供了可直接运行的完整项目配置

通过合理配置 Webpack,可以实现:

  • 快速的开发体验(HMR、缓存)
  • 优化的生产构建(代码分割、压缩、Tree Shaking)
  • 高效的浏览器缓存(contenthash)

希望本文能帮助你更好地理解和使用 Webpack 5!

参考资料


文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录