Node 依赖管理

Xebcnor     最近更新时间:2019-10-10 11:24:48

701

我们介绍了 npm 注册表和 npm CLI。在本单元中,我们将学习如何使用 package.json 文件管理依赖。您还将了解到 Node 应用程序的演变过程,也就是应用程序的编写、发布以及维护周期。

软件在最初发布后需要长期维护。维护活动包括:

  • 修复错误

  • 添加新功能

  • 对底层系统组合进行升级或其他变更

  • 对第三方软件依赖进行升级

开发者往往比较熟悉前三个活动,但最后一个活动或多或少有些失控。这在 Node 中尤为严重,因为要管理的依赖数量庞大。因此,了解 package.json 及其在维护和发展应用程序方面所发挥的作用就十分关键。

什么是 package.json

一个 Node 项目通常称为一个。JSON (JavaScript Object Notation) 是用于描述 Node 包的表示法。

Package.json 是包含包名和版本号等基本信息以及较为复杂的元数据的文件。

npm 需要 package.json 文件来管理项目,提交到 npm 注册表的每个项目都必须有一个 package.json 文件。如果要创建成功的 Node 项目,就需要了解如何创建 package.json 文件。

接下来我们更详细地介绍一下该文件的元素。

项目清单

清单文档用于描述特定对象(如容器)的内容。package.json 是每个 Node 项目都需要的清单。一个 package.json 文件必须至少包含两个元素:

  • name:包的名称

  • version:包的版本号

除此之外,package.json 还可以包含更多字段,但我们首先从必需字段开始。很快我会介绍其他字段。

项目元数据

除了 name 和 version 等元素外,package.json 还包含更复杂的元数据。这些元数据帮助 npm 管理依赖,运行脚本以启动应用程序(参阅第 6 单元),运行单元测试以及执行其他操作。

