2. Webpack 核心概念与打包流程深度剖析

Webpack 这东西,说白了就是一个模块打包器。你写了一大堆 JS、CSS、图片,它帮你整理成一个或几个文件。但它的核心概念,很多人用了两三年都没完全搞明白。今天咱们就把 Entry、Output、Loader、Plugin、Mode、Module 这六个概念彻底说透。

一句话总结:Webpack 就是「从入口开始,按依赖关系构建模块依赖图,经过 Loader 转换、Plugin 增强,最终输出打包产物」的工具。

2.1 六大核心概念速览

先给个表格,让你一眼看清每个概念是干嘛的。我习惯把这六个概念分成两组:「流程控制」和「转换增强」。

概念 作用 类比
Entry(入口) 告诉 Webpack 从哪个文件开始打包 像一栋楼的入口大门
Output(输出) 指定打包后的文件放哪、叫什么 像快递的收货地址
Loader(加载器) 处理非 JS 文件(CSS、图片、TS 等) 像翻译官,把不同语言转成 JS
Plugin(插件) 在打包过程中做额外的事情 像装修队,能改造整个房子
Mode(模式) 设置 development / production 等环境 像工作模式:调试 or 上线
Module(模块) Webpack 中一切文件都是模块 像乐高积木,每块都能组合

2.2 Entry:打包的起点

Entry 就是 Webpack 开始工作的第一个文件。你想想看,如果不知道从哪开始,怎么构建依赖图?

// 单入口(最常见)
module.exports = {
  entry: './src/index.js'
};

// 多入口(多页面应用)
module.exports = {
  entry: {
    home: './src/home.js',
    about: './src/about.js',
    contact: './src/contact.js'
  }
};
我的习惯:单页面应用用单入口就够了。多页面应用才用多入口,每个页面一个入口文件。别为了「灵活」搞一堆入口,维护起来很痛苦。

2.3 Output:打包结果放哪

Output 决定了打包后的文件长什么样。我刚开始用 Webpack 时,经常忘记配 output,结果文件全跑 dist 目录里乱成一团。

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    // 多入口时用占位符
    filename: '[name].[contenthash].js'
  }
};

[contenthash] 这个占位符特别有用。文件内容变了,hash 才变。浏览器缓存就能精准失效。嗯,这里要注意:开发环境别用 contenthash,不然每次热更新都生成新文件,慢得要命。

2.4 Loader:文件转换器

Webpack 原生只认识 JS 和 JSON。遇到 CSS、图片、TypeScript 怎么办?Loader 就是干这个的。

Loader 的执行顺序是从右到左,从下到上。这个坑我踩过好几次。

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
        // 先执行 css-loader(解析 CSS),再执行 style-loader(注入到页面)
      },
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  }
};
我曾经踩过的坑:有一次我把 css-loader 和 style-loader 顺序写反了,结果报错说「你让我注入一个还没解析的 CSS 字符串」。记住:执行顺序是倒着写的,最后一个先执行。

2.5 Plugin:打包过程的增强器

Loader 只能处理单个文件,Plugin 能干预整个打包过程。比如生成 HTML、压缩代码、提取 CSS 到单独文件。

const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    })
  ]
};

Plugin 的机制是「钩子」。Webpack 在打包的不同阶段会触发不同钩子,Plugin 就挂在这些钩子上干活。比如 HtmlWebpackPlugin 在 emit 阶段把生成的 JS/CSS 注入到 HTML 里。

2.6 Mode:一键切换环境

Mode 这个配置,说白了就是帮你省事。设成 development,Webpack 自动开启 source map、更快的增量编译。设成 production,自动开启 tree shaking、代码压缩。

module.exports = {
  mode: 'development', // 开发模式
  // 或
  mode: 'production'   // 生产模式
};
我的建议:别手动配那些压缩插件了。设成 production 模式,Webpack 内置的 TerserPlugin 会自动帮你压缩 JS。省心又高效。

2.7 Module:一切皆模块

在 Webpack 的世界里,每个文件都是一个模块。JS 文件是模块,CSS 文件是模块,甚至图片也是模块。模块之间通过 import / require 建立依赖关系。

Webpack 会从 Entry 开始,递归地解析所有模块的依赖,最终生成一个模块依赖图(Module Graph)。这个图就是打包的核心依据。

2.8 Webpack 打包流程深度剖析

光知道概念还不够,得理解 Webpack 到底是怎么工作的。我把打包流程拆成四个阶段:

  1. 初始化阶段:读取配置文件,合并命令行参数,创建 Compiler 对象
  2. 编译阶段:从 Entry 开始,递归解析模块依赖,用 Loader 转换文件内容
  3. 生成阶段:把所有模块打包成 Chunk,生成最终代码
  4. 输出阶段:把打包结果写入磁盘

下面这张图,把整个流程串起来了。

Webpack 打包流程全景图 1. 初始化 读取配置 + 创建 Compiler 2. 编译 从 Entry 递归解析依赖 3. 生成 模块 → Chunk → 代码 4. 输出 写入磁盘(dist 目录) 编译阶段内部细节 Entry 入口文件 Loader 转换 构建依赖图 生成 Chunk 递归处理所有依赖模块 关键点: • Plugin 在编译阶段通过钩子介入,比如在生成 Chunk 前做代码分割 • Loader 只处理单个文件转换,Plugin 可以影响整个打包流程

2.9 核心流程的代码视角

咱们用代码来模拟一下 Webpack 的打包逻辑。虽然简化了,但核心思想一模一样。

// 伪代码:Webpack 打包核心逻辑
class Compiler {
  run() {
    // 1. 读取入口文件
    const entryModule = this.buildModule(this.config.entry);

    // 2. 递归构建所有模块
    const modules = [entryModule];
    while (modules.length) {
      const module = modules.shift();
      // 用 Loader 转换文件内容
      const transformedCode = this.applyLoaders(module);
      // 解析依赖(import / require)
      const dependencies = this.parseDependencies(transformedCode);
      dependencies.forEach(dep => {
        modules.push(this.buildModule(dep));
      });
    }

    // 3. 生成 Chunk
    const chunk = this.createChunk(modules);

    // 4. 输出文件
    this.emitFiles(chunk);
  }
}
理解这个流程有什么用?当你遇到打包慢、产物大、依赖报错时,脑子里有这个流程图,你就能快速定位问题出在哪个阶段。比如「Loader 转换太慢」还是「依赖解析卡住了」。

2.10 避坑指南:我踩过的三个坑

  • Loader 顺序搞反:我曾经把 sass-loader 和 css-loader 写反,结果报错说「不认识 $ 变量」。记住:从右到左执行,最后一个 Loader 最先处理。
  • 多入口忘记配 output:单入口时 filename: 'bundle.js' 没问题。多入口必须用 [name] 占位符,不然文件互相覆盖。
  • Plugin 重复实例化:同一个 Plugin 在 plugins 数组里出现两次,会导致重复执行。比如 HtmlWebpackPlugin 实例化两次,会生成两个 HTML 文件。

2.11 本章小结

六个核心概念,其实就三件事:入口输出定边界,Loader 转换文件,Plugin 增强流程。Mode 帮你一键切换环境,Module 是 Webpack 看待文件的方式。

打包流程四个阶段:初始化 → 编译 → 生成 → 输出。其中编译阶段最复杂,涉及 Loader 转换、依赖解析、递归构建。理解了这个流程,你就能驾驭 Webpack 了。