首页 / 文章 / npm install的实际功能:注册表、package.json与锁定文件

npm install的实际功能:注册表、package.json与锁定文件

npm实用指南:注册表与命令行界面,npm install如何解析包,package.json和package-lock.json记录了什么内容,以及如何发布包。

1605 词

几乎所有的 JavaScript 项目,无论是在 Node.js、React 还是 Next.js 上运行,都是从同一个命令开始的。许多开发者每天都在使用它,却并不清楚它的具体作用。了解包的来源、npm 是如何决定安装什么的,以及 package.jsonpackage-lock.jsonnode_modules 各自的用途,都能帮助更轻松地调试安装故障。本指南将涵盖从首次安装到发布自己的包的每一个环节。

大家常用的命令

最常见的方式是安装项目已声明的所有依赖:

npm install

同样常见的做法是按名称添加库,比如 Express Web 框架:

npm install express

或是 Axios HTTP 客户端:

npm install axios

这些简短语句背后其实隐藏着几个问题:代码是从哪里下载的?npm 是如何知道一个项目依赖了哪些内容的?package.json 的作用是什么?又为何 node_modules 的体积会变得如此之大?

什么是 npm

npm 是 Node.js 的默认包管理器,随节点引擎一同提供。它让开发者能够使用其他开发者发布的海量可重用包,从而直接引入经过验证的代码,无需自行编写。

可以将其视为一个公共代码库。假设某个 Node.js 后端需要一个 HTTP 服务器,与其从零开始构建路由和中间件功能,不如直接使用 Express:

npm install express

需要调用其他 API?那就添加一个 HTTP 客户端:

npm install axios

针对传入数据的结构验证工作由 Zod 负责:

npm install zod

bcrypt负责对密码进行哈希处理:

npm install bcrypt

这正是该生态系统的最大优势:既然已有经过充分测试的解决方案,就很少有必要自行编写。但另一方面,每个已安装的包都是你需要信任的代码,因此应优先选择维护完善、使用广泛的库,并尽量精简依赖列表。有关为何这一点如此重要的详细说明,请参阅npm供应链攻击的运作原理及防御方法

一个生态系统,而不仅仅是一个命令

初学者往往将npm视为只是在终端中输入的一个单词。实际上,它涵盖了多个相互协作的组成部分:

  • npm注册表:用于发布和下载软件包的公共服务。
  • npm CLI:用于与注册表交互并管理依赖关系的命令行工具。
  • 包:即可重复使用的代码单元本身。
  • package.json用于描述项目及其依赖项的清单文件。
  • 公共包是免费的:无需账户即可安装任何公共包。这种低门槛促使 JavaScript 开源项目在全球范围内传播。只有发布时才需要账户。

    运行 npm install 时会发生什么

    以一个添加了 Express 的项目为例:

    npm install express
    

    大致流程如下:

    1. npm 在注册表中查找该包,并选择符合要求范围的版本(若未指定则选最新版)。
    2. 它将该包自身的依赖项以及这些依赖项的进一步依赖项解析为完整的依赖树。
    3. 将尚未缓存的文件下载并存放在 node_modules 中。
  • 它在package.json中记录依赖关系,而在package-lock.json中记录精确的已解析依赖结构。
  • 正是通过这样一个简短的命令,就能引入一整套功能。

    package.json:项目清单

    每个Node.js项目的核心都包含一个文件:

    package.json
    

    它兼具项目身份标识与配置功能,其中包含项目名称、版本、描述、脚本、运行时依赖和开发依赖以及作者信息。一个最简单的示例会在main中指定入口文件、一个脚本以及一个依赖项:

    {
      "name": "my-project",
      "version": "1.0.0",
      "description": "My Node.js application",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "dependencies": {
        "express": "^5.1.0"
      }
    }
    

    name

    name用于标识项目或包。如果将其发布到注册表中,人们就会输入这个名称来安装它,因此它在注册表中的值必须是唯一的。

    "name": "my-project"
    

    version

    version用于存储包的当前版本号。

    "version": "1.0.0"
    

    在发布时这一点尤为重要。npm采用语义化版本控制(MAJOR.MINOR.PATCH):破坏性变更会提升主版本号,兼容性功能则更新次版本号,修复问题则用补丁版本号。

    描述

    description是该项目功能的一行摘要,会显示在注册表搜索结果中。

    "description": "My Node.js application"
    

    脚本

    scripts用于为常用命令定义带名称的快捷方式。这里的start用于启动入口文件:

    "scripts": {
      "start": "node index.js"
    }
    

    如果没有它,就需要输入完整的命令:

    node index.js
    

    有了它,就可以用这种方式实现相同功能:

    npm start
    

    关键在于一致性:所有贡献者和每项CI任务都会执行相同名称的命令。像build这样的自定义名称则通过npm run build来执行。

    依赖项:项目所依赖的内容

    想象一个外卖配送平台。其后端可能会使用:

    • 用于处理 API 的 Express
    • 用于数据验证的 Zod
    • 用于密码哈希处理的 bcrypt
    • 用于身份验证的 JSON Web Token 库
    • 用于操作 MongoDB 的 Mongoose

    这些都是依赖项。当你安装其中一个时:

    npm install express
    

    npm 会在 package.json 中添加一条记录:

    "dependencies": {
      "express": "^5.1.0"
    }
    

    ^5.1.0 中的波浪号表示范围:任何后续的 5.x 版本均可,但 6.0.0 不行。这条记录会告知查看该项目的任何人,要运行它需要 Express。仅在开发阶段需要的工具,如测试运行器,会通过 npm install --save-dev 被放入 devDependencies 中。

    为何一个命令就能服务于整个团队

    假设你加入了一个团队并克隆了它的代码库。没人会期望你手动下载五十个库。你只需运行:

    npm install
    

    npm会读取所声明的依赖项并将它们全部安装。正是通过根据描述重建工作环境,包管理器才显得不可或缺。

    node_modules:已安装代码的存放位置

    安装完成后,会出现一个新文件夹:

    node_modules/
    

    该文件夹中包含你请求的包以及它们所依赖的所有内容,这也是它体积较大的原因。典型的目录结构如下:

    my-project/
    │
    ├── node_modules/
    ├── package.json
    ├── package-lock.json
    └── index.js
    

    切勿将node_modules提交到Git中。它的体积很大,可能包含特定平台的构建版本,而且随时都可以重新生成。应将其加入.gitignore文件中,而是提交以下内容:

    package.json
    package-lock.json
    

    这样其他人就可以运行:

    npm install
    

    从而获得相同的依赖项。

    package-lock.json:精确的记录文件

    在manifest文件旁边还有一个额外的文件:

    package-lock.json
    

    为什么需要两个文件?简而言之:

    • package.json说明了项目所需的内容,通常以版本范围的形式呈现。
    • package-lock.json则记录了实际安装的内容:整个依赖树中的每个版本信息以及完整性校验值。

    由于版本范围可能会随着时间与新的发布版本匹配,因此锁文件能确保不同设备、团队成员以及持续集成环境使用完全相同的版本。请将此锁文件加入版本控制,并在持续集成过程中优先使用npm ci,它会严格依据锁文件中的信息进行安装。

    值得了解的npm命令

    添加一个包:

    npm install express
    

    安装项目声明的所有依赖:

    npm install
    

    node_modulespackage.json中移除一个包:

    npm uninstall express
    

    将包更新到版本范围允许的最新版本(不会跨越主版本号):

    npm update
    

    打印 npm 版本:

    npm -v
    

    打印 Node.js 版本:

    node -v
    

    显示您当前登录的 npm 账户:

    npm whoami
    

    从用户变为发布者

    您也可以为开源项目贡献代码。发布一个有用的库,其他人就可以像使用其他库一样安装它:

    npm install your-package
    

    正是这样的循环让开源项目得以持续发展:在他人工作的基础上进行改进,创建属于自己的作品并分享回去。

    逐步发布流程

    您需要一个 npm 账户。在终端中登录:

    npm login
    

    确认当前使用的账户:

    npm whoami
    

    进入项目目录:

    cd myproject
    

    确保项目中存在包含唯一 nameversion 的有效 manifest 文件:

    package.json
    

    然后进行发布:

    npm publish
    

    该包现已向所有人开放。两个建议:首先运行 npm pack --dry-run 查看哪些文件会被打包,同时在每次发布时都要提升 version 数值,因为已发布的版本无法重复使用。请查阅最新的 npm 文档,了解有关双重身份验证等账户安全要求的信息。

    各部分如何协同工作

    整个流程如下:

    Developer
        ↓
    npm install
        ↓
    npm Registry
        ↓
    Package + Dependencies
        ↓
    node_modules
        ↓
    package.json
        ↓
    Your Application
    

    你运行命令后,CLI 会从注册表中获取包,代码会被存入 node_modules 目录,清单文件和锁定文件会记录相关信息,最终你的应用程序会导入这些代码。正是这一系列流程让开发者能够高效地重复使用、管理、共享和发布代码。

    关键要点

    npm 看起来可能只是一堆终端命令,但一旦你了解了其背后的运作机制:

    npm install
    

    这样的系统更易于理解。JavaScript 提供了编程语言,Node.js 提供运行时环境,而 npm 则提供了代码共享的生态系统。实际使用中:

    • package.json 用于声明意图;package-lock.json 用于记录实际状态。两者都需提交。
    • 绝不要提交 node_modules 目录。
    • 通过光标选择范围可以实现小版本和补丁更新,npm update 也应在该范围内执行。
    • 在自动化场景中可使用 npm ci 进行精确的安装。
    • 应谨慎添加依赖项;每一个依赖都是你信任的代码。

    所以下次当你输入代码时:

    npm install
    

    请记住,你正在将自己的项目与由全球开发者构建的生态系统相连。

    相关阅读