package.json 中的 dependencies ,不会因为被列在依赖清单中,就全部被用户的浏览器下载。
浏览器最终下载什么,主要取决于两个方面:
- 构建阶段: build 过程中生成了哪些资源,包括打包了哪些代码、复制了哪些静态文件。
- 访问阶段: 页面如何引用这些资源,以及应用运行时何时加载它们。
这里说的"下载"是指用户浏览器下载网站资源 ,需要与开发时执行 npm install 下载依赖包区分开。
本文以未启用 SSR 的 Angular 应用及其生产构建为例。
1. dependencies 和 devDependencies 管什么?
它们描述依赖的用途,并影响安装和分发时如何处理这些包。
| 分类 | Angular 项目中的示例 | 用途 |
|---|---|---|
dependencies |
@angular/core、@angular/router |
提供应用功能 |
devDependencies |
@angular/cli、typescript、vitest |
创建、编译和测试应用 |
普通开发环境下执行:
npm install
通常会安装两类依赖。执行下面的命令,则不安装开发依赖:
ini
npm install --omit=dev
发布 npm 库时,分类也有作用:别人安装你的库,npm 会处理它声明的运行依赖,但不会连带安装这个库自己的开发依赖。
因此,依赖分类有实际意义,但它不是控制浏览器下载内容的开关。
2. 构建阶段:哪些资源进入产物?
Angular 构建工具会从 main.ts 等配置入口出发,沿着代码引用关系分析依赖:
css
main.ts
└── 根组件
└── 学习卡片组件
└── 工具库
如果一个日期库虽然写在 dependencies 中,却没有被应用直接或间接引用,也没有通过构建配置额外引入,它通常不会进入浏览器构建产物。
反过来,应用引用了某个放在 devDependencies 中的包,只要它已经安装且适用于浏览器构建,其中的代码仍可能被打包。
这并不意味着分类可以随意填写:分类影响包是否被安装,构建工具则根据入口、引用关系和配置处理已经可用的代码。
引用了一个包,也不一定保留整个包
假设一个工具模块提供两个函数:
javascript
export function double(value: number) {
return value * 2;
}
export function triple(value: number) {
return value * 3;
}
应用只使用:
sql
import { double } from './math';
console.log(double(5));
生产构建时,工具可以移除未被使用的 triple。这类优化叫 Tree Shaking(摇树优化) 。
不过,实际效果取决于代码写法和模块格式。如果代码有副作用,或者工具无法证明删除是安全的,就可能保留。引用一个函数,不保证只打包这一个函数。
保留下来的代码还会经过编译、压缩和拆分等处理。图片等静态资源则可能按配置直接复制到输出目录,并不需要被 JavaScript 引用。
构建产物可能类似:
css
index.html
main-ABC123.js
chunk-DEF456.js
styles-GHI789.css
logo.png
这些文件名仅作示意。一个 JavaScript 文件可能混合了业务代码和多个依赖库的代码,已经不是 npm 包原来的结构。
3. 访问阶段:浏览器如何加载资源?
构建生成了文件,不代表浏览器会立即下载全部文件。
浏览器首先根据 HTML 中的引用加载资源,例如:
ini
<link rel="stylesheet" href="styles-GHI789.css">
<script src="main-ABC123.js" type="module"></script>
应用运行后,还可能继续请求其他资源:
| 资源 | 可能的加载时机 |
|---|---|
| 入口 JavaScript、全局样式 | 浏览器解析 HTML 时 |
| 懒加载页面的代码 | 访问对应页面时,或被预加载时 |
| 图片 | 对应元素出现并满足加载条件时 |
| 未被任何页面或代码引用的静态文件 | 即使部署在服务器上,也不一定被请求 |
所以, "进入构建产物"和"被浏览器下载"是两回事。
浏览器不会根据 package.json 去安装 npm 包。它根据页面引用和应用运行时的加载行为,请求实际需要的资源。
4. 为什么生产构建还需要 devDependencies?
因为生成生产文件,也需要开发工具。
Angular CLI、构建工具和 TypeScript 编译器虽然属于 devDependencies,但它们参与构建过程。用户访问构建好的网站时,才不再需要运行这些工具。
整个过程可以串成:
markdown
npm 根据依赖声明安装包
↓
构建工具分析代码和配置,生成资源
↓
将构建产物部署到服务器
↓
浏览器根据页面引用和运行时逻辑加载资源
判断一个依赖是否会被用户下载,需要沿着后两个环节看:它的哪些代码进入了构建产物?包含这些代码的文件,又会在什么情况下被浏览器加载?
参考资料: