npm命令解析
npm i -D unplugin-auto-import
npm i:npm install的简写,安装包
-D:--save-dev的简写,装到开发依赖(devDependencies)
unplugin-auto-import: 包名,就是 Vite 里那个自动导入插件
为什么用 -D 而不是直接 npm i?
因为这类插件只在开发 / 构建阶段起作用(Vite 编译时扫描代码、补 import),跑起来的网页本身不依赖它。所以放 devDependencies:生产部署时 npm install --production 不会装它,包更小。像 Vue、Element Plus 这种运行时要用的是 dependencies。
package.json 里的分工:
"dependencies": { // 运行时需要
"vue": "^3.5.40",
"element-plus": "^2.14.3"
},
"devDependencies": { // 只在开发/构建时用
"unplugin-auto-import": "^21.1.0",
"unplugin-vue-components": "^32.1.0",
"vite": "^8.1.5"
}
装 "构建工具" 用 -D,装 "运行时库" 不用 -D
详细步骤
npm i -D unplugin-auto-import 执行时:
1. 读本地(只加载,不判定)
读 package.json(已有依赖)、lock(虚拟树)、node_modules(实际树)
2. 构建理想树
└─先根据package.json建立理想树
├─ 已有边:查 lock 候选 → 满足 → 照抄 lock 版本;不满足/没有 → 问 registry 取范围内最高版
├─ 新边 unplugin-auto-import:lock 里没有 → 问 registry → latest = 21.1.0 → 挂入树
├─ 递归它的 dependencies(unimport、unplugin…):每条边同上判定
└─没有 lock 时:以 node_modules 为起点建树,并进行后续的校验
理想树的边就是依赖
不带版本号表示下载最新版本,下载最新版本的时候不查 lock/本地
3. diff: 获取操作清单
对比理想树和实际树(node_modules),给出 "该装什么、该删什么、该换什么" 的完整操作清单
关于node_modules的校验:即使在建树阶段校验过了,这里还要校验了,因为建树阶段的校验是为了知道"这条边用什么版本",而diff的校验是为了获得操作清单
4. 实际化(reify):按树下载
照着diff给出的清单执行 → 下载解压、删除孤儿、替换版本
下载好的依赖解压进node_modules
即使我运行npm i <包名>的目的只是想要安装<包名>,npm依旧按照清单下载解压所有的包
5. 保存(全部成功后才写)
先写 package.json(save-prefix(^) + 21.1.0 → "^21.1.0")→ 最后写 lock(21.1.0 + 整棵树)
关于理想树
理想树是我们想要得到的依赖结构。
命令运行结束后,理想树会写进 package-lock.json。
我们将package-lock.json称为虚拟树,node_modules称为实际树。
建树时使用
package-lock.json是因为package-lock.json是上一次npm命令运行之后的产物,通过直接调用上一次运行后的结果来建树会更加的快
关于npm install
-
npm i unplugin-auto-import没写版本 = 默认要 latest 标签,
问 registry 要 latest,包 manifest 里有dist-tags: { latest: "21.1.0" }registry是 npm 的 "中央仓库服务器",存放着所有公开 npm 包的地方。
官方地址:https://registry.npmjs.org
使用命令行npm config ls -l可以查看本机的具体配置
manifest 是你要装的那个包(unplugin-auto-import)在 registry 上的 "登记档案" -
如果直接执行
npm install,会把package.json和package-lock.json里面的依赖全部装齐package.json里面是范围,lock 里面是具体版本
如果package里面有而lock里面没有,下载package范围内的最高版本
如果package里面有且lock里面也有,下载lock的版本
如果package里面没有但lock里面有,
如果这个依赖是某个已声明依赖的传递依赖,保留,如果不是,则清除
(传递依赖会随着主依赖的下载而顺带着下载) -
npm i <包名>(不带版本), = 默认要 latest 标签,即使本地已经下载了最新版本依然会问register询问 -
npm i <包名>@<版本>(指定版本),如果本地已经有了就不在询问register -
如果下载没有完全下载好就失败了,那就不会修改package和lock,但是第二次继续下载的时候第一次下载好的包不会重新下载
-
在下载依赖的时候,如果该依赖所需的传递依赖本地已经有了,就不会再下载
下载中断后,第二次各包怎么处理
-
已完整下载的包: 直接从npm 的本地缓存解压
-
下载到一半失败的包:缓存没有合法条目,必须重新下载
-
已解压进 node_modules 的包:实际树里有它,重试时 diff 算"已有",连解压都省了
diff 是 reify(动手装) 之前的第一步:
Diff.calculate({ actual, ideal })把 node_modules 现状和理想树逐节点对比,产出一张 "ADD / REMOVE / CHANGE / 不动" 的操作清单,_reifyPackages 再照着清单执行。 它是 npm 的 "决策层",负责回答一个关键问题:到底哪些事值得做。
为什么缓存能 "认出" 要用的文件
npm 的缓存是内容寻址的:每个下载完成的 tarball 按它的哈希值(integrity)存进 _cacache。而 lock 里恰好记录着每个包的 integrity 字段 ——lock 里的 integrity 就是找缓存的钥匙:
"node_modules/left-pad": {
"version": "1.0.0",
"integrity": "sha512-Xvly4CWfzT7TGZRB2Qd7ub2dmJDomZCNf3BuT3XXfrNerQHtta82NNhbvToU4Qiz7IHrgY...",
"resolved": "https://registry.npmmirror.com/left-pad/-/left-pad-1.0.0.tgz"
}
下载时先算哈希,对得上才写进缓存;第二次要装时按同一个哈希查缓存,查到就直接用。
npm i <包名>@<版本> --offline
--offline 表示完全不联网,包括查询 registry 也不行。
--prefer-offline 表示缓存优先。如果缓存命中了,即使缓存过期了,也不查询registry
默认 什么都不加的话也是缓存优先,但如果缓存过期了的话,会查询registry验证一次

浙公网安备 33010602011771号