认识 Webpack:为什么前端需要打包器

本节目标

现代前端不再是「几个 <script> 标签」能搞定的。一个真实项目动辄几百个文件:你的业务代码、从 npm 装来的依赖(react、lodash…)、CSS、图片、字体。浏览器原生只认识 <script src="..."> 和 <link>,它并不知道什么是「import 一个 npm 包」。

Webpack 做的事,就是当** bundler(打包器):从你指定的入口出发,顺着 import/require 把整张依赖图**全部收进来,再把它们「翻译 + 拼装」成浏览器能直接跑的几个文件(bundle)。

# 运行环境:Node.js 16+;项目里先装 webpack 与 webpack-cli
# npm init -y
# npm i -D webpack webpack-cli

# 一个最简单的项目结构
# my-app/
# ├─ src/
# │  └─ index.js          // 入口:import 了 sayHello
# └─ package.json

# src/index.js
import { sayHello } from './utils.js'
sayHello('Webpack')

# src/utils.js
export function sayHello(name) {
  console.log('Hello, ' + name)
}

# 在 my-app 目录直接跑(不写配置也能打包,webpack 会按默认 entry=./src/index.js)
npx webpack
# 产物默认输出到 dist/main.js —— 这就是浏览器能直接 <script> 引用的 bundle

打包后,dist/main.js 里已经把 utils.js 的内容「内联」了进来,浏览器不再需要理解 import——它拿到的就是一段自包含的脚本。这就是打包器存在的根本理由:把「人类写的多文件模块世界」翻译成「浏览器能跑的单文件世界」。

名词解释

模块(Module):一个独立的代码单元,通常是 import/export 的一个文件。你的 utils.js 就是一个模块。

依赖图(Dependency Graph):从入口出发、顺着 import 找到的所有模块及其依赖关系,画出来像一张图。Webpack 就是按这张图来打包的。

打包(Bundle):打包的最终产物文件,里面包含了一个或多个模块拼接后的代码,浏览器可直接加载。

分块(Chunk):构建过程中 Webpack 内部的中间代码块,最终会被输出成一个或多个 bundle。代码分割时你会看到多个 chunk 对应多个 bundle。

课后练习

练习 1:浏览器原生支持 <script type="module"> 了,为什么还需要 Webpack?

答案:原生 ESM 有两个硬伤——① 它只能加载「浏览器能直接 fetch 的 URL」,无法直接 import 'react'(npm 包不在你的服务器上);② 模块一多,浏览器就要发几十上百个请求,加载慢。Webpack 把 npm 依赖、各种资源全打进 bundle,既解决了「import 包」的问题,也合并了请求、还能顺手做压缩/兼容转换。

练习 2:bundle 和 chunk 是一回事吗?

答案:不是。chunk 是 Webpack 构建过程中的「中间分块」,bundle 是最终写到磁盘的文件。一个 chunk 通常会对应一个 bundle,但「代码分割」时一个 chunk 可能拆出多个 bundle,二者概念层次不同。

总结

我的判断很直接:只要你在写「多文件 + 用 npm 依赖」的前端项目,就需要打包器。Webpack 的价值不是「发明模块」,而是把散落各处的模块和依赖,按依赖图收拢成浏览器能跑的自包含文件。入门第一课你只需记住三件事——入口(从哪开始)、依赖图(顺着 import 收集什么)、bundle(最终吐出什么)。下一节我们写第一个真正的 webpack.config.js,把 entry/output/mode 配明白。