参考(http://www.alloyteam.com/2016/03/master-npm/)

outline

  1. 什么是npm
  2. 常用命令
  3. 我的npm
  4. 写一个npm包
  5. npm2与npm3

什么是npm

npm.png
npm(Node Package Manager)本来是Node.js的包管理工具,但随着JS这几年的蓬勃发展,现在npm已经成为了几乎所有跟JS相关的工具和软件包的管理工具,并且还在不断的发展完善中.现在最新版的Node.js的安装都会自带npm,装上Node.js就可以使用npm.

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 全局命令
# 显示版本, 检查npm 是否正确安装
npm -v

# 全局安装grunt-cli 模块
npm install -g grunt-cli

# 列出全局已安装模块
npm list -g

# 全局卸载grunt-cli 模块
npm uninstall -g grunt-cli

# 升级全局安装的grunt-cli模块
npm update -g grunt-cli



# 局部命令(cd /path/to/your/project)
# 安装
npm install express

# 列出当前文件夹已安装模块
npm list

# 显示模块详情
npm show express

# 升级当前目录下的项目的所有模块
npm update

# 升级当前目录下的项目的指定模块
npm update express

# 删除指定的模块
npm uninstall express

# 初始化一个node项目目录(引导用户创建package.json,package.json中包含项目名称、版本、作者、git仓库地址)
npm init(交互模式)
npm init -y(非交互模式)

我的npm

这里我把的npm单独拎出来将,主要是因为很多时候npm官方的registry并稳定,我们有时候需要使用国内的npm registry.这里推荐直接使用nrm一个用来管理npm registry的node包(可以替代cnpm),使用方法如下:

1
2
3
4
5
6
7
8
9
10
# 全局安装nrm
npm install -g nrm
# 当前配置了那些registry
# 包含了npm、cnpm、taobao、edunpm、european、australia、strongloop、nodejitsu、Portuguese的源
# 后续可以通过nrm add <registry> <url>的当时增加自定义的私有npm registry
nrm ls
# 测试不同registry的网络速度
nrm test
# 使用taobao的registry(淘宝npm registry速度杠杠的,在教育网、电信网都是taobao的registry最快)
nrm use taobao

除了npm源之外,这里再提一下Node.js的版本的问题.Node.js的版本更新迭代是非常快的,可能我们需要在不同的版本之间不断切换,我们就需要一个node的版本管理工具.
这里说下我的使用习惯,在windows平台上我直接用nodist,在Linux下,我更习惯使用nvm
nodist常用命令如下(具体细节可以参考https://github.com/marcelklehr/nodist/blob/master/usage.txt):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 列举远端的nodejs/iojs的所有版本
nodist dist
# list本机上安装的nodejs/iojs的版本
nodist list
# 安装指定版本的nodejs/iojs
nodist add <version>比如iojsv3.3.1
# 删除指定版本的nodejs/iojs
nodist rm <version>比如iojsv3.3.1
# 全局使用v4.4.3版本的nodejs
nodist nodev4.4.3
# 在当前terminal使用某个版本的nodejs
nodist env nodev0.12.13
# 在当前项目(当前目录以及子目录下)通过./node-version指定nodejs的版本
nodist local nodev0.12.13
# 以v4.4.3版本的Nodejs执行foo.js
nodist r nodev4.4.3 -- foo.js -s

写一个npm包

这里写npm包主要动机来源于,我希望做一个给torrent洗白的工具,结果发现网上需要洗白的种子并不好找.于是我就去找一些磁链网站,希望根据磁链生成torrent.简单来说magnet之于torrent的关系就像md5(‘helloworld’)之于’helloworld’的关系一样.根据磁链找到种子还是需要有人去收集,这里我们用一个叫做bt.box.n0808.com的网站来做根据magnet生成torrent的事情.这里就不说代码具体的逻辑是什么,直接贴一个github地址,有兴趣的朋友可以自己去看.https://github.com/warjiang/magnet2torrent.
这里我主要说一下写npm包的一个基本流程,这里我尽量以代码为主注释为辅说明一下(代码什么的说起来最清楚了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
mkdir your/npm/package
cd your/npm/package
# 这一步是可选的,但是一般开发一个插件都会开源到github上去的
git init -> git remote add origin your/github/repository/ssh_address -> git pull origin master
# 引导用户生成一个package.json
npm init
# 这里就是你需要coding的地方.
touch index.js
# 当然coding的过程你可能需要各种包,用于开发阶段的比如断言库,或者是你的包必须的
# 那么针对这两种的情况npm install的后缀参数是不一样的.
# 对于开发阶段需要的库,比如`mocha`这类的,只有开发阶段用,这样的包用`npm install --save-dev mocha`来安装
# 对于使用我们的库必须要用的包,比如`locash`这里的,这样的包用`npm install --save lodash`来安装
# ...coding....
# ...coding....
# coding完毕,这里建议写一下功能测试代码
# 建立一个test文件夹
# 这里就对应外面的功能js代码在test文件夹下建立同名测试文件
# 开发测试完毕,后面就是发布到https://www.npmjs.com了
# 这里我们首先需要到https://www.npmjs.com去注册一个帐号
# 注册完毕本地执行npm adduser输入用户名、密码就可以
# cd path/to/your/npm/package
# npm publish就可以发布了
# 中间过程如果发布不成功,很有可能是同名的package已经被别人发布过了,
# 判断同名package是否被发布过,可以到https://www.npmjs.com去search一下.
# 如果命名冲突,换个名字重新发布

到这里,基本上npm包也基本写完了.下面还要说一下npm包的文档问题.文档的话,就在开发的package下面生成一个README.md,按照markdown的语法写就成.
到目前为止我们的目录结构如下:
npmpackage.png
下面我们说说怎么更新package
更新包的话,coding完了千万不直接发布,这里我们需要修改package的version号,但这里不要直接修改.修改之前先说下npm维护package版本的规则x.y.z.
x表示主版本号,通常有重大改变或者达到里程碑才改变;
y表示次要版本号,或二级版本号,在保证主体功能基本不变的情况下,如果适当增加了新功能可以更新此版本号;
z表示尾版本号或者补丁号,一些小范围的修修补补就可以更新补丁号.
对应到npm的用法为

1
2
3
npm version patch <=> z++
npm version minor <=> y++ && z=0
npm version major <=> x+= && y=0 && z=0

所以说当我们对我们的package进行功能迭代之后,根据改动功能的大小,我们执行npm version <update_type>,再执行npm publish就是重新发布新的package.
同时注意:
如果npm包同时又是一个git仓库,在运行了npm version <update_type>npm publish之后,npm会自动给git仓库打上一个跟当前版本号一样的tag,对于挂在github上的npm包很有用。

npm2与npm3

最后说说npm2与npm3的区别.

升级到npm3 npm install -g npm

npm3的出现解决了npm包目录太深的问题.相信使用过npm1或者npm2的同学都知道,node_modules太多太深了,甚至一不小心就超过windows资源管理器能处理的最长路径长度了,听起来有点拗口,说白了这时候复制粘贴删除就会报错了.而npm3将依赖模块扁平化存放了,node_modules文件夹里面子文件夹增多了,出现了很多没有通过npm install安装过的模块.npm3在安装包的时候,由于每个包和包的依赖都会去计算是否需要再安装,搜索起来确实变慢了,好在至少现在的npm3速度还是可以接受的.

假如我们写了个模块App,需要安装两个包A@1和C@1,其中A@1依赖另一个包B@1,C@1依赖B@2,用npm2和npm3安装之后的依赖图分别是这样的
npm2&npm3_1.png
npm2的安装思路就是首先安装A@1,发现A@1依赖B@1就安装B@1.在安装C@1,发现C@1依赖B@2就安装B@2.
而npm3的安装思路是首先安装A@1,没有发现A的其他版本的冲突,则直接安装在第一级目录,同时发现A@1依赖B@1,也不存在其他版本的B冲突,所以B@1就直接安装在第一级目录,然后安装C@1,没有其他版本的C冲突,直接安装在第一级目录,发现C@1依赖B@2,这里发现已经安装了B@1,就把B@2放在C@1的目录下.

现在App又需要安装一个包D@1,D@1依赖B@2.首先安装D@1,发现没有冲突的D,D@1直接安装在一级目录.D@1依赖B@2,同时已经有B@1存在于第一级目录上,所以这时B@2就安装在D@1目录下.使用npm3安装之后,包结构将变成下面这样:
npm2&npm3_2.png

接着又安装了一个E@1,依赖B@1.安装E@1时没有冲突的E,所以E直接安装在一级目录下.E@1依赖B@1,这时由于B@1在一级目录上已经安装,这里就无需重复安装B@1,包结构会变成这样:
npm2&npm3_3.png

随着App升级了,需要把A@1升级到A@2,而A@2依赖B@2,把E@1升级到E@2,E@2也依赖B@2.先升级A@1,首先就是移除A@1,再移除A@1的时候,由于E@1还依赖B@1,所以直接移除A@1,安装A@2,发现A@2依赖B@2,同时一级目录上还存在B@1,则把B@2安装在啊A@2目录下.升级E@1的时候,移除E@1发现B@1不再被任何包依赖,于是移除B@1.安装E@2,发现E@2依赖B@2,同时一级目录上不存在任何B的包,直接把B@2安装在一级目录上.新的包结构将变成这样:
npm2&npm3_4.png

显然,出问题了.可以看到出现了冗余,结果跟预期的不一样,既然所有对B的依赖都是B@2,那么只安装一次就够了.

npm在安装包的时候没有这么”智能”,不过npm dedupe命令做的事就是重新计算依赖关系,然后将包结构整理得更合理.执行一遍npm dedupe将得到:
npm2&npm3_5.png