你、我和 package.json
?? 西班牙语?查看以下文章的翻译版本:Tú, yo, y package.json
如果你曾经参与过 Node.js 或客户端 JavaScript 项目,那么你很可能见过一个名为package.json的文件,并且仔细研究过它的内容。尽管里面有很多你可能非常熟悉的内容,但你可能遇到过一些你不太清楚其含义,甚至不知道它们最初是如何出现的。
在本文中,我们将详细介绍该文件中每个内容的含义、您可以在其中放入哪些内容以及它如何帮助您在开发项目时提高工作效率。
但首先要做的事情是:
package.json 文件是什么?
从历史上看,Node 一直使用名为npm的工具来管理包和依赖项。此工具通常与 Node 一起安装,主要有两个作用:
- 将您的项目发布到 npm 的公共注册表(以便其他用户可以将其下载为他们的项目的依赖项)。
- 管理您自己项目的依赖项。
为了实现这一点,npm 客户端创建并使用了一个名为package.json的文件。该文件包含以下项目信息:
- 姓名。
- 版本。
- 依赖项。
- 存储库。
- 作者。
- 执照。
还有更多。
此外,除了保存记录之外,使用此文件还可以保证每个获得副本的人的项目完整性。这意味着任何用户在任何时间点都能够访问同一组类似的兼容依赖项。在某种程度上,我们可以将package.json文件视为我们项目的宣言。这里要记住的一件事是,虽然 package.json 文件中列出的依赖项应该与原始依赖项相似且兼容,但如果自原始声明以来已经过去了相当长的时间,则不能保证项目能够毫无问题地运行(可能是在不同版本的包中引入了更改,这些更改也被认为是兼容的,但可能会破坏某些功能)。为此,建议使用锁文件。
让我们通过考虑以下场景来看一个例子:
两位开发人员正在开发同一个项目,并在各自的电脑上分别拥有独立的副本。开发人员 #1 决定,为了完成新功能,他需要在项目中使用一个新库。
如果没有任何依赖管理,他需要做以下两件事之一:
- 手动将库下载到项目目录中,并在其中保留一份副本,该副本必须包含在项目存储的任何地方,这可能会增加新开发人员获取副本时需要传输的数据量。
- 下载库的副本但不保留副本,但让每个参与该项目的人(现在和将来)都知道他们需要获取副本,并确保他们使用完全相同的版本。(结交新朋友的好方法,但在时间管理方面不太好)。
有了依赖管理工具(例如 npm),这些步骤就都不再需要了。任何获得项目副本的人(只要软件包尚未取消发布)都可以立即安装所有依赖项,而无需传输它们的实际副本。因此,存储在存储库中并共享的实际项目会轻量很多,并且无需传输冗余数据。
值得一提的是,尽管 package.json 文件中包含的许多信息似乎特定于在 npm 注册表中发布项目,但我们仍然可以使用 npm 来管理永远不会在其中发布的其他类型的项目,例如 Web 和/或移动应用程序、游戏等。
关于依赖管理的最后一点说明,不久前,我在 Facebook 的好朋友(注意:他们实际上并不知道我们是朋友……然而 :())推出了一个类似的工具,称为yarn,就本文的所有意图和目的而言,它能够执行我们上面提到的相同的两个任务,并且它对 package.json 文件的使用是相同的,除非明确说明。
如何创建 package.json 文件
一条规则,给它们全部响铃(?)
在创建 package.json 文件之前,需要了解一条规则:该文件必须采用有效的 JSON 格式,并且必须遵守JSON 样式规范。
考虑到这一点,有两种不同的方法来创建文件:手动或使用 npm/yarn cli:
手动创建 package.json
如果由于任何原因使用 npm/yarn cli 的选项不可用并且我们确实需要手动创建文件,则我们需要在项目根目录中添加一个包含以下字段的新文件(名为package.json) :
name
。version
。
尽管建议使用,但其他每个字段(在下一节中列出)都是可选的。
使用 npm/yarn cli 创建 package.json
这是推荐的方法。您可以在项目根目录下运行以下命令(取决于您使用的包管理器)来创建 package.json 文件:
npm init
或者
yarn init
根据使用的是 npm 还是 yarn,在创建文件之前必须提供某些信息:
完成后,将在项目根目录中创建一个全新的package.json文件。
快速提示:如果您需要快速创建具有默认值的 package.json 文件,您可以运行:
npm init -y
或者
yarn init -y
package.json 文件的部分
创建 package.json 文件后(手动或使用命令行),我们会发现里面有一个包含不同键和值的大对象(就像本文开头的图片一样)。此外,随着时间的推移,新的依赖项/配置也会被添加进来,新的键和值也会随之添加。以下列出了一些我们可能在某个时间点遇到的最常见的键和值:
注意:此列表仅包含 npm 官方声明和支持的属性。有多个外部库也支持从 package.json 文件中读取键(例如 Jest 和属性“jest”)。
姓名
这是文件中必须包含的两个字段之一(与版本号一起)。它是一个字符串,代表当前项目的名称,并且在项目发布到注册表中时用作唯一标识符。
规则:
- 名称必须小写,并且不能以句点或下划线开头。
- 名称的最大长度为 214 个字符,并且必须是 URL 安全的(有关 URL 安全字符的更多信息可以在此处找到,第 2.3 节)。
还有几件事需要记住:
- 如果项目将在 npm 注册表中发布,则名称必须是唯一且可用的(之前没有其他项目使用相同的名称发布)。
- 尽管如果包属于某种技术,使用相关名称被认为是一种很好的做法(例如使用react-{something}作为 React 库),但也建议不要在名称中使用node或js 。
版本
另一个必填字段,与名称一起。它是一个字符串,指示项目的当前版本。Node.js 和 JavaScript 项目通常遵循语义版本控制(semver)中定义的约定,该约定定义了以下版本结构:
MAJOR.MINOR.PATCH
描述
简短说明项目内容的字符串。如果该软件包发布,此文本也会与搜索结果相关。
关键词
与描述相同,但不是文本,而是包含可用于搜索包的相关术语的字符串数组。
主页
包含项目网站有效 URL 的字符串。
错误
一个包含有效 URL 的字符串,用户可以在此报告项目中发现的问题。通常,使用问题存储库 URL。
执照
用于指定项目发布许可证类型的字符串。许可证类型可以是个人许可证、商业许可证、开放许可证或私有许可证。
作者
它可以是字符串,也可以是包含项目创建者信息的对象。
如果是对象,必须采用以下格式:
- 姓名。
- 电子邮件。
- 网址。
如果它是一个字符串:
"Name <email> (URL)"
贡献者
与作者类似,它是一个对象数组(或字符串数组),包含项目贡献者的信息。
文件
一个字符串或模式数组(例如 *.js),其中包含在项目发布到注册表后将被包含的文件。如果此部分未定义,则所有文件(未在 .gitignore 等文件中明确排除的文件)都将包含在内。
关于这一点,需要记住以下几点:
- 默认情况下, .gitignore中列出的每个文件都将被排除在发布之外。
- 无需添加文件部分,而是可以在项目根目录中包含一个.npmignore文件,其中包含要排除在发布之外的文件列表(类似于 .gitignore 的作用)。
- 某些文件始终会被包含,无论是否明确排除。这些文件包括:package.json、README、CHANGES / CHANGELOG / HISTORY、LICENSE / LICENCE、NOTICE 以及定义为应用程序入口点的文件(更多内容请参阅下一节)。
- 某些文件无论是否明确包含,都将始终被忽略。这些文件的列表可在此处找到。
主要的
定义项目入口点的字符串。如果项目是一个包/库,则每当有人需要它时,都会导入该文件。例如:
如果您的项目名为super-awesome-library,并且用户安装了它,然后在他们的应用程序中执行以下操作:
const superAwesomeLibrary = require("super-awesome-library");
superAwesomeLibrary变量将包含主文件导出的内容,因此如果您的 package.json 文件有如下声明:
{
"main": "lib/foo.js"
}
那么superAwesomeLibrary
变量将包含在中导出的内容lib/foo.js
。
index.js
如果省略此部分,则将使用位于项目根目录中的文件的内容。
垃圾桶
一个字符串(如果只有一个)或一个对象(如果有多个),定义将被安装并可作为 PATH 中的命令使用的脚本。软件包安装完成后,将创建一个从/usr/local/bin到项目内相应文件的符号链接,并将其作为命令行程序使用。
例如,假设我们的项目中有一个名为cli.js的文件,我们希望用户可以直接从终端调用它。实现方法是在 package.json 中包含一个字符串bin ,如下所示:
{
"name": "super-awesome-library",
"bin": "cli.js"
}
现在可以通过在终端中运行我们输入的项目名称来使用cli.js的内容:
super-awesome-library
当用户运行这个友好名称时,实际上,“幕后”正在发生类似这样的事情:
node cli.js
然后该文件上的任何内容都将被运行。
如果我们有多个文件需要转换成可执行脚本,可以使用对象格式。这将为每个键值对添加一个符号链接,并使用键作为之后可用的命令:
{
"bin": {
"script-1": "super-h4x0r-script1.js",
"script-2": "on-your-left.js"
}
}
有了该对象,“script-1”和“script-2”都将包含在 PATH 中,每个都指向 bin 对象内的相应 .js 文件。
许多已知的软件包(例如nodemon或react-native )都包含此功能,因此我们可以直接将它们用作终端命令,而无需运行node regardless-the-path-to-the-file-is。
男人
一个字符串或一个字符串数组,定义一个(或多个)文件,如果为该项目运行man命令,则这些文件将可用/显示。
目录
定义项目结构以及每个文件夹在特定部分的位置的对象。最常见的文件夹是bin、doc、example、lib、man和test。
{
"bin": "./bin",
"doc": "./doc",
"lib": "./lib"
}
存储库
一个对象,定义此项目的存储位置,并可在其中找到以供贡献。该对象的格式如下:
{
"type": string,
"url": string
}
其中type指的是存储库的类型(例如 svn 或 git),URL是可以找到存储库的有效 URL。
例子:
{
"type": "git",
"url": "https://github.com/my-user/super-awesome-project"
}
脚本
定义可与项目的 npm/yarn cli 一起使用的命令的对象。某些脚本是预定义和保留的,无需定义即可使用,例如start、install、preinstall、pretest、test 和 posttest等。 (完整列表可在此处找到)。
同样,我们可以定义自己的脚本,并使用自定义的名称和指令。这对于创建快捷方式和/或组合任务非常有用,无需每次都记住完整的命令和/或参数。
例如,假设我们有一个应用需要在创建新版本之前运行一个任务来压缩 JS 文件,我们使用位于tasks/minify.js中的脚本并传递一个内部使用的标志或参数来实现。通常,每次我们想要实现这一点时,我们都会运行node tasks/minify.js --someflag --maybeanother (并且我们还需要记住标志的名称)。但是,如果我们将其添加到 npm scripts 中,我们可以执行以下操作:
"scripts": {
"minify": "node tasks/minify.js --someflag --maybeanother"
}
然后运行:
npm run minify
这实现了完全相同的结果。这样做的好处在于,我们不仅不必记住每次需要运行的具体命令,而且 npm scripts 可以组合并按顺序执行,因此我们可以创建复杂的任务,甚至在使用任何pre hooks (例如 pretest 或 prepublish)时自动触发某些任务。
例如,假设我们想在运行应用测试之前运行同样的 minify 任务,并通过 linter 测试代码。为此,我们可以添加如下内容(假设我们的应用代码位于src文件夹中):
"scripts": {
"pretest": "node tasks/minify.js --someflag --maybeanother && eslint src"
}
或者我们可以将其直接作为测试脚本的一部分(此示例使用 jest,但您可以将其替换为 mocha/ava/tape/etc 或您选择的工具):
"scripts": {
"test": "node tasks/minify.js --someflag --maybeanother && eslint src && jest"
}
最后需要注意的是,这些脚本需要通过npm run 'script'来运行,除非它们是 npm 预定义/保留的脚本之一(列在本节开头)。但是,如果您使用的是 yarn,则可以完全省略run部分,只需执行yarn 'script'即可,无论它是否是预定义脚本。
配置
一个对象,其中的值可以设置为环境变量,稍后可以从代码中访问。
要设置配置值,我们可以在 package.json 文件中执行此操作:
{
"name": "my-app",
"config": {
"port": 8000
}
}
然后,可以使用process.env.npm_package_config_{value}从代码中引用值,如下所示:
const express = require('express');
const app = express();
const port = process.env.npm_package_config_port;
app.get('/', (req, res) => res.send('Hello!'));
app.listen(port, () => console.log(`App listening on port ${port}!`));
可以随时通过执行以下命令从 package.json 文件外部更改这些配置值:
npm config set {name of the project}:{config key} {config value}
对于我们之前的例子,我们可以这样做:
npm config set my-app:port 3000
依赖项
一个对象,用于存储项目历史中安装(并保存)的每个依赖项的名称和版本。每当有人获取此项目的新副本并运行npm install 时,所有这些依赖项都将被安装(使用最新的兼容版本)。这些依赖项以及接下来的两个类别采用以下格式定义:
"name-of-the-dependency": "(^|~|version)|url"
一些例子:
"dependencies": {
"backbone": "1.0.0",
"lodash": "^4.6.1",
"mocha": "~3.5.3",
"super-mega-library": "https://example.com/super-mega-library-4.0.0.tar.gz"
}
这些依赖项可以是已安装并保存的版本,也可以是可以获取当前版本的包的有效 URL (此 URL 也可以是同一台计算机内的本地路径)。
版本开头的 ^ 和 ~ 符号是什么?
每个安装的依赖项都可以有一个字符来定义可接受的兼容版本范围。这两个是最常见的,但完整列表可以在这里找到。
换句话说,这些字符添加了有关下次npm install时应如何处理此依赖项的指令:
- 如果版本号带有插入符号
(^)
:允许安装其他版本,只要只是小改动(版本号的第二个数字)。如果没有找到其他小版本,则将安装相同的版本。 - 如果版本号带有波浪号
(~)
:则允许安装其他版本,只要它只是补丁更改(版本号的最后一个数字)。如果没有发现其他补丁,则将安装相同的版本。 - 如果版本只有数字而没有字符:则必须安装定义的完全相同的版本。
例如,有了上面指定的依赖项,如果我们运行npm install并且有新版本可用:
- backbone和super-mega-library将保持使用相同的版本(分别为 1.0.0 和 4.0.0)。
- lodash可以安装相同的版本,也可以安装4.6.1到4.9.9之间的任何版本,但绝不会安装5.xx或更高版本的版本。
- mocha可以安装相同的版本,也可以安装3.5.3到3.5.9之间的任何版本,但绝不会安装高于该版本的版本。
开发依赖项
格式与上面列出的依赖项相同,但此部分将包含项目使用但生产环境不需要的所有依赖项(例如测试工具、本地开发服务器、优化工具等)。任何获取此项目副本并将生产设置为NODE_ENV变量的计算机都不会安装此部分列出的依赖项。
peerDependencies
它也使用相同的格式,但是这些依赖项虽然不一定安装,但它们定义了此应用程序/包正常运行所需的兼容性。例如,如果我们正在开发一个仅与 React 16 版本兼容的库,则需要执行以下操作:
"peerDependencies": {
"react": "16.0.0"
}
旧版本的 npm (1 和 2)会自动安装这些 peerDependencies,但现在不再如此。从 3 版本开始,如果在安装此项目时未找到兼容版本,则会触发警告。
发动机
一个对象,用于定义该项目支持的最低 Node 和 npm 版本。其定义格式如下:
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
项目安装时,会进行兼容性检查。如果不满足,安装过程将停止。
与依赖项的情况一样,我们可以使用范围(如、、**>=**
等)**^**
**~**
来定义兼容版本。
更多信息
虽然这些是我们在 package.json 文件中会找到和使用的最常见内容,但还有一些其他内容值得查看或参考。如果需要更多参考,我建议定期查看 npm 的官方文档,因为每当有新版本发布时,它都会不断更新。
有用的链接:
文章来源:https://dev.to/xabadu/you-me-and-package-json-1dc2