NPM 包描述文件

npm 即 npde package module

CommonJS 的包规范的定义由 包结构包描述文件 两个部分组成,前者用于组织包中的各种文件,后者则用于描述包的相关信息,以供外部读取分析。

包结构

包实际上是一个存档文件,即一个目录直接打包为 .zip.tar.gz 格式的文件,安装后解压还原为目录。完全符合 CommonJS 规范的包目录应该包含如下这些文件。

  • package.json:包描述文件
  • bin:用于存放二进制文件的目录
  • lib:用于存放 JavaScript 代码的目录
  • doc:用于存放文档的目录
  • test:用于存放单元测试用例的代码

包描述文件

包描述文件用于表达非代码相关的信息,它是一个 JSON 格式的文件,位于包的根目录下。NPM 的所有行为都与包描述文件的字段息息相关。

CommonJS 的包规范:

  • name:包名。由小写的字母和数字组成,可以包含 ._-,但不允许出现空格。
  • descript:包简介。
  • version:版本号。语义化的版本号,详细定义参看 http://semver.org
  • keywords:关键词数组,用于分类搜索。
  • maintainers:包维护者列表。每个维护者由 name、email 和 web 这三个属性组成。
  • contributors:贡献者列表。
  • bugs:用于反馈 BUG 的网页地址或邮件地址
  • licenses:许可证列表。
  • repositories:托管源代码的位置列表,表明可以通过哪些方式和地址访问包的源代码
  • dependencies:包所需要的依赖的包列表
  • devDependencies:开发时所需要的依赖列表。
  • peerDependencies

其他可选字段

  • homepage:当前包的网站地址
  • os:操作系统支持列表。
  • cpu:CPU 架构的支持列表
  • engine:支持的 JavaScript 引擎列表
  • builtin:标识当前包是否是内建在底层系统的标准组件
  • directories:包目录说明
  • implements:实现规范的列表
  • scripts:脚本说明对象。主要被包管理器用于安装、编译、测试和卸载包。

NPM 包规范额外的字段:

  • author:包作者
  • bin:一些包作者希望包可以作为命令行工具使用。配置好 bin 字段后,通过 npm install package_name -g 命令可以将脚本添加到执行路径中,之后可以在命令行中直接执行
  • main:模块引入方法 require() 在引入包的时,会优先检查这个字段,并将其作为保重其余模块的入口

必备属性

name

name 字段定义了模块的名称,其命名时需要遵循官方的一些规范和建议:

  • 模块名会成为模块 URL、命令行中的一个参数或者一个文件夹名称,任何非 URL 安全的字符在模块名中都不能使用(我们可以使用 validate-npm-package-name 包来检测模块名是否合法);
  • 语义化模块名,可以帮助开发者更快的找到需要的模块,并且避免意外获取错误的模块;
  • 若模块名称中存在一些符号,将符号去除后不得与现有的模块名重复,例如:由于 react-router-dom 已经存在,react.router.domreactrouterdom 都不可以再创建。

name 字段不能与其他模块名重复,我们可以执行以下命令查看模块名是否已经被使用:

npm view <package-name>
  • 如果模块存在,可以查看该模块的基本信息
  • 如果该模块名称未被使用过,则会抛出 404 错误

version

npm 包中的模块版本都需要遵循 SemVer 规范,相关介绍可查阅 npm 版本控制

描述信息

description & keywords

description 字段用于添加模块的描述信息,便于用户了解该模块。

keywords 字段用于给模块添加关键字。

当我们使用 npm 检索模块时,会对模块中的 description 字段和 keywords 字段进行匹配,写好 package.json 中的 descriptionkeywords 将有利于增加我们模块的曝光率。

author

author 字段用于描述主要作者。

contributors

contributors 字段用于描述贡献者。

homepage

homepage 字段用于描述项目的主页。

repository

repository 字段用于描述模块的代码仓库。

bugs

repository 字段用于描述提交 BUG 的地址。

依赖配置

dependencies

dependencies 字段指定了项目生产环境运行所依赖的模块(生产环境使用),如 antdreactmoment 等依赖库:

  • 它们是我们生产环境所需要的依赖项,在把项目作为一个 npm 包的时候,用户安装 npm 包时只会安装 dependencies 里面的依赖。

devDepenedencies

devDependencies 字段指定了项目开发所需要的模块(开发环境使用),如 webpacktypescriptbabel 等:

  • 在代码打包提交线上时,我们并不需要这些依赖包,所以我们将它放入 devDependencies 中。

peerDepenencies

peerDependencies 字段的目的是提示宿主环境去安装满足插件 peerDependencies 所指定依赖的包,然后在插件 import 或者 require 所依赖的包的时候,永远都是引用宿主环境统一安装的 npm 包,最终解决插件与所依赖包不一致的问题。

举个例子,就拿目前基于 React 的 UI 组件库 ant-design@4.x 来说,因该 UI 组件库只是提供一套 React 组件库,它要求宿主环境需要安装指定的 React 版本。具体可以看它 package.json 中的配置:

{
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
}

它要求宿主环境安装 react@>=16.9.0react-dom@>=16.9.0 的版本,而在每个 antd 组件的定义文件顶部:

