本文目录
现代前端工具链里,最核心的两个角色通常就是包管理器和构建工具。
- 包管理器负责把“项目需要哪些依赖”变成一套可安装、可复现、可解析的文件结构
- 构建工具负责把“面向开发者的源码”变成浏览器或运行时能直接消费的产物
它们经常一起出现,但解决的并不是同一个问题。
为什么这两个要放在一起看
一个现代前端项目,至少要先回答两类问题:
- 依赖怎么安装、锁定、复现
- 源码怎么转译、组织、提供给浏览器执行
第一类问题对应包管理器,第二类问题对应构建工具。
如果把现代前端项目看成一条工作流,那么可以粗略理解为:
声明依赖
-> 安装依赖
-> 解析模块
-> 转译源码
-> 开发时按需提供
-> 生产时打包优化
其中前两步主要是包管理器,后几步主要是构建工具。
包管理器在做什么
依赖管理的本质是:把“我需要哪些包”变成一套稳定的本地依赖结构,让运行时和工具都能找到它们。
一个项目里和依赖管理最相关的几个东西通常是:
package.json = 依赖需求
包管理器 = 解析算法和安装执行者
lock file = 精确解析结果快照
node_modules = 安装后的文件结构
package.json
package.json 描述的是项目的依赖需求和元信息,比如:
- 这个项目叫什么
- 需要哪些第三方包
- 可以执行哪些 scripts
- 某些工具应该如何理解这个项目
它声明的是“范围”或“意图”,不是最终安装结果。
lockfile
锁文件记录的是最终精确解析结果。
例如:
{
"dependencies": {
"react": "^19.0.0"
}
}
这里的 ^19.0.0 并不是固定版本,它允许安装多个兼容版本;但锁文件会把最终结果固定下来,这样团队成员在不同时间安装依赖时,不会得到不同的依赖树。
node_modules
node_modules 是安装后的事实源。
- 对包管理器来说,它是依赖落地后的目录结构
- 对运行时和构建工具来说,它是解析第三方包的重要输入
也就是说,包管理器把依赖“放进来”,而后续工具会继续“消费它”。
pnpm install 在做什么
以 pnpm 为例,安装流程可以粗略理解为:
读取 package.json
-> 读取 pnpm-lock.yaml(如果存在)
-> 检查 lock 是否满足 package.json
-> 如果满足,按 lock 安装
-> 如果不满足,重新解析
-> 写回新的 pnpm-lock.yaml
-> 生成 node_modules 的链接结构
这也是为什么 pnpm 解决的核心问题不是“下载文件”这么简单,而是依赖解析、版本锁定和目录拓扑构建。
构建工具在做什么
构建工具负责的是另一件事:把开发者友好的源码,变成浏览器或运行时能直接执行的结果。
现代前端源码通常包含这些内容:
- TypeScript、JSX、TSX
- CSS 预处理、静态资源导入
- 裸模块导入,例如
import React from "react" - 别名路径、动态导入、环境变量注入
这些内容浏览器原生并不能完整理解,因此需要构建工具来处理。
模块化能力增强
原生 ESM 只理解比较朴素的模块 URL,比如 ./foo.js 或 /bar.js。
但现代前端项目常见的是:
import React from "react";
import Button from "@/components/Button";
import "./style.css";
import logoUrl from "./logo.png";
这里已经超出了浏览器原生模块解析的能力边界。构建工具需要帮你完成:
- 裸模块导入解析
- 路径别名解析
- 资源文件导入转换
- TypeScript / JSX 转译
- 动态导入拆分
这部分可以配合 模块化与打包增强 一起看。
模块图分析
包管理器处理的是 package graph,构建工具处理的是 module graph。
两者的区别在于:
| 对象 | 包管理器 | 构建工具 |
|---|---|---|
| 分析单位 | 包 | 模块/文件 |
| 依赖信息来源 | package.json 这类显式元数据 | 源码里的 import / export / import() |
| 主要目标 | 安装、锁定、复现依赖 | 转译、组织、拆分、优化模块 |
package graph 的依赖信息是显式的,而 module graph 的依赖信息藏在代码语法里,所以构建工具通常要解析 AST,才能准确知道谁依赖谁。
开发阶段在做什么
以 Vite 为代表的现代构建工具,在开发阶段的核心思路不是“整项目先打成一个 bundle”,而是:
浏览器请求入口模块
-> 构建工具解析 import
-> 按需转译当前模块
-> 返回浏览器可执行的 ESM
同时,它还会:
- 预构建第三方依赖
- 维护模块图
- 监听文件变更
- 基于模块边界做 HMR
这部分可以继续看 [Vite 的构建流程](./Vite/Vite 的构建流程.md) 和 [HMR 如何工作](./Vite/HMR 如何工作.md)。
生产构建在做什么
到生产阶段,构建工具会完整分析依赖图,并做真正的打包优化:
解析 import
-> 构建完整依赖图
-> 转译与资源处理
-> tree-shaking
-> code splitting
-> 压缩与产物输出
也就是说,开发阶段更偏向“按需提供模块”,而生产阶段更偏向“重组和优化整个模块图”。
包管理器和构建工具怎么衔接
两者的关系可以理解成:
- 包管理器先把第三方依赖解析并落到本地
- 构建工具再基于这些依赖和你的源码,建立模块图并完成转译/打包
最直观的例子就是第三方包导入:
import React from "react";
包管理器负责把 react 安装进本地依赖结构里,构建工具负责把 "react" 这条导入解析到具体文件,并转换成浏览器可消费的形式。
所以它们不是替代关系,而是上下游关系:
- 包管理器提供依赖基础设施
- 构建工具消费依赖基础设施并组织运行产物
放在一起理解时,一个很重要的认识是:
pnpm解决“依赖怎么落地”,Vite解决“模块怎么被解析、转译和交付”。
因此,一个现代前端项目的主工作流通常可以概括为:
package.json / lockfile
-> pnpm 安装依赖
-> 生成 node_modules 结构
-> Vite 基于源码和 node_modules 建立模块图
-> 开发时按需提供模块
-> 生产时打包并输出优化产物
- 包管理器解决“我要什么依赖,以及怎样稳定拿到它们”
- 构建工具解决“这些源码和依赖,最终怎样变成可运行的应用”
前者关注依赖树,后者关注模块图。前者是基础设施,后者是交付流水线。