首页 > 编程笔记

NPM包管理器简介(非常详细)

早期的浏览器 JavaScript 中并不存在标准库的概念,甚至于在设计 JavaScript 时连模块化的概念都没有,JavaScript 语言无非是嵌入在浏览器里的一小段代码片段,整个 JavaScript 语言只包含一些基础原生对象和类型,更多的对象和 API 取决于其所在的浏览器,在 JavaScript 里只能调用浏览器已经封装好的接口。

因此,模块化在当时的 JavaScript 并没有什么用处和价值,所以,我们可以看到在 Node.js 之前 JavaScript 缺少这些功能。

具体来说就是:
显然致力于将 JavaScript 应用到服务器上的 Node.js 不能接受这样的 JavaScript 现状,而解决上面这一系列问题的是 CommonJS(http://www.commonjs.org)规范的出现。

CommonJS 制定了解决上面这些问题的一系列标准规范,其目标是构建 JavaScript 在包括 Web 服务器、桌面、命令行工具及浏览器在内的各方面的统一生态系统,而Node.js就是这些规范的一种实现,Node.js 自身实现了 require 方法来引入各种模块

Node.js 通过 export 方法在模块内部向外暴露接口,然后通过 require 方法来加载模块。

NMP 是什么

虽然 Node.js 内置的标准库已经覆盖了网络操作所需要的绝大部分功能,但是标准库仍然过于初级,并且不能满足所有的编程需要,所以有必要引入第三方库。

而 Node.js 包管理器(Node.js Package Manager,NPM)就是第三方应用库的管理工具,其官网地址为:https://www.npmjs.com/

JavaScript 的流行多少要归功于 NPM,如果没有 NPM 管理数量众多的开源软件包,也许就不会有现在的 JavaScript 与 Node.js。由 NPM 来管理和分发软件包,极大地方便了 Node.js 软件的管理,特别是海量的开源 Node.js 软件的管理。

NPM 的思想或多或少来源于 Java 世界的 Jar 包管理器,包括 Apache Maven。NPM 几乎可以说是继 GitHub 之后最成功的开源软件管理系统。

在 NPM 的网站上,用户不仅可以搜索所需要的包,而且可以查看包的流行程度和下载量,还可以给喜欢的包加上星号。当然用户也可以发布自己的包。要设计自己的包,我们首先需要了解包结构。

包结构

浏览器时代的 JavaScript 缺少模块结构,更进一步的浏览器时代的 JavaScript 也缺少包管理结构。在 JavaScript 中,包的概念是一些 JavaScript 程序的集合,相类比的是 Java 中的 Jar 包,一个 Jar 包包含了多个 class 文件。

Node.js 作为服务器端程序自然也致力于改变这种现状。

Node.js 遵循 CommonJS 标准,而 CommonJS 标准已经定义了包的结构规范(http://wiki.commonjs.org/wiki/Packages/1.0)。NPM 则是在 CommonJS 规范的基础上,实现解决包的安装与卸载、依赖管理、版本管理等问题。

一个符合 CommonJS 规范的 NPM 包应该是如下这种目录结构:
Node.js 在加载程序过程中没有找到目标文件时,会将当前目录当作一个包来尝试加载,所以在 package.json 文件中,最重要的一个字段是 main。

实际上,main 字段是 Node.js 的扩展,CommonJS 标准定义中并不包含此字段。

对于 Node.js 的 require 方法,只需要 main 属性即可。但是在除此之外包需要接受安装、卸载、依赖管理、版本管理等流程,所以 CommonJS 为 package.json 文件定义了如下一些必需的字段。

字段 说明
name 包名,在 NPM 上是唯一的,不能有空格。
description 包简介,通常会显示在一些列表中。
version 版本号,是一个语义化的版本号(http://semver.org),通常为 x.y.z。该版本号十分重要,常常用于一些版本控制的场合。
keywords 关键字数组,用于 NPM 中的分类搜索。
maintainers 包维护者的数组,数组元素是一个包含 name、email、web 三个属性的 JSON 对象。
contributors 包贡献者的数组,其中第一个元素是包的作者。

在开源社区,如果提交的“补丁”被合并进主分支的话,就应当加上这个贡献“补丁”的人,格式包含 name 和 email,例如:
"contributors": [
    { "name": "Jackson Tian", "email": "mail @gmail.com"},
    { "name": "fengmk2", "email": "mail2@gmail.com"}
]
bugs 一个可以提交 bug 的 URL 地址,可以是邮件地址(mailto: mailxx@domain),也可以是网页地址(http://url)。
licenses 包所使用的许可证,例如:
"licenses": [ { "type": "GPLv2", "url":"http://www.example.com/licenses/gpl.html"} ]
repositories 托管源代码的地址数组。
dependencies 当前包需要的依赖。这个属性十分重要,NPM 会通过这个属性自动加载依赖的包。

以下是 Express 框架的 package.json 文件,值得参考:
{
    "name": "express",
    "description": "Sinatra inspired web development framework",
    "version": "3.0.0alpha1-pre",
    "author": "TJ Holowaychuk",
    ......
}
除了前面提到的几个必选字段外,我们还发现了一些额外的字段,如 bin、scripts、engines、devDependencies、author,这里重点提及一下 scripts 字段。

NPM 在对包进行安装或者卸载的时候需要进行一些编译或者清除的工作,scripts 字段的对象指明了在进行操作时运行哪个文件,或者执行哪条命令。

以下为一个较全面的 scripts 案例:
"scripts": {
    "install": "install.js",
    "uninstall": "uninstall.js",
    "build": "build.js",
    "doc": "make-doc.js",
    "test": "test.js",
    "start": "index.js"
}

包管理

如果你完善了自己的 JavaScript 库,使之实现了 CommonJS 的包规范,那么你可以通过 NPM 来将自己的包发布到 NPM 网站上,这样别人就可以通过 NPM 网站搜索并使用你的包了。

npm publish <folder>

命令十分简单。但是在这之前需要通过 npm adduser 命令在 NPM 上注册一个账户,以便后续包的维护。

NPM 会分析该文件夹下的 package.json 文件,然后上传目录到 NPM 的站点。用户在使用该包时,也十分简明:

npm install <package>

甚至对于 NPM 无法安装的包(因为某些网络原因),可以通过 GitHub 手动下载其稳定版本,解压之后通过以下命令进行安装:

npm install <package.json folder>

只需将路径指向 package.json 所在的目录即可。然后在代码中通过require('package')即可使用。

如果想把某个包安装到 Node.js 的全局环境里,可以使用:

npm install -g <package>


除了常用的 install 之外,还有下面这些常用命令:

【拓展阅读】 一个名字引发的“血案”——NPM 删库事件

2016 年 3 月 23 日,NPM 发生了一次严重的库依赖被删除事件,事件起因是对一个名为 KIK 包的命名权的纠纷。

某家公司发律师函电子邮件给 KIK 包的作者,声称 KIK 这个名字是他们的注册商标,KIK 包作者未理会他们的警告。接着 NPM 官方介入,修改了 KIK 包的名字。

由于这一行为没有和 KIK 包作者沟通,引起了 KIK 包作者的强烈不满。

23 日凌晨,KIK 包作者决定撤出 NPM,取消发布自己的所有模块,这些模块一共有 273 个,其中包括被广泛使用的 left-pad,导致 Babel、ReactNative、Ember 等大量工具构建失败。这几乎使全球所有的 JavaScript 开发者在那一天停摆。

该事件不仅造成大量依赖 left-pad 的软件重新修改代码,发布不依赖 left-pad 的版本,而且促使 NPM 修改包发布规则,以防止类似事件再次发生。

推荐阅读