import * as React from 'react';
import * as ReactDOM from 'react-dom';

组件中引入的 reactreact-dom 包其实都是宿主环境提供的依赖包。

有了 package.json 文件,开发直接使用 npm install / yarn install 命令,就会在当前目录中自动安装所需要的模块,安装完成项目所需的运行和开发环境就配置好了。

optionalDependencies

不阻断安装依赖

bundledDependencies

打包依赖

目录文件

main

main 字段是 package.json 中的另一种元数据功能,它可以用来指定加载的入口文件。假如你的项目是一个 npm 包,当用户安装你的包后,require('my-module') 返回的是 main 字段中所列出文件的 module.exports 属性。

当不指定 main 字段时,默认值是模块根目录下面的 index.js 文件。

bin

用过 vue-clicreate-react-app 等脚手架的朋友们,不知道你们有没有好奇过,为什么安装这些脚手架后,就可以使用类似 vue create/create-react-app 之类的命令,其实这和 package.json 中的 bin 字段有关。

bin 字段用来指定各个内部命令对应的可执行文件的位置。当 package.json 提供了 bin 字段后,即相当于做了一个命令名和本地文件名的映射。

当用户安装带有 bin 字段的包时,

  • 如果是全局安装,npm 将会使用符号链接把这些文件链接到 /usr/local/node_modules/.bin/
  • 如果是本地安装,会链接到 ./node_modules/.bin/

举个例子,如果要使用 my-app-cli 作为命令时,可以配置以下 bin 字段:

{
"bin": {
"my-app-cli": "./bin/cli.js"
}
}

上面代码指定,my-app-cli 命令对应的可执行文件为 bin 子目录下的 cli.js,因此在安装了 my-app-cli 包的项目中,就可以很方便地利用 npm 执行脚本:

{
"scripts": {
"start": "node node_modules/.bin/my-app-cli"
}
}

咦,怎么看起来和 vue create/create-react-app 之类的命令不太像?原因:

  • 当需要 node 环境时就需要加上 node 前缀
  • 如果加上 node 前缀,就需要指定 my-app-cli 的路径 -> node_modules/.bin,否则 node my-app-cli 会去查找当前路径下的 my-app-cli.js,这样肯定是不对。

若要实现像 vue create/create-react-app 之类的命令一样简便的方式,则可以在上文提到的 bin 子目录下可执行文件 cli.js 中的第一行写入以下命令:

#!/usr/bin/env node

这行命令的作用是告诉系统用 node 解析,这样命令就可以简写成 my-app-cli 了。

files

files 字段用于描述我们使用 npm publish 命令后推送到 npm 服务器的文件列表,如果指定文件夹,则文件夹内的所有内容都会包含进来。

我们可以查看下载的 antdpackage.jsonfiles 字段,内容如下:

{
"files": ["dist", "lib", "es"]
}

另外,我们还可以通过配置一个 .npmignore 文件来排除一些文件, 防止大量的垃圾文件推送到 npm 上。

man

directories

脚本配置

script

scripts 字段是 package.json 中的一种元数据功能,它接受一个对象,对象的属性为可以通过 npm run 运行的脚本,值为实际运行的命令(通常是终端命令),如:

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

将终端命令放入 scripts 字段,既可以记录它们又可以实现轻松重用。

config

发布配置

private

一般公司的非开源项目,都会设置 private 属性的值为 true,这是因为 npm 拒绝发布私有模块,通过设置该字段可以防止私有模块被无意间发布出去。

preferGlobal

publishConfig

os

假如我们开发了一个模块,只能跑在 darwin 系统下,我们需要保证 windows 用户不会安装到该模块,从而避免发生不必要的错误。

这时候,使用 os 属性则可以帮助我们实现以上的需求,该属性可以指定模块适用系统的系统,或者指定不能安装的系统黑名单(当在系统黑名单中的系统中安装模块则会报错):

{
// 适用系统
"os": ["darwin", "linux"],
// 黑名单
"os": ["!win32"]
}

Tips:在 node 环境下可以使用 process.platform 来判断操作系统。

cpu

和上面的 os 字段类似,我们可以用 cpu 字段更精准的限制用户安装环境:

{
// 适用 CPU
"cpu": ["x64", "ia32"],
// 黑名单
"cpu": ["!arm", "!mips"]
}

Tips:在 node 环境下可以使用 process.arch 来判断 cpu 架构。

engines

有时候,新拉一个项目的时候,由于和其他开发使用的 node 版本不同,导致会出现很多奇奇怪怪的问题(如某些依赖安装报错、依赖安装完项目跑步起来等)。

为了实现项目开箱即用的伟大理想,这时候可以使用 package.jsonengines 字段来指定项目 node 版本:

{
"engines": {
"node": ">=8.16.0"
}
}

该字段也可以指定适用的 npm 版本:

{
"engines": {
"npm": ">= 6.9.0"
}
}

需要注意的是,engines 属性仅起到一个说明的作用,当用户版本不符合指定值时也不影响依赖的安装。

协议

MIT Apache GPL

自定义字段

一些第三方 npm 包,会在 package.json 中定义字段。

例如 husky 等 pre-commit

参考资料