如何使用带有npm和package.json的Node.js模块
作为 Write for DOnations 计划的一部分,作者选择了 Open Internet/Free Speech Fund 来接受捐赠。
介绍
由于其快速的输入/输出 (I/O) 性能和众所周知的 JavaScript 语法等特性,Node.js 已迅速成为后端 Web 开发的流行运行时环境。 但随着兴趣的增长,构建了更大的应用程序,并且管理代码库的复杂性及其依赖关系变得更加困难。 Node.js 使用 modules 来组织这种复杂性,这些模块是任何单个 JavaScript 文件,其中包含其他程序或模块可以使用的函数或对象。 一个或多个模块的集合通常称为 包 ,这些包本身由包管理器组织。
Node.js 包管理器 (npm) 是 Node.js 生态系统中默认和最流行的包管理器,主要用于安装和管理 Node.js 项目中的外部模块。 它还常用于安装各种 CLI 工具和运行项目脚本。 npm 使用 package.json
文件跟踪项目中安装的模块,该文件位于项目目录中,包含:
- 项目所需的所有模块及其安装的版本
- 项目的所有元数据,例如作者、许可证等。
- 可以运行以在项目中自动执行任务的脚本
当您创建更复杂的 Node.js 项目时,使用 package.json
文件管理元数据和依赖项将为您提供更可预测的构建,因为所有外部依赖项都保持不变。 该文件将自动跟踪此信息; 虽然您可以直接更改文件以更新项目的元数据,但您很少需要直接与其交互来管理模块。
在本教程中,您将使用 npm 管理包。 第一步是创建和理解 package.json
文件。 然后,您将使用它来跟踪您在项目中安装的所有模块。 最后,您将列出您的软件包依赖项、更新您的软件包、卸载您的软件包,并执行审计以查找您的软件包中的安全漏洞。
先决条件
要完成本教程,您需要:
- Node.js 安装在您的开发机器上。 本教程使用版本 10.17.0。 要在 macOS 或 Ubuntu 18.04 上安装它,请按照 如何在 macOS 上安装 Node.js 和创建本地开发环境中的步骤或 的 使用 PPA 部分安装如何在 Ubuntu 18.04 上安装 Node.js。 通过安装 Node.js,您还将安装 npm; 本教程使用版本 6.11.3。
第 1 步 — 创建 package.json
文件
我们从设置示例项目开始本教程——一个虚构的 Node.js locator
模块,它获取用户的 IP 地址并返回原产国。 您将不会在本教程中对模块进行编码。 但是,如果您正在开发它,您管理的软件包将是相关的。
首先,您将创建一个 package.json
文件来存储有关项目的有用元数据并帮助您管理项目的依赖 Node.js 模块。 正如后缀所暗示的,这是一个 JSON(JavaScript Object Notation)文件。 JSON 是一种用于共享的标准格式,基于 JavaScript 对象 并由存储为键值对的数据组成。 如果您想了解有关 JSON 的更多信息,请阅读我们的 JSON 简介 文章。
由于 package.json
文件包含许多属性,手动创建可能很麻烦,无需从其他地方复制和粘贴模板。 为了让事情变得更简单,npm 提供了 init
命令。 这是一个交互式命令,它会询问您一系列问题并根据您的回答创建一个 package.json
文件。
使用 init
命令
首先,设置一个项目,以便您可以练习管理模块。 在您的 shell 中,创建一个名为 locator
的新文件夹:
mkdir locator
然后进入新文件夹:
cd locator
现在,通过输入以下内容初始化交互式提示:
npm init
注意:如果您的代码将使用Git进行版本控制,请先创建Git存储库,然后运行npm init
。 该命令会自动理解它位于启用 Git 的文件夹中。 如果设置了 Git 远程,它会自动为您的 package.json
文件填写 repository
、bugs
和 homepage
字段。 如果您在创建 package.json
文件后初始化了 repo,则必须自己添加此信息。 有关 Git 版本控制的更多信息,请参阅我们的 Git 简介:安装、使用和分支 系列。
您将收到以下输出:
OutputThis 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: (locator)
首先会提示您输入新项目的 name
。 默认情况下,该命令假定它是您所在文件夹的名称。 每个属性的默认值显示在括号 ()
中。 由于 name
的默认值适用于本教程,请按 ENTER
接受它。
下一个要输入的值是 version
。 如果您的项目将与 npm 包存储库中的其他人共享,则与 name
一起,此字段是必需的。
注意: Node.js 包应该遵循 Semantic Versioning (semver) 指南。 因此,第一个数字将是 MAJOR
版本号,仅在 API 更改时才会更改。 第二个数字将是 MINOR
版本,在添加功能时会发生变化。 最后一个数字将是 PATCH
版本,在修复错误时会更改。
按 ENTER
接受默认版本。
下一个字段是 description
——一个有用的字符串来解释你的 Node.js 模块的作用。 我们虚构的 locator
项目将获取用户的 IP 地址并返回原产国。 合适的 description
将是 Finds the country of origin of the incoming request
,所以输入这样的内容并按 ENTER
。 当人们搜索您的模块时,description
非常有用。
以下提示将要求您输入 entry point
。 如果有人安装并 requires
您的模块,您在 entry point
中设置的内容将是您程序的第一部分被加载。 该值需要是 JavaScript 文件的相对位置,并将添加到 package.json
的 main
属性中。 按 ENTER
保持默认值。
注意:大多数模块都有一个index.js
文件作为主要入口点。 这是 package.json
的 main
属性的默认值,它是 npm 模块的入口点。 如果没有package.json
,Node.js会默认尝试加载index.js
。
接下来,您将被要求提供 test command
,一个可执行脚本或命令来运行您的项目测试。 在许多流行的 Node.js 模块中,测试是使用 Mocha、Jest、Jasmine 或其他测试框架编写和执行的。 由于测试超出了本文的范围,请暂时将此选项留空,然后按 ENTER
继续。
然后 init
命令将询问项目的 GitHub 存储库。 在此示例中您不会使用它,因此也将其留空。
在存储库提示之后,该命令要求输入 keywords
。 此属性是一个字符串数组,其中包含人们可以用来查找您的存储库的有用术语。 最好有一小部分与你的项目真正相关的词,这样搜索才能更有针对性。 将这些关键字列为字符串,用逗号分隔每个值。 对于此示例项目,在提示符处键入 ip,geo,country
。 完成的 package.json
将在 keywords
的数组中包含三个项目。
提示中的下一个字段是 author
。 这对于想要与您联系的模块用户很有用。 例如,如果有人在您的模块中发现了漏洞,他们可以使用它来报告问题,以便您修复它。 author
字段是以下格式的字符串:"Name \<Email\> (Website)"
。 例如,"Sammy \<sammy@your_domain\> (https://your_domain)%22
是有效作者。 电子邮件和网站数据是可选的——有效的作者可能只是一个名字。 将您的联系方式添加为作者并使用 ENTER
确认。
最后,系统会提示您输入 license
。 这决定了用户在使用您的模块时将拥有的合法权限和限制。 许多 Node.js 模块都是开源的,因此 npm 将默认设置为 ISC。
此时,您将查看您的许可选项并决定最适合您的项目的选项。 有关不同类型的开源许可证的更多信息,请参阅 Open Source Initiative 中的此 许可证列表。 如果您不想为私有存储库提供许可证,可以在提示符处键入 UNLICENSED
。 对于此示例,使用默认的 ISC 许可证,然后按 ENTER
完成此过程。
init
命令现在将显示它要创建的 package.json
文件。 它看起来类似于:
OutputAbout to write to /home/sammy/locator/package.json: { "name": "locator", "version": "1.0.0", "description": "Finds the country of origin of the incoming request", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "ip", "geo", "country" ], "author": "Sammy <sammy@your_domain> (https://your_domain)", "license": "ISC" } Is this OK? (yes)
一旦信息与您在此处看到的相符,请按 ENTER
完成此过程并创建 package.json
文件。 使用此文件,您可以记录为项目安装的模块。
现在您已经有了 package.json
文件,您可以在下一步中测试安装模块。
第 2 步 — 安装模块
在软件开发中,使用外部库来执行项目中的辅助任务是很常见的。 这使开发人员可以专注于业务逻辑并更快、更高效地创建应用程序。
例如,如果我们的示例 locator
模块必须发出外部 API 请求来获取地理数据,我们可以使用 HTTP 库来简化该任务。 由于我们的主要目标是向用户返回相关的地理数据,我们可以安装一个包,使 HTTP 请求对我们来说更容易,而不是为自己重写此代码,这项任务超出了我们的项目范围。
让我们来看看这个例子。 在您的 locator
应用程序中,您将使用 axios 库,它将帮助您发出 HTTP 请求。 通过在 shell 中输入以下内容来安装它:
npm install axios --save
您以 npm install
开始此命令,它将安装软件包(为简洁起见,您可以使用 npm i
)。 然后列出要安装的软件包,用空格分隔。 在这种情况下,这是 axios
。 最后,使用可选的 --save
参数结束命令,该参数指定 axios
将保存为项目依赖项。
安装库后,您将看到类似于以下内容的输出:
Output... + axios@0.19.0 added 5 packages from 8 contributors and audited 5 packages in 0.764s found 0 vulnerabilities
现在,使用您选择的文本编辑器打开 package.json
文件。 本教程将使用 nano
:
nano package.json
您将看到一个新属性,如下所示:
定位器/package.json
{ "name": "locator", "version": "1.0.0", "description": "Finds the country of origin of the incoming request", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "ip", "geo", "country" ], "author": "Sammy sammy@your_domain (https://your_domain)", "license": "ISC", "dependencies": { "axios": "^0.19.0" } }
--save
选项告诉 npm 使用刚刚安装的模块和版本更新 package.json
。 这很棒,因为在您的项目上工作的其他开发人员可以很容易地看到需要哪些外部依赖项。
注意:您可能已经注意到axios
依赖的版本号之前的^
。 回想一下语义版本控制由三个数字组成:MAJOR、MINOR 和 PATCH。 ^
符号表示任何更高的 MINOR 或 PATCH 版本都将满足此版本约束。 如果您在版本号的开头看到 ~
,则只有更高的 PATCH 版本才能满足约束。
查看完 package.json
后,退出文件。
开发依赖
用于项目开发但不用于在生产中构建或运行的包称为 开发依赖项 。 它们不是您的模块或应用程序在生产中工作所必需的,但在编写代码时可能会有所帮助。
例如,开发人员通常使用 code linter 来确保他们的代码遵循最佳实践并保持风格一致。 虽然这对开发很有用,但这只会增加可分发的大小,而在生产中部署时不会提供切实的好处。
安装 linter 作为项目的开发依赖项。 在你的 shell 中试试这个:
npm i eslint@6.0.0 --save-dev
在此命令中,您使用了 --save-dev
标志。 这会将 eslint
保存为仅用于开发的依赖项。 另请注意,您将 @6.0.0
添加到依赖项名称中。 当模块更新时,它们会被标记为版本。 @
告诉 npm 查找您正在安装的模块的特定标签。 如果没有指定标签,npm 会安装最新的标签版本。 再次打开package.json
:
nano package.json
这将显示以下内容:
定位器/package.json
{ "name": "locator", "version": "1.0.0", "description": "Finds the country of origin of the incoming request", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "ip", "geo", "country" ], "author": "Sammy sammy@your_domain (https://your_domain)", "license": "ISC", "dependencies": { "axios": "^0.19.0" }, "devDependencies": { "eslint": "^6.0.0" } }
eslint
已保存为 devDependencies
,以及您之前指定的版本号。 退出 package.json
。
自动生成的文件:node_modules
和package-lock.json
当您第一次将包安装到 Node.js 项目时,npm 会自动创建 node_modules
文件夹来存储您的项目所需的模块和您之前检查过的 package-lock.json
文件。
确认这些在您的工作目录中。 在您的 shell 中,输入 ls
并按 ENTER
。 您将观察到以下输出:
Outputnode_modules package.json package-lock.json
node_modules
文件夹包含项目的所有已安装依赖项。 在大多数情况下,您应该 而不是 将此文件夹提交到您的版本控制存储库中。 随着您安装更多依赖项,此文件夹的大小将迅速增长。 此外,package-lock.json
文件以更简洁的方式记录了安装的确切版本,因此不需要包含 node_modules
。
虽然 package.json
文件列出了告诉我们应该为项目安装的合适版本的依赖项,但 package-lock.json
文件跟踪 package.json
或 [ 中的所有更改X187X] 并告诉我们安装的软件包的确切版本。 您通常将它提交到您的版本控制存储库而不是 node_modules
,因为它是所有依赖项的更清晰表示。
从 package.json 安装
使用您的 package.json
和 package-lock.json
文件,您可以在开始开发新项目之前快速设置相同的项目依赖项。 为了证明这一点,在目录树中向上移动一个级别,并在与 locator
相同的目录级别中创建一个名为 cloned_locator
的新文件夹:
cd .. mkdir cloned_locator
进入你的新目录:
cd cloned_locator
现在将 package.json
和 package-lock.json
文件从 locator
复制到 cloned_locator
:
cp ../locator/package.json ../locator/package-lock.json .
要安装此项目所需的模块,请键入:
npm i
npm 将检查 package-lock.json
文件以安装模块。 如果没有可用的锁定文件,它将从 package.json
文件中读取以确定安装。 从 package-lock.json
安装通常更快,因为锁定文件包含模块的确切版本及其依赖项,这意味着 npm 不必花时间找出合适的版本来安装。
部署到生产环境时,您可能希望跳过开发依赖项。 回想一下,开发依赖项存储在 package.json
的 devDependencies
部分中,并且对您的应用程序的运行没有影响。 在部署应用程序的 CI/CD 过程中安装模块时,通过运行以下命令省略开发依赖项:
npm i --production
--production
标志在安装期间忽略 devDependencies
部分。 现在,坚持你的开发版本。
在进入下一部分之前,请返回 locator
文件夹:
cd ../locator
全球安装
到目前为止,您一直在为 locator
项目安装 npm 模块。 npm 还允许您安装包 全局 。 这意味着该软件包在更广泛的系统中可供您的用户使用,就像任何其他 shell 命令一样。 此功能对于作为 CLI 工具的许多 Node.js 模块很有用。
例如,您可能想在博客上介绍您当前正在处理的 locator
项目。 为此,您可以使用 Hexo 之类的库来创建和管理您的静态网站博客。 像这样全局安装 Hexo CLI:
npm i hexo-cli -g
要全局安装软件包,请将 -g
标志附加到命令中。
注意:如果尝试全局安装此软件包时出现权限错误,您的系统可能需要超级用户权限才能运行该命令。 用 sudo npm i hexo-cli -g
再试一次。
通过键入以下内容测试软件包是否已成功安装:
hexo --version
您将看到类似于以下内容的输出:
Outputhexo-cli: 2.0.0 os: Linux 4.15.0-64-generic linux x64 http_parser: 2.7.1 node: 10.14.0 v8: 7.6.303.29-node.16 uv: 1.31.0 zlib: 1.2.11 ares: 1.15.0 modules: 72 nghttp2: 1.39.2 openssl: 1.1.1c brotli: 1.0.7 napi: 4 llhttp: 1.1.4 icu: 64.2 unicode: 12.1 cldr: 35.1 tz: 2019a
到目前为止,您已经学习了如何使用 npm 安装模块。 您可以将包安装到本地项目中,作为生产或开发依赖项。 您还可以基于预先存在的 package.json
或 package-lock.json
文件安装软件包,允许您使用与同行相同的依赖项进行开发。 最后,您可以使用 -g
标志全局安装包,因此无论您是否在 Node.js 项目中,您都可以访问它们。
现在您可以安装模块了,在下一节中,您将练习管理依赖项的技术。
第 3 步 — 管理模块
一个完整的包管理器可以做的不仅仅是安装模块。 npm 有超过 20 条与依赖管理相关的可用命令。 在此步骤中,您将:
- 列出您已安装的模块。
- 将模块更新到更新的版本。
- 卸载不再需要的模块。
- 对您的模块执行安全审计以查找和修复安全漏洞。
虽然这些示例将在您的 locator
文件夹中完成,但所有这些命令都可以通过在它们末尾附加 -g
标志来全局运行,就像您在全局安装时所做的一样。
列出模块
如果您想知道项目中安装了哪些模块,使用 list
或 ls
命令会更容易,而不是直接读取 package.json
。 为此,请输入:
npm ls
你会看到这样的输出:
Output├─┬ axios@0.19.0 │ ├─┬ follow-redirects@1.5.10 │ │ └─┬ debug@3.1.0 │ │ └── ms@2.0.0 │ └── is-buffer@2.0.3 └─┬ eslint@6.0.0 ├─┬ @babel/code-frame@7.5.5 │ └─┬ @babel/highlight@7.5.0 │ ├── chalk@2.4.2 deduped │ ├── esutils@2.0.3 deduped │ └── js-tokens@4.0.0 ├─┬ ajv@6.10.2 │ ├── fast-deep-equal@2.0.1 │ ├── fast-json-stable-stringify@2.0.0 │ ├── json-schema-traverse@0.4.1 │ └─┬ uri-js@4.2.2 ...
默认情况下,ls
显示整个依赖树——你的项目依赖的模块和你的依赖依赖的模块。 如果您想对已安装的内容进行高级概述,这可能有点笨拙。
要仅打印您安装的没有依赖关系的模块,请在 shell 中输入以下内容:
npm ls --depth 0
您的输出将是:
Output├── axios@0.19.0 └── eslint@6.0.0
--depth
选项允许您指定要查看的依赖关系树的级别。 当它是 0
时,您只能看到您的顶级依赖项。
更新模块
让你的 npm 模块保持最新是一个很好的做法。 这提高了您获得模块最新安全修复程序的可能性。 使用 outdated
命令检查是否可以更新任何模块:
npm outdated
您将获得如下输出:
OutputPackage Current Wanted Latest Location eslint 6.0.0 6.7.1 6.7.1 locator
此命令首先列出已安装的 Package
和 Current
版本。 Wanted
列显示哪个版本满足您在 package.json
中的版本要求。 Latest
列显示已发布的模块的最新版本。
Location
列说明了包在依赖树中的位置。 outdated
命令具有 --depth
标志,如 ls
。 默认情况下,深度为 0。
看来您可以将 eslint
更新到更新的版本。 像这样使用 update
或 up
命令:
npm up eslint
该命令的输出将包含已安装的版本:
Outputnpm WARN locator@1.0.0 No repository field. + eslint@6.7.1 added 7 packages from 3 contributors, removed 5 packages, updated 19 packages, moved 1 package and audited 184 packages in 5.818s found 0 vulnerabilities
如果您想一次更新所有模块,那么您将输入:
npm up
卸载模块
npm uninstall
命令可以从你的项目中移除模块。 这意味着该模块将不再安装在 node_modules
文件夹中,也不会出现在您的 package.json
和 package-lock.json
文件中。
从项目中删除依赖项是软件开发生命周期中的正常活动。 依赖项可能无法像宣传的那样解决问题,或者可能无法提供令人满意的开发体验。 在这些情况下,最好卸载依赖项并构建自己的模块。
想象一下,axios
没有提供您希望发出 HTTP 请求的开发体验。 使用 uninstall
或 un
命令卸载 axios
,方法是输入:
npm un axios
您的输出将类似于:
Outputnpm WARN locator@1.0.0 No repository field. removed 5 packages and audited 176 packages in 1.488s found 0 vulnerabilities
它没有明确说明 axios
已被删除。 要验证它是否已卸载,请再次列出依赖项:
npm ls --depth 0
现在,我们只看到安装了 eslint
:
Output└── eslint@6.7.1
这说明你已经成功卸载了axios
包。
审计模块
npm 提供了一个 audit
命令来突出显示依赖项中的潜在安全风险。 要查看实际的审计,请通过运行以下命令来安装 request 模块的过时版本:
npm i request@2.60.0
当您安装此过时版本的 request
时,您会注意到类似于以下内容的输出:
Output+ request@2.60.0 added 54 packages from 49 contributors and audited 243 packages in 7.26s found 6 moderate severity vulnerabilities run `npm audit fix` to fix them, or `npm audit` for details
npm 告诉您,您的依赖项中存在漏洞。 要获取更多详细信息,请通过以下方式审核您的整个项目:
npm audit
audit
命令显示突出显示安全漏洞的输出表:
Output === npm audit security report === # Run npm install request@2.88.0 to resolve 1 vulnerability ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ Moderate │ Memory Exposure │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ tunnel-agent │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ request │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ request > tunnel-agent │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://npmjs.com/advisories/598 │ └───────────────┴──────────────────────────────────────────────────────────────┘ # Run npm update request --depth 1 to resolve 1 vulnerability ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ Moderate │ Remote Memory Exposure │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ request │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ request │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ request │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://npmjs.com/advisories/309 │ └───────────────┴──────────────────────────────────────────────────────────────┘ ...
您可以看到漏洞的路径,有时 npm 会为您提供修复它的方法。 您可以按照建议运行更新命令,也可以运行 audit
的 fix
子命令。 在您的外壳中,输入:
npm audit fix
您将看到与以下类似的输出:
Output+ request@2.88.0 added 19 packages from 24 contributors, removed 32 packages and updated 12 packages in 6.223s fixed 2 of 6 vulnerabilities in 243 scanned packages 4 vulnerabilities required manual review and could not be updated
npm 能够安全地更新其中的两个包,从而将您的漏洞减少相同的数量。 但是,您的依赖项中仍有四个漏洞。 audit fix
命令并不总能解决所有问题。 尽管模块的某个版本可能存在安全漏洞,但如果您将其更新为具有不同 API 的版本,则它可能会破坏依赖关系树中更高位置的代码。
您可以使用 --force
参数来确保漏洞消失,如下所示:
npm audit fix --force
如前所述,除非您确定它不会破坏功能,否则不建议这样做。
结论
在本教程中,您完成了各种练习来演示如何将 Node.js 模块组织成包,以及这些包是如何由 npm 管理的。 在 Node.js 项目中,您通过创建和维护一个 package.json
文件来使用 npm 包作为依赖项 - 一个项目元数据的记录,包括您安装的模块。 除了列出项目的依赖关系树以及检查和更新过时的模块之外,您还使用 npm CLI 工具安装、更新和删除模块。
将来,通过使用模块来利用现有代码将加快开发时间,因为您不必重复功能。 您还可以创建自己的 npm 模块,而这些模块又将由其他人通过 npm 命令进行管理。 至于接下来的步骤,通过安装和测试各种各样的包来试验你在本教程中学到的东西。 看看生态系统提供了什么让问题解决变得更容易。 例如,您可以试用 JavaScript 的超集 TypeScript,或者使用 Cordova 将您的网站变成移动应用程序。 如果您想了解有关 Node.js 的更多信息,请参阅我们的 其他 Node.js 教程 。