项目元数据包含如下元素:

  • description:人类可阅读的描述,如果将包发布到 npm 注册表,那么此描述会出现在 npm search 中。

  • entry point:包的主 JavaScript 模块。

  • license:包的许可证标识(软件包数据交换 (SPDX) 格式- 可用许可证包括   Apache2.0MITISC(默认值)等。

  • author:开发者(更具体地说,开发者的姓名和电子邮件地址)。

  • scripts:在包生命周期中的各个时间点运行的脚本(例如,starttest)。

  • dependencies:包所依赖的任何其他 Node.js 包(稍后会作更详细的说明)。

这些只是用于 Node 的一部分元数据元素。可在 Node 文档中阅读有关 package.json 中提供的所有元数据的信息。

共享代码

Node 的最大的优势之一就是 npm 生态系统,而共享代码则是 Node 文化的关键。但是,在自己的程序中使用第三方软件也会让项目变得更加复杂。了解如何梳理 package.json 文件可以帮助您节省大量精力,不必再花费无数的不眠之夜去管理错误修复、软件更新和其他问题。

如何创建 package.json 文件

可通过两种方式来从头创建 package.json

选项 1:交互式问答

如果从命令行运行 npm initnpm 将就新包与您进行互动。它会提出一系列问题。每个问题都有一个默认答案,汇总在下表中。

整个问答如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
 
See `npm help json` for definitive documentation on these fields
and exactly what they do.
 
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
 
Press ^C at any time to quit.
package name: (unit-8)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to package.json:
 
{
  "name": "unit-8",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
 
 
Is this OK? (yes) yes
$

选项 2:接受默认值

创建 package.json 的最快方式就是接受所有默认值,稍后再根据需要进行更改。很幸运,我们可以轻松地使用 npm init -y 命令接受默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ npm init -y
Wrote to package.json:
 
{
  "name": "Unit-8",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
 
 
$

如果尚未执行上述操作,可打开终端窗口或命令提示符,然后浏览至一个空目录。本单元的全部源代码均位于 GitHub 上,但我希望大家从头开始构建此示例。这样,全程自己动手,有助于加深理解。

管理 Node 中的依赖

基于 Node 的开发依赖于第三方软件,因此大多数 Node 应用程序都有为数众多的依赖。随着软件的发展变化,这些依赖可能导致 Node.js 应用程序有如一团乱麻。安装依赖后,就需要对他们进行有效管理。本节将介绍如何管理所有这些依赖。

开始之前,先修改在上一节中生成的 package.json,如下所示(这样我们就同步使用相同的文件了):

1
2
3
4
5
6
7
8
9
10
11
12
{
  "name": "Unit-8",
  "version": "1.0.0",
  "description": "Node.js Course Unit 8 Example Code",
  "main": "unit-8.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "J Steven Perry <jstevenperry@gmail.com>",
  "license": "Apache-2.0"
}

可随意在您的本地副本中用自己的姓名替换 author

可通过两种方式将新包作为依赖添加到 Node 项目中。让我们看一下这两个选项。

选项 1:使用 npm 安装新包

可使用 npm 直接安装新包。在命令行中,输入以下命令:npm install makoto-logger

您应看到类似下面这样的信息:

1
2
3
4
5
6
7
8
9
$ npm install makoto-logger
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN Unit-8@1.0.0 No repository field.
 
+ makoto-logger@1.0.2
added 1 package and audited 1 package in 3.706s
found 0 vulnerabilities
 
$

查看 package.json,会发现与之前安装的 makoto-logger 有两处不同。

第一处,在 license 下有一个新的 dependencies 部分,如下所示:

1
2
3
"dependencies": {
    "makoto-logger": "^1.0.2"
  }

第二处,可以看到 npm 创建了 node_modules 目录,并在此目录下放置了新安装的依赖。

选项 2:在 package.json 中指定新的依赖

安装新包的另一种方式是将其指定为 package.json 中的依赖,然后运行无参数的 npm install。这样会安装新的依赖项及其所有依赖。

要查看实际操作,可将以下依赖添加到 package.json 中的 dependencies 元素(不要忘了在 makoto-logger 依赖后添加逗号):

1
"sqlite3": "^4.0.1"

现在,运行无参数的 npm install 命令。可以看到类似以下的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ npm install
 
> sqlite3@4.0.1 install node_modules/sqlite3
> node-pre-gyp install --fallback-to-build
 
node-pre-gyp WARN Using needle for node-pre-gyp https download
[sqlite3] Success: "node_modules/sqlite3/lib/binding/node-v64-darwin-x64/node_sqlite3.node" is installed via remote
npm WARN Unit-8@1.0.0 No repository field.
 
added 68 packages from 48 contributors and audited 98 packages in 11.595s
found 0 vulnerabilities
 
$

安装多个包

在第 7 单元中,我介绍了 eslint,它用于查找 JavaScript 代码中可能存在的错误。运行以下命令以安装 eslint 和几个随附工具(注意 iinstall 的缩写):

1
npm i --save-dev eslint babel-eslint eslint-config-strongloop

这会安装以下包及其依赖(以递归方式安装):

  • eslint

  • babel-eslint

  • eslint-config-strongloop

--save-dev 标志用于将包依赖保存到 package.json 中名为 devDependencies 的元素内。以下是输出:

1
2
3
4
5
6
7
8
9
10
$ npm i --save-dev eslint babel-eslint eslint-config-strongloop
npm WARN Unit-8@1.0.0 No repository field.
 
+ eslint-config-strongloop@2.1.0
+ babel-eslint@8.2.6
+ eslint@5.1.0
added 148 packages from 186 contributors and audited 431 packages in 10.889s
found 0 vulnerabilities
 
$

刚才我们向项目中添加了几个依赖。package.json 现在应如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "name": "Unit-8",
  "version": "1.0.0",
  "description": "Node.js Course Unit 8 Example Code",
  "main": "unit-8.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "J Steven Perry <jstevenperry@gmail.com>",
  "license": "Apache-2.0",
  "dependencies": {
    "makoto-logger": "^1.0.2",
    "sqlite3": "^4.0.1"
  },
  "devDependencies": {
    "babel-eslint": "^8.2.6",
    "eslint": "^5.1.0",
    "eslint-config-strongloop": "^2.1.0"
  }
}

您可能想知道,每个版本号之前的插入符号 (^) 代表什么。这是语义版本控制 (SemVer) 语法,我们稍后会讨论。

指定版本号

语义版本控制(或 SemVer)是指定版本号的正式方式。该方法由 GitHub 的联合创始人之一 Tom   Preston-Warner 创建。截止撰写本文时,最新的 SemVer   规范为 2.0.0。

SemVer 使用由 3 部分组成的编号方案来指定发行版标签,如下所示:

Major.Minor.Patch

下面是每个部分的含义:

  • Major 用于新的公共 API,它取代(不向后兼容)先前的版本(Major > 0,便是如此)。

  • Minor 用于新的功能部件,仅向后兼容(并不取代)当前的主版本。

  • Patch 用于非取代式的错误修复。

SemVer 规范还有一些扩展,用于创建发行前的标签,但此处将不再详述。

使用 SemVer 的示例和规则

我们来看几个示例:

  • 1.0.0 始终是公共 API 的第 1 个发行版。

  • 1.1.1 表示公共 API 的第 1 个发行版的第 1 个功能部件发行版的第 1 次修订。

  • 2.0.4 表示第 1 个取代性变更(从 1.x 更新到 2.0.0)发布后的第 4 次修订。

版本号不能跳数(比如,从 1.2.41.2.6,跳过 1.2.5);应始终保持递增。

数字始终在任何次级版本组成部分内增加,其上级版本变更时则重置为 0。例如,版本的功能部件发行版 1.2.3 变成 1.3.0,其中 Patch 重置为 0。从 2.9.113.0.0 的取代性变更将次级 MinorPatch 号重置。

管理版本的变更容忍度

依赖发生变化后,如果这不是您想要的结果,那么这些变更可能会对代码带来负面影响。

SemVer 的版本号使包作者能够定性声明特定变更对于先前版本的相对影响。这还使包使用者能够指定对于特定依赖的变更容忍程度

包生成者可遵循 SemVer 准则,轻松通知包使用者,某个依赖的下一个发行版中到底发生了多大变化。

包使用者则可以遵循 SemVer 准则,使包管理器(npmyarn等)能够自动处理这些升级。

如果使用得当,SemVer 可以免去您阅读数百个发行说明的麻烦,只需确定执行升级时代码是否会受到影响即可。

自动升级

如果有成百上千个依赖,那么 SemVer 的工具支持就能发挥至关重要的作用。接下来我们了解一下,如何使用 SemVer 告知包管理器,对于每个包,您愿意接受的版本变更容忍程度。

我们使用 package.json 中的以下 dependencies 片段作为示例:

1
2
3
4
5
6
7
8
9
.
.
  "dependencies": {
    "foo-a": 1.2.3,
    "bar-a": ~2.1.4,
    "baz-a": ^1.2.5
  }
  .
  .

这些语法可能十分复杂,具体取决于您希望 npm 执行的操作。本课程的示例仅限于用户最有可能使用的语法。

示例 1:仅接受单一版本

假设您不愿意容忍对依赖当前版本的任何变更 在这种情况下,只需指定版本号,不含任何特殊字符:

1
"foo-a": 1.2.3,

这指示 npm,"对于包 foo-a,无论何种情况仅使用版本 1.2.3。"

如果不希望 npm 自动升级 foo-a,可使用此命令。

示例 2:仅接受次级更新的一系列补丁

假设您允许针对特定组件的单一次级版本应用一系列补丁。在这种情况下,应使用波浪号 (~) 字符。

1
"bar-a": ~2.1.4,

这指示 npm:"对于包 bar-a,从 2.1.4 到下一功能部件发行版(可能是 2.2.0)之间的任何近似发行版均可接受。"换言之,2.1.5 可以接受(2.1.62.1.7 等也可接受)但 2.2.0 不可接受。

如果您愿意接受错误修订(补丁)但不希望 npm 自动升级到新功能部件发行版,可使用此命令。

示例 3:接受一系列次级功能部件发行版

展开阅读全文