npm 模块安装机制简介

作者: 阮一峰

日期: 2016年1月21日

npm 是 Node 的模块管理器,功能极其强大。它是 Node 获得成功的重要原因之一。

正因为有了npm,我们只要一行命令,就能安装别人写好的模块 。


$ npm install 

本文介绍 npm 模块安装机制的细节,以及如何解决安装速度慢的问题。

一、从 npm install 说起

npm install 命令用来安装模块到node_modules目录。


$ npm install <packageName>

安装之前,npm install会先检查,node_modules目录之中是否已经存在指定模块。如果存在,就不再重新安装了,即使远程仓库已经有了一个新版本,也是如此。

如果你希望,一个模块不管是否安装过,npm 都要强制重新安装,可以使用-f--force参数。


$ npm install <packageName> --force

二、npm update

如果想更新已安装模块,就要用到npm update命令。


$ npm update <packageName>

它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。

三、registry

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 installnpm update命令,都是通过这种方式安装模块的。

四、缓存目录

npm installnpm 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 searchnpm view),npm会先查看.cache.json里面的模块最近更新时间,跟当前时间的差距,是不是在可接受的范围之内。如果是的,就不再向远程仓库发出请求,而是直接返回.cache.json的数据。

.npm目录保存着大量文件,清空它的命令如下。


