内容来自:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/22
npm模块安装机制
- 发出npm install命令
- 查询node_modules目录之中是否已经存在指定模块
- 若存在,不再重新安装
- 若不存在,
- npm 向register查询模块压缩包的网址
- 下载压缩包,存放在根目录下的.npm目录里
- 解压压缩包到当前项目的node_modules目录
npm实现原理
输入npm install命令并敲下回车后,会经历如下几个阶段:
1.执行工程自身preinstall
当前npm工程如果定义了preinstall钩子,此时会被执行
2.确定首层依赖模块
首先,需要做的是确定工程中的首层依赖,也就是dependencies和devDependencies属性中直接指定的模块(假设此时没有添加npm install参数)
工程本身是整棵依赖树的根节点,每个首层依赖模块都是根节点下面的一棵子树,npm会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。
3.获取模块
获取模块是一个递归的过程,分为以下几步:
- 获取模块信息,在下载一个模块之前,首先要确定其版本,这是因为package.json中往往是语义化版本。此时如果版本描述文件中有该模块信息直接拿即可,如果没有则从仓库获取。如package.json中某个包的版本是^1.1.0, npm就会去仓库中获取符合1.x.x形式的最新版本。
- 获取模块实体。上一步会获取到模块的压缩包地址(resolved字段),npm会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。
- 查找该模块依赖,如果有依赖则回到第1步,如果没有则停止
4.模块扁平化
上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。比如A模块依赖于lodash,B模块同样依赖于lodash,在npm3以前会严格按照依赖树的结构进行安装,因此会造成模块冗余。
从npm3开始默认加入了一个dedupe的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是node-modules下,foo模块依赖lodash@^1.0.0,bar模块依赖lodash@^1.1.0,则^1.1.0为兼容版本。
而当foo依赖lodash@^2.0.0,bar依赖lodash@^1.1.0,则根据semver的规则,二者不存在兼容版本,将会一个版本放在node_modules里,另一个仍保留在依赖树里。
举个例子,假设一个依赖树原本是这样:
node_modules
-- foo
---- lodash@version1
-- bar
---- lodash@version2
假设version1和version2是兼容版本,则经过dedupe会成为下面的形式:
node_module
-- foo
-- bar
-- lodash (保留的版本为兼容版本)
假设version1和version2为非兼容版本,则后面的版本保留在依赖树中
node_modules
-- foo
-- lodash@version1
-- bar
---- lodash@version2
5.安装模块
这一步将会更新工程中的node_modules,并执行模块中的生命周期函数(按照preinstall、install、postinstall的顺序)
6.执行工程自身生命周期
当前npm工程如果定义了钩子此时会被执行(按照install、postinstall、prepublish、prepare的顺序)
最后一步以生成或更新版本描述文件,npm install过程完成。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
读阮一峰《npm模块按照机制简介》笔记
原文:http://www.ruanyifeng.com/blog/2016/01/npm-install.html
npm是Node的模块管理器,功能极其强大。它是Node获得成功的重要原因之一
本文介绍npm模块安装机制的细节,以及如何解决安装速度慢的问题。
一、npm install
如果你希望,一个模块不管是否安装过,npm都要强制重新安装,可以使用-f或-force参数
npm install <packageName> --force
二、npm update
如果想更新已安装模块,就要用到npm update命令
npm update <packageName>
它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。
registry
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
访问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命令,从registry下载压缩包之后,都存放在本地的缓存目录。
这个缓存目录,在Linux或Mac默认是用户主目录下的.npm目录,在Windows默认是%AppData%/npm-cache.
通过配置命令,可以查看这个目录的具体位置
npm config get cache
浏览目录
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
清空缓存命令
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 999999 <package-name>
上面命令指定,只有超过999999分钟的模块才从registry下载。实际上就是指定,所有模块都从缓存安装,这样就大大加快了下载速度。
它还有另一种写法
npm install --cache-min Infinity <package-name>
离线安装的解决方案
第一类,Registry代理
- npm-proxy-cache
- local-npm
- npm-lazy
上面三个模块的用法很类似,都是在本机起一个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目录作为缓存。