4.1 npm基础:命令、结构与依赖治理


4.1 npm 基础:命令、结构与依赖治理

本节摘要:npm 是 Node 的官方包管理器,也是全球最大软件仓库的入口。本节覆盖高频命令与依赖类型、node_modules 扁平化结构的由来与幽灵依赖问题、npm/yarn/pnpm 的设计差异,以及 scripts 与 npx 的日常用法。目标:装得快、装得对、装得可复现。

学习目标

  1. 能区分 dependencies 与 devDependencies 等五种依赖类型的用途
  2. 能解释扁平化安装与幽灵依赖的成因
  3. 能对比 npm 与 pnpm 在磁盘与严格性上的取舍
  4. 能用 scripts 与 npx 组织本地工具链

一、日常命令清单

npm init -y # 生成 package.json npm install # 按清单安装全部依赖 npm install express --save # 装包并记入 dependencies(默认 --save) npm install -D jest # 记入 devDependencies npm install -g pm2 # 全局安装(通常是命令行工具) npm uninstall express # 卸载并从清单移除 npm outdated # 查看过期依赖 npm audit # 安全审计已知漏洞 npm ci # 严格按 lockfile 安装,CI 专用

最容易混淆的是 installci:前者会做版本仲裁、可能改写 lockfile;后者要求 lockfile 与清单完全一致,按锁定版本原样安装、装完删掉旧 node_modules,速度更快也更可复现——CI 流水线里永远用 ci

五种依赖类型各管一段生命周期:

类型 去向 典型内容
dependencies 生产环境 express、数据库驱动
devDependencies 仅开发与测试 jest、eslint
peerDependencies 宿主提供 插件声明自己兼容的框架版本
optionalDependencies 可选 装不上也不报错的功能增强
bundledDependencies 打包随行 需要一起发布的私有模块

peerDependencies 值得多看一眼:写插件时声明它,可以避免同一个框架被装两份——两份 React 或两份 Express 混用是诡异的运行时错误来源。

二、node_modules:为什么长成这样

npm v2 时代,每个依赖把自己的依赖装进自己的 node_modules,树越套越深,同样的 lodash 在不同分支重复出现,一个中型项目轻松几百 MB。npm v3 起改为扁平化(提升):尽量把所有包拍平到顶层 node_modules,只有版本冲突时才下沉嵌套。

这带来一个著名的副作用——幽灵依赖:你在 package.json 里只声明了 express,但代码里 require('debug') 也能成功,因为它是 express 的依赖、被提升到了顶层。问题在于这层便利没有契约保障:哪天 express 升级换掉了 debug,你的代码就在没有改任何一行的情况下崩了。

治理手段有三个层次:

1. 规约层:lint 规则禁止 require 未声明的包(如 eslint-plugin-import) 2. 工具层:改用 pnpm,默认只暴露已声明的依赖 3. 审查层:定期 npm ls <包名> 查看某包从哪条依赖链来

npm ls 是排查「版本打架」的第一工具,它会打印出依赖树里同名的不同版本及其引入路径。

三、npm 之外的选手

yarn 在 npm v6 时代以离线缓存与确定性安装胜出,如今功能差距已小。真正的思路革新来自 pnpm:

  • 硬链接内容寻址:同一个包的全局内容只存一份,项目里只是硬链接,十个项目共用一份磁盘占用。
  • 非扁平的严格结构:node_modules 里只有你声明的包可直接访问,依赖的依赖藏在与常规解析隔离的存储里,幽灵依赖从结构上绝迹。
  • monorepo 友好:workspace 协议让多包仓库共享依赖版本。

代价是「太严格」:从 npm 迁过来的老项目里那些幽灵依赖会立刻暴露为报错——这其实是帮你把隐患提前引爆。我的建议:新项目直接 pnpm,老项目迁移前先跑一遍 lint 把幽灵依赖清干净。

四、scripts 与 npx:本地工具链

package.json 的 scripts 字段是项目的自动化入口:

{ "scripts": { "start": "node app.js", "dev": "node --watch app.js", "test": "jest --coverage", "lint": "eslint ." } }

npm run dev 会把项目内 node_modules 的 bin 目录加入临时 PATH,所以本地安装的命令行工具不需要全局装。npx 则更进一步——直接执行,未安装就临时拉取:

npx cowsay "依赖不该为了用一次而常驻" npx eslint --fix src

这也回答了「全局安装何时合适」:只有跨项目天天用的通用工具(如第 7 章的 pm2)值得 -g,其余尽量本地化,把环境装进项目而不是装进机器——新同事克隆仓库后一条 install 即可复现你的全部工具链。

⚠️ 常见坑:install 失败时先看网络与 registry 镜像配置,再怀疑代码;删 node_modules 重装是最后一招,别当成第一反应。锁文件冲突时以「合并工具的 union 策略 + 重新 install 校验」解决,而不是手改 lockfile。

一条可选的进阶路径

当项目多到需要共享代码时,下一步是私有 registry 与 workspace:前者让公司内部包不经过公网分发,后者让单仓库多包共享依赖与脚本。这两件事都不急于第一时间做,但值得知道路在哪里——依赖治理的尽头不是工具,而是团队对「可复现构建」的共识。

本节要点回顾

  • ci 不是 install 的别名:CI 用 ci,本地开发用 install,各司其职。
  • 扁平化换来了空间,送出了幽灵依赖:用 lint 规则或 pnpm 堵住。
  • pnpm 的两把刀:硬链接省磁盘,严格结构防越权引用。
  • 依赖五分类:生产/开发/宿主/可选/随行,写对类型才能瘦生产镜像。
  • 工具本地化:scripts + npx 让项目自带工具链,机器保持干净。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U