npm 是 Node 的模块管理器,功能极其强大。它是 Node 得到成功的重要缘由之一。 node
正由于有了npm,咱们只要一行命令,就能安装别人写好的模块 。 react
$ npm install
本文介绍 npm 模块安装机制的细节,以及如何解决安装速度慢的问题。 git
npm install 命令用来安装模块到node_modules目录。 github
$ npm install <packageName>
安装以前,npm install会先检查,node_modules目录之中是否已经存在指定模块。若是存在,就再也不从新安装了,即便远程仓库已经有了一个新版本,也是如此。 shell
若是你但愿,一个模块不论是否安装过,npm 都要强制从新安装,可使用-f或--force参数。 npm
$ npm install <packageName> --force
若是想更新已安装模块,就要用到npm update命令。 json
$ npm update <packageName>
它会先到远程仓库查询最新版本,而后查询本地版本。若是本地版本不存在,或者远程版本较新,就会安装。 缓存
npm update命令怎么知道每一个模块的最新版本呢? 服务器
答案是 npm 模块仓库提供了一个查询服务,叫作 registry 。以 npmjs.org 为例,它的查询服务网址是 https://registry.npmjs.org/ 。 网络
这个网址后面跟上模块名,就会获得一个 JSON 对象,里面是该模块全部版本的信息。好比,访问 https://registry.npmjs.org/react,就会看到 react 模块全部版本的信息。
它跟下面命令的效果是同样的。
$ npm view react # npm view 的别名 $ npm info react $ npm show react $ npm v react
registry 网址的模块名后面,还能够跟上版本号或者标签,用来查询某个具体版本的信息。好比, 访问 https://registry.npmjs.org/react/v0.14.6 ,就能够看到 React 的 0.14.6 版。
返回的 JSON 对象里面,有一个dist.tarball属性,是该版本压缩包的网址。
dist: { shasum: '2a57c2cf8747b483759ad8de0fa47fb0c5cf5c6a', tarball: 'http://registry.npmjs.org/react/-/react-0.14.6.tgz' },
到这个网址下载压缩包,在本地解压,就获得了模块的源码。npm install和npm update命令,都是经过这种方式安装模块的。
npm install或npm update命令,从 registry 下载压缩包以后,都存放在本地的缓存目录。
这个缓存目录,在 Linux 或 Mac 默认是用户主目录下的.npm目录,在 Windows 默认是%AppData%/npm-cache。经过配置命令,能够查看这个目录的具体位置。
$ npm config get cache $HOME/.npm
你最好浏览一下这个目录。
$ ls ~/.npm # 或者 $ npm cache ls
你会看到里面存放着大量的模块,储存结构是{cache}/{name}/{version}。
$ npm cache ls react ~/.npm/react/react/0.14.6/ ~/.npm/react/react/0.14.6/package.tgz ~/.npm/react/react/0.14.6/package/ ~/.npm/react/react/0.14.6/package/package.json
每一个模块的每一个版本,都有一个本身的子目录,里面是代码的压缩包package.tgz文件,以及一个描述文件package/package.json。
除此以外,还会生成一个{cache}/{hostname}/{path}/.cache.json文件。好比,从 npm 官方仓库下载 react 模块的时候,就会生成registry.npmjs.org/react/.cache.json文件。
这个文件保存的是,全部版本的信息,以及该模块最近修改的时间和最新一次请求时服务器返回的 ETag 。
{ "time":{ "modified":"2016-01-06T23:52:45.571Z", // ... }, "_etag":"\"7S37I0775YLURCFIO8N85FO0F\"" }
对于一些不是很关键的操做(好比npm search或npm view),npm会先查看.cache.json里面的模块最近更新时间,跟当前时间的差距,是否是在可接受的范围以内。若是是的,就再也不向远程仓库发出请求,而是直接返回.cache.json的数据。
.npm目录保存着大量文件,清空它的命令以下。
$ rm -rf ~/.npm/* # 或者 $ npm cache clean
总结一下,Node模块的安装过程是这样的。
- 发出npm install命令
- npm 向 registry 查询模块压缩包的网址
- 下载压缩包,存放在~/.npm目录
- 解压压缩包到当前项目的node_modules目录
注意,一个模块安装之后,本地其实保存了两份。一份是~/.npm目录下的压缩包,另外一份是node_modules目录下解压后的代码。
可是,运行npm install的时候,只会检查node_modules目录,而不会检查~/.npm目录。也就是说,若是一个模块在~/.npm下有压缩包,可是没有安装在node_modules目录中,npm 依然会从远程仓库下载一次新的压缩包。
这种行为当然能够保证老是取得最新的代码,但有时并非咱们想要的。最大的问题是,它会极大地影响安装速度。即便某个模块的压缩包就在缓存目录中,也要去远程仓库下载,这怎么可能不慢呢?
另外,有些场合没有网络(好比飞机上),可是你想安装的模块,明明就在缓存目录之中,这时也没法安装。
为了解决这些问题,npm 提供了一个--cache-min参数,用于从缓存目录安装模块。
--cache-min参数指定一个时间(单位为分钟),只有超过这个时间的模块,才会从 registry 下载。
$ npm install --cache-min 9999999 <package-name>
上面命令指定,只有超过999999分钟的模块,才从 registry 下载。实际上就是指定,全部模块都从缓存安装,这样就大大加快了下载速度。
它还有另外一种写法。
$ npm install --cache-min Infinity <package-name>
可是,这并不等于离线模式,这时仍然须要网络链接。由于如今的--cache-min实现有一些问题。
(1)若是指定模块不在缓存目录,那么 npm 会链接 registry,下载最新版本。这没有问题,可是若是指定模块在缓存目录之中,npm 也会链接 registry,发出指定模块的 etag ,服务器返回状态码304,表示不须要从新下载压缩包。
(2)若是某个模块已经在缓存之中,可是版本低于要求,npm会直接报错,而不是去 registry 下载最新版本。
npm 团队知道存在这些问题,正在重写 cache。而且,未来会提供一个--offline参数,使得 npm 能够在离线状况下使用。
不过,这些改进没有日程表。因此,当前使用--cache-min改进安装速度,是有问题的。
社区已经为npm的离线使用,提出了几种解决方案。它们能够大大加快模块安装的速度。
解决方案大体分红三类。
第一类,Registry 代理。
上面三个模块的用法很相似,都是在本机起一个 Registry 服务,全部npm install命令都要经过这个服务代理。
# npm-proxy-cache $ npm --proxy http://localhost:8080 \ --https-proxy http://localhost:8080 \ --strict-ssl false \ install # local-npm $ npm set registry http://127.0.0.1:5080 # npm-lazy $ npm --registry http://localhost:8080/ install socket.io
有了本机的Registry服务,就能彻底实现缓存安装,能够实现离线使用。
第二类,npm install替代。
若是可以改变npm install的行为,就能实现缓存安装。npm-cache 工具就是这个思路。凡是使用npm install的地方,均可以使用npm-cache替代。
$ npm-cache install
第三类,node_modules做为缓存目录。
这个方案的思路是,不使用.npm缓存,而是使用项目的node_modules目录做为缓存。
上面两个工具,都能将项目的node_modules目录打成一个压缩包,之后安装的时候,就从这个压缩包之中取出文件。
做者: 阮一峰