Menu

npm 入门:install、init、update 与依赖管理

npm 实际上是怎么工作的:安装包、init、开发依赖、更新,以及理解 node_modules 和锁文件的思路。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

npm 到底是什么

npm 是用一个名字打包的三样东西。它是一个仓库:位于 npmjs.com 的庞大公共 JavaScript 包数据库。它是一个命令行工具:随 Node.js 一起提供,用来安装和管理这些包。它还是一套规范(package.json 格式),用来描述一个项目需要什么。

当你运行 npm install express 时,命令行工具会访问仓库,下载 express 及其所有依赖,把文件放进一个名为 node_modules 的文件夹,并在 package.json 中记录这个包和它的版本。整个流程就是这样。

只要装了 Node.js,你就已经有 npm 了。检查一下:

node --version
npm --version

如果两条命令都打印出版本号,就可以开始了。

创建项目:npm init

每个 npm 项目都需要一个 package.json。它是项目的清单,列出项目的名称、版本、脚本和依赖。创建它最快的方法是 npm init -y,它会接受所有默认值:

mkdir my-app
cd my-app
npm init -y

生成的内容大致如下:

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

去掉 -y,npm 会以交互方式逐个询问每个字段。无论哪种方式,最后你都会得到一个 package.json,其他一切都围绕这个文件展开。下一页会详细介绍它的字段。

安装一个包

有了 package.json 之后,用 npm install(简写为 npm i)安装包:

npm install lodash

这会发生三件事:

  1. npm 把 lodash 及其依赖下载到 node_modules/。
  2. 它在 package.json 的 dependencies 部分加入 "lodash": "^4.17.21"(或当前最新的版本)。
  3. 它写入一个 package-lock.json,记录依赖树中每个包的确切版本。

现在就可以使用它了:

require 调用(在 ESM 项目中是 import)会到 node_modules 里查找这个包。你不需要写路径,Node 的模块解析器会处理好。

dependencies 与 devDependencies

并不是每个包都要在生产环境中运行。测试框架、代码检查工具和打包器只在开发时有用。用 --save-dev(或 -D)安装它们:

npm install --save-dev jest
npm install -D eslint prettier

它们会被放进 devDependencies,而不是 dependencies:

{
  "dependencies": {
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "eslint": "^8.57.0",
    "prettier": "^3.2.5"
  }
}

在生产服务器上,npm install --omit=dev 会完全跳过开发依赖部分,让安装更小、更快。把这两类分清楚比看起来更重要:一个误放进 dependencies 的 webpack,会让每次生产部署都变得臃肿。

一次安装所有依赖

克隆一个已经有 package.json 的仓库时,不需要逐个列出包,只要运行:

npm install

不带参数时,npm 会读取 package.json(并遵循 package-lock.json 中的确切版本),把整个依赖树安装到 node_modules。这是每次拿到新代码后要运行的第一条命令。

这也是 node_modules 应该放进 .gitignore 的原因。它可以通过锁文件重新生成,体积巨大,而且每次有人运行 npm install 都会变化。提交 package.json 和 package-lock.json,让其他人自己生成 node_modules。

更新包

npm outdated 会显示哪些包落后了:

npm outdated

你会看到一个包含 Current、Wanted 和 Latest 列的表格。Wanted 是 package.json 中的版本范围所允许的最新版本(对于 ^4.17.21,就是低于 5.0.0 的任何版本)。Latest 是已发布的最新版本,可能是一个你还没有选择升级的主版本。

在允许的范围内更新:

npm update

要直接升级到真正的最新版本(包括主版本升级),用 @latest 重新安装这个包:

npm install lodash@latest

主版本升级可能会让你的代码出错,这正是版本号想要传达的信号。跨越主版本之前,先读一读更新日志。

卸载

删除一个包的操作与安装对称:

npm uninstall lodash

这会把它从 node_modules 中移除,并删除 package.json 中对应的条目。如果它是开发依赖,可以加上 -D(不加 npm 也能识别,但写明白可以避免脚本中出现意外)。

全局与本地

几乎所有的安装都应该是本地的,固定在某个项目的 node_modules 中。例外是那些你希望在任何地方都能使用的命令行工具:

npm install -g typescript
npm install -g http-server

全局安装会把工具放到系统范围的位置,并把它的 bin 入口加入 PATH,这样你就可以在任何目录下运行 tsc 或 http-server。但全局安装不会按项目记录,不同机器之间的版本也可能逐渐不一致。

对于偶尔用一次的命令,更好的折中方案是 npx,它随 npm 一起提供:

npx create-react-app my-app
npx prettier --write .

npx 可以在不全局安装的情况下运行一个包:按需获取,运行完就结束。对于只用一次的工具,这比永久的全局安装更干净。

精简速查表

日常真正会用到的命令:

npm init -y                     # create package.json
npm install                     # install everything in package.json
npm install <pkg>               # add a runtime dependency
npm install -D <pkg>            # add a dev dependency
npm install -g <pkg>            # install a CLI tool globally
npm uninstall <pkg>             # remove a dependency
npm outdated                    # see what's out of date
npm update                      # update within allowed ranges
npm install <pkg>@latest        # jump to the newest version
npm run <script>                # run a script from package.json
npx <pkg>                       # run a package without installing it

npm 的大部分内容就是这些。其余的部分,比如发布、工作区、作用域包,可以等需要时再学。

node_modules 里到底有什么

最后一个要理解的概念。node_modules 是一个大体扁平的文件夹,包含项目依赖的每个包,以及这些包所依赖的一切,层层传递。安装一个包可能会拉进来一百个包,这很正常。npm 会尽可能去重,所以两个依赖同一版本 lodash 的包会共享一份副本。

锁文件(package-lock.json)记录了每个包最终解析出的确切版本。正是它让构建可以复现:两个开发者基于同一个锁文件运行 npm install,即使相隔几个月,得到的依赖树也逐字节相同。

把 node_modules 当作生成的产物。永远不要修改其中的文件:下次有人安装时,你的改动就会消失。

下一章:package.json

package.json 是 npm 在幕后不断读取和改写的文件。了解它的字段,比如 scripts、main、type、版本范围和 engines,能让 npm 从一个黑盒变成你能掌控的工具。这就是下一章的内容。

常见问题

npm 是什么?

npm 是 Node.js 默认的包管理器。它随 Node 一起安装,托管着一个庞大的公共 JavaScript 包仓库,并提供一个命令行工具来安装、更新和发布这些包。当你运行 npm install lodash 时,npm 会从仓库下载 lodash 到 node_modules,并把它记录到 package.json 中。

dependencies 和 devDependencies 有什么区别?

dependencies 是应用在生产环境中运行所需的包,比如 express 或 react。devDependencies 只在开发或构建时需要,比如测试运行器、打包器和代码检查工具。后者用 npm install --save-dev <pkg>(或 -D)安装。在生产环境中,npm install --omit=dev 会跳过 devDependencies。

node_modules 应该提交到 git 吗?

不应该。node_modules 动辄几百 MB,而且完全可以通过 package.json + package-lock.json 重新生成。把它加入 .gitignore,改为提交锁文件。任何人克隆你的仓库后运行 npm install,都会得到完全相同的依赖树。

npm 全局安装和本地安装是什么意思?

本地安装(npm install <pkg>)把包放进项目的 node_modules,并记录到 package.json 中。全局安装(npm install -g <pkg>)把包装到系统范围内,通常用于你想在任何地方都能使用的命令行工具。项目依赖优先用本地安装,这样每个项目的版本都是固定的。

Coddy 编程语言插图

和 Coddy 一起学编程

开始