npm 是一个方便的 Node 模块分发、管理工具。咱们日常会使用 npm install
安装模块,使用 npm publish
发布模块。事实上除了基本功能,这 2 个命令还有其余很是有用的特性。这篇文章会给你们介绍这些命令的一些“高级”用法。javascript
咱们先来讲说版本号。npm 使用的是一种叫作 semantic version 的规范,它的规则很简单,总结起来就是下面几条:java
使用 semver 的软件必须定义公开、严谨、易于理解的 API。也就是模块要提供功能给用户。node
版本号格式为:X.Y.Z
,而且 X、Y、Z 均为正整数而且不断递增。X 表示大版本(major)、Y 表示小版本(minor)、Z 表示补丁版本(patch)。git
一个版本发布后,此版本内容不能再变动,变动必须再发布一个新版本。也就是不能覆盖发布。github
0.Y.Z
表示初始版本,这种版本下的 API 不能保证稳定,随时可能变动。npm
当进行了向后兼容的 bug 修复时,补丁版本 Z 必须增长。json
当引入了向后兼容的新功能时,小版本 Y 必须增长,同时 Z 必须重置为 0(小版本里面可能会包含 bug 修复)。promise
当引入了不兼容的变动时,大版本 X 必须增长,同时 Y、Z 必须重置为 0(大版本里面可能会包含小版本或者补丁版本的改动)。koa
X.Y.Z
后面还能够加预发布版本号、构建信息,格式为:X.Y.Z-pre_lease+build_meta
,好比:1.0.0-alpha+20151226
、1.0.0-beta.2+20151230
。工具
进行版本号比较时,遵循下面的规则:1)依次按数值比较 X、Y、Z 的值,直到第一个不一样的位置;2)若是两个版本的 X、Y、Z 都相等,含有 pre-release 版本号的较小;3)若是两个版本的 X、Y、Z 都相等而且都含有 pre-release 版本号,要单独比较 pre-release 版本。好比:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1
,1.0.0-alpha < 1.0.0
,1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2
。
在进行项目开发时,对于依赖的模块进行管理是很是必要的。这种管理主要体如今:
如何方便的获取项目须要使用的模块
当前项目依赖了哪些模块,或许还会指定模块依赖的环境(哪些模块是生产阶段依赖的、哪些是开发阶段依赖的)
模块的哪些版本是和当前项目兼容的,能够直接使用
其余成员(或者系统)如何方便的获取全部模块,从而能让项目正常运行
下面咱们看看 npm 是如何帮助咱们解决上面这些问题的。
咱们在进行项目开发时,若是须要某个功能,都会先去 npm registry 上搜一搜,看看有没有相似的模块能够直接复用,从而提升开发效率。获取模块很是简单,咱们在项目的根目录直接执行 npm install name
就能够将模块安装在 node_modules
目录下,而后直接 require
就可以使用。
上面获取模块的方法是一种“临时”起意的方式,并不能记录咱们依赖模块的具体信息(除非咱们本身去查找)。咱们安装模块时,能够执行 npm install name --save
(生产阶段的依赖) 或是 npm install name --save-dev
(开发阶段的依赖),来将模块信息保存到项目的 package.json
文件中。
执行 npm install name --save
来安装模块时,npm 会首先安装模块的最新版本,而后将模块名及模块版本号以最“保险”的表示方式写入到 package.json
文件中。
好比,咱们执行 npm install koa --save
,安装模块后,会更新package.json
文件的 dependencies
字段:
{ "dependencies": { "koa": "~1.1.0" } }
项目对模块的依赖可使用下面的 3 种方法来表示(假设当前版本号是 1.1.0 ):
兼容模块新发布的补丁版本:~1.1.0
、1.1.x
、1.1
兼容模块新发布的小版本、补丁版本:^1.1.0
、1.x
、1
兼容模块新发布的大版本、小版本、补丁版本:*
、x
能够看到,npm 默认会将依赖表示为最“保险”的方式(即:获取模块新发布的补丁版本),咱们也可使用 npm install koa --save-exact
来精确的指定模块版本(这种状况依赖就直接表示为:"koa": "1.1.0"
)。
除了获取模块的最新版本,咱们还能够精确获取模块的某个具体版本,如:npm install koa@1.0.0 --save
,这种状况依赖就会表示为:"koa": "~1.0.0"
。
模块的依赖都被写入了 package.json
文件,其余合做者(好比:其余小伙伴、或者是部署服务)只要进入项目的根目录,执行 npm install
就能够将依赖的模块所有安装到 node_modules
目录下。
好比下面的 package.json
:
{ "dependencies": { "bluebird": "~3.1.1", "request-promise": "^1.0.2", "lodash": "*" }, "devDependencies": { "mocha": "~2.3.4" } }
当咱们执行 npm install
时,就会安装 dependencies
及 devDependencies
字段里列出的全部模块。若是只想安装 dependencies
里面列出的模块,可使用 npm install --only=production
。
这里 npm 实际安装的模块是根据依赖的表示来决定的:
"bluebird": "~3.1.1"
表示会安装最新的补丁版本。好比:安装 3.1.二、3.1.3 等,可是不会安装 3.2.0 这种小版本或者4.0.0 这种大版本(就算这些版本是最新的)。这种表示法是比较保险的,由于补丁版本只是 bug 修复,不会新增功能。
"request-promise": "^1.0.2"
表示会安装最新的补丁版本或者小版本。好比:安装 1.1.0、1.1.1 等小版本,或者 1.0.三、1.0.4 等补丁版本,可是不会安装 2.0.0 这种大版本。并且,老是安装版本号最大的(也就是优先安装小版本)。这种表示法一般是保险的,小版本会增长向后兼容的功能。
"lodash": "*"
表示安装最新的版本(无论这个版本是大版本、小版本、仍是补丁版本)。这种表示法很是危险,若是有大版本,直接就安装了大版本,而大版本一般是不会向后兼容的,可能致使项目功能运行异常。
对于模块的提供者来讲,每一次发布都应该尽可能遵循 semver 的规范来变动版本号,不要让用户困惑甚至是想骂人......
在“模块的版本号”部分咱们介绍了 semver 规范,npm 也提供了相关的命令来让咱们方便的进行版本号变动:
升级补丁版本号:npm version patch
升级小版本号:npm version minor
升级大版本号:npm version major
一个比较合适的发布流程能够是:
根据这次变动执行 npm version [new version]
命令(npm 会根据 new version
指定的类型更新 package.json
中的 version
字段,而后进行一次 commit,最后打上一个该版本号的 tag)。
执行 git push origin master --tags
,将改动同步到远程代码仓库。
执行 npm publish
,将模块发布到 npm 仓库。
模块安装命令的最简形式 npm install name
的完整版其实应该是:npm install name@latest
。这里的 latest
是模块版本的一个 tag,会对应到模块的一个具体版本。
咱们来看一个例子:模块 koa 在 npm registry 上的信息以下(完整版见:http://registry.npmjs.com/koa):
{ "name": "koa", "dist-tags": { "latest": "1.1.2", "next": "2.0.0-alpha.3" }, "versions": { "0.0.1": {...}, "1.1.2": {...}, "2.0.0-alpha.3": {...} } }
当执行 npm install koa
时,实际上是执行 npm install koa@latest
,而这个 latest
等于 dist-tags.latest
(版本 1.1.2),最后版本 1.1.2 被安装,同时依赖会标记为 "koa": "~1.1.2"
。
当执行 npm install koa@next
时, next
等于 dist-tags.next
(版本 2.0.0-alpha.3),最后版本 2.0.0-alpha.3 被安装,同时依赖会标记为 "koa": "~2.0.0-alpha.3"
。
模块的维护者在进行模块发布时,能够指定将当前版本发布为哪一个 tag(默认是 latest)。
当执行 npm publish
时,会首先将当前版本发布到 npm registry,而后更新 dist-tags.latest
的值为新版本。
当执行 npm publish --tag=next
时,会首先将当前版本发布到 npm registry,而且更新 dist-tags.next
的值为新版本。这里的 next 能够是任意有意义的命名(好比:v1.x、v2.x 等等)。
能够看出,tag 就是对模块某一个版本的标记(类比 git 中 tag 是对某一次提交的标记),而且这个 tag 还能够随时更新为新的版本号。
能对版本打 tag,使得咱们在维护多个版本时很是方便。好比,能够像 koa 的作法同样,新开一个 next 的 tag 来提供新版本给社区试用,而不影响如今的稳定版本。等到新版本逐渐稳定后,再将其发布为 latest 便可。
这样看起来,发布 Node 模块是否是很简单?其实,咱们没有说的东西还有不少。好比下面这些问题:
每次发布前怎么保证模块提供的功能没有损坏?能够考虑接入持续集成系统,每次 push 运行单元测试,未经过不进行发布。
版本号其实对维护者不友好,很容易改错,有没有什么好方法来解决?能够考虑规范提交信息,维护者只是描述变动,从提交信息中抽取变动类型而后生成正确的版本号和变动日志(好比:angular 的提交信息规范:https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit)
这些问题要是能够自动化多好!我尝试搜索了下,果真有开发者在搞了。推荐你们看看这个工具:https://github.com/semantic-release/semantic-release,很好的解决了上面的问题。
可是其实最最核心的问题是维护者得养成好习惯(写单测、规范提交信息),否则再好的工具都白搭。我也在慢慢培养中,真是痛苦......