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
这会发生三件事:
- npm 把
lodash及其依赖下载到node_modules/。 - 它在
package.json的dependencies部分加入"lodash": "^4.17.21"(或当前最新的版本)。 - 它写入一个
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>)把包装到系统范围内,通常用于你想在任何地方都能使用的命令行工具。项目依赖优先用本地安装,这样每个项目的版本都是固定的。