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

  1. 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 上的 "登记档案"

  2. 如果直接执行npm install,会把package.jsonpackage-lock.json里面的依赖全部装齐

    package.json里面是范围,lock 里面是具体版本
    如果package里面有而lock里面没有,下载package范围内的最高版本
    如果package里面有且lock里面也有,下载lock的版本
    如果package里面没有但lock里面有,
    如果这个依赖是某个已声明依赖的传递依赖,保留,如果不是,则清除
    (传递依赖会随着主依赖的下载而顺带着下载)

  3. npm i <包名>(不带版本), = 默认要 latest 标签,即使本地已经下载了最新版本依然会问register询问

  4. npm i <包名>@<版本>(指定版本),如果本地已经有了就不在询问register

  5. 如果下载没有完全下载好就失败了,那就不会修改package和lock,但是第二次继续下载的时候第一次下载好的包不会重新下载

  6. 在下载依赖的时候,如果该依赖所需的传递依赖本地已经有了,就不会再下载

下载中断后,第二次各包怎么处理

  1. 已完整下载的包: 直接从npm 的本地缓存解压

  2. 下载到一半失败的包:缓存没有合法条目,必须重新下载

  3. 已解压进 node_modules 的包:实际树里有它,重试时 diff 算"已有",连解压都省了

    diffreify(动手装) 之前的第一步: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验证一次

posted @ 2026-09-19 16:39  来自M78的光之文轩  阅读(2)  评论(0)    收藏  举报