$ rm -rf ~/.npm/*
# 或者
$ npm cache clean

五、模块的安装过程

总结一下,Node模块的安装过程是这样的。

  1. 发出npm install命令
  2. npm 向 registry 查询模块压缩包的网址
  3. 下载压缩包,存放在~/.npm目录
  4. 解压压缩包到当前项目的node_modules目录

注意,一个模块安装以后,本地其实保存了两份。一份是~/.npm目录下的压缩包,另一份是node_modules目录下解压后的代码。

但是,运行npm install的时候,只会检查node_modules目录,而不会检查~/.npm目录。也就是说,如果一个模块在~/.npm下有压缩包,但是没有安装在node_modules目录中,npm 依然会从远程仓库下载一次新的压缩包。

这种行为固然可以保证总是取得最新的代码,但有时并不是我们想要的。最大的问题是,它会极大地影响安装速度。即使某个模块的压缩包就在缓存目录中,也要去远程仓库下载,这怎么可能不慢呢?

另外,有些场合没有网络(比如飞机上),但是你想安装的模块,明明就在缓存目录之中,这时也无法安装。

六、--cache-min 参数

为了解决这些问题,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目录打成一个压缩包,以后安装的时候,就从这个压缩包之中取出文件。

(完)

留言(39条)

建议增加使用npm镜像的部分,比如淘宝镜像,http://npm.taobao.org/

对npm相当清析了

好!镜像什么的,我倒是更建议大家学会国际加速

可惜win的文件系统(确切地说是系统api)对长路径支持太差了,node维护起来很吃力

npm 真的很好用

还有一个npm插件:node-gyp-install 可以大大降低国内用 npm install 命令安装一些需要 C++ 编译器编译的插件的安装速度。

当然,首先得要学会科学上网

如果在 windows 下npm模块安装失败,有一种原因是文件和目录权限不足。
原因可能是: npm 采用的是另一个普通用户来执行安装,所以权限较低, 不能读写当前登录用户 Administrator 的目录。这时候需要修改目录权限。 如 C:\Users\Administrator\AppData\Roaming\npm 或者你的项目目录。

用了一段时间的nodejs,发现npm用起来真的是很方便

这几天是想装ghost,可是总不成功。还是先静下心学习下再说

对于在 GFW 内的朋友,可以试一试使用 nrm 这个插件,来快速切换 npm 的镜像,例如淘宝的 npm 镜像。这样 npm install 时候成功率高一些。

有些問題想請教,關於 `{cache}/{hostname}/{path}/.cache.json` 的部分有些不太明白,這份文件是存放在本機的嗎?

另外不太確定 --cache-min 單位是分鐘?這指的是 cache 到期時間嗎?

https://docs.npmjs.com/misc/config#cache-min

@andyyou:是的,存在本机。cache-min指缓存多少分钟。

感謝您快速的回覆,那關於檔案路徑位置該如何尋找,我在 `~/.npm` 下怎麼都找不著。還是觀念上有錯誤呢?

npm install 是不是完全不依赖node也可以单独执行?谢谢。

我想问一下,通过 npm 安装的包,如果是需要在 HTML 里包含使用,路径怎么找呢?是需要手动复制出来用么?

您好,我现在遇到一个问题:用npm install 指令下载modules的时候,只有~/.npm 里面下载到了包,我当前项目所在的根目录根本没有node_modules。 我是升级了node之后才出现这个问题的。您知道怎么解决吗?

上面这个问题我解决了,手动初始化npm init建了一个node_modules后就好了。另外有一个问题我是真的完全不知道怎么解决。我用npm install -g 想全局安装,同样,只有~/.npm里面有,全局安装却不成。我也不知道全局安装的包是放在哪里。terminal里面总是说command not found。(Mac OS)

update: 我知道了用npm root -g 查看全局包安装的路径。我现在的问题是,几个包明明已经下载到全局路径了,可是terminal就是提示command not found。以下包都是这种情况。
npm install -g nrm;
npm install -g cnpm --registry=https://registry.npm.taobao.org;

cordova 和ionic直接就下载不下来。

本想放弃的一刻,--cache-min 拯救了我,npm还不至于那么糟糕。react编译环境折腾了2天,每次安装都在中途卡住,ctrl+c重新安装,重复了几十次……最后用--cache-min分段安装,终于成功了!谢谢!

谢谢分享~ npm install安装了N多次失败,用cache终于解决

阮哥,你的一手资料都是从哪得来的,这么系统和详细啊

阮哥,我安装pm2 的时候发现 无论是卸载还是安装都会 提示成功 可是使用的时候还是 很久以前安装的 pm2 0.15的版本!

苹果下也可以这么使用吗?

阮哥新手看了教程对比发现node_modules 目录下的解压过的东西和~./npm的东西并不一样,_./npm中的一些东西都没有解压.想请问机器怎么知道我要用的那些modules

%AppData%/npm-cache 应该是%AppData%/Roaming/npm-cache

现在 npm 请求得到 304状态码,是不是直接从缓存中复制到当前node_modules了,不再下载了

为什么每次使用npm i指令都会删除若干node文件夹下已有依赖包。。。。?

引用石樱灯笼的发言:

可惜win的文件系统(确切地说是系统api)对长路径支持太差了,node维护起来很吃力

taobao的cnpm就ok,安装包平级的,包和包之间的引用使用快捷方式

生产环境没有网络,所以我借鉴了阮老师说的离线安装的第三者做法,用到了npmbox.
然后我到https://github.com/arei/npmbox上面去按照教程安装,报错,仍然需要网络。
我的环境是: cenos6.5,[email protected] ,[email protected] . [email protected]/4.2.1 ,没有网络。
我想问下阮老师有没有好的办法解决,如何离线安装。从我目前收集到的知识发现不可以,还望请教。

npm nvm 下不下来

引用王彭彭的发言:

为什么每次使用npm i指令都会删除若干node文件夹下已有依赖包。。。。?

同问 ,这个问题有解决办法么?

npm_cache_share,缓存安装产物

local-npm也要npm install啊???
我的情况是:
1.有npm,本机无网络权限,无法npm install
2.只能从远程连接的电脑下载github的代码复制粘贴回本机
3.但是像webpack这样的源码,我粘贴到node_modules也好像不行啊

阮老师你好,请教一个问题。从2013年开始使用node.js,一直被他的包管理机制所困惑,就是磁盘的占用问题:现在随随便便建一个项目,npm install之后,node_modules就几百M.

1.Node默认从项目的node_modules里查找模块,这没问题,但是是否有必要为每一个项目的依赖包,保留他的副本在node_modules里?
2.能否学习java里的gradle/Maven的方式,在.npm里缓存模块,然后项目的node_modules里放的只是一个文件夹Link到.npm里?

感谢

这部分知识可能等以后遇到坑会来看看,知识体系太单薄,感谢阮老师

最好在文章首部添加写这篇文章时 npm 版本,搜索引擎搜到安装机制一看才发现很多东西都由于版本升级不再适用了,容易对新人产生误导。npm update 无效、[email protected] 之后 cache 缓存目录也有很大变化。

有个疑问,dist.tarball中的压缩文件为什么解压后会没有.gitignore文件。有什么讲究吗

有个疑问,npm install打包后,从私服上拉取的模块的package.json文件,包含了私服地址和该模块的存放地址"_resolved"和"_where",这两个数据能在执行inatall命令时不生成吗?

现在2023年了,是否考虑更新文章内容了?

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接