运行 npm install 命令的时候会发生什么?
本文分享自华为云社区《运行npm install命令的时候会发生什么?》,作者: gentle_zhou。
npm(node package manager),是随同 Node.js 一起安装的第三方包管理器;通过 npm,我们可以安装、共享、分发代码,管理项目的依赖关系。
我们日常在下载第三方依赖的时候,都会用到一个命令npm install
,然后依赖包就会被安装到 node_modules 目录下;但是我们在运行这个命令的时候,都会发生什么呢?带着好奇心,我去调研学习了一番。
大致的流程是:npm install
命令输入 > 检查 node_modules 目录下是否存在指定的依赖 > 如果已经存在则不必重新安装 > 若不存在,继续下面的步骤 > 向 registry(本地电脑的.npmrc 文件里有对应的配置地址)查询模块压缩包的网址 > 下载压缩包,存放到根目录里的.npm 目录里 > 解压压缩包到当前项目的 node_modules 目录中。
(上面的图片就显示了项目中依赖如果过多的尴尬:等待时间过长,下载下来依赖过多导致 node_modules 过大)
下面会介绍一下 npm 处理依赖的早前和当前的方式 以及 几种不同的 install 命令下载方式。
早期版本:递归
在 npm 早期版本里,npm 处理依赖的方式很粗暴简单。它会严格按照根目录下 package.json 文件的结构以及各个子依赖包的 package.json 文件的结构,递归地把依赖安装到它们各自的 node_modules 目录里。这样如果是个小项目,只需要几个依赖且这些依赖不会依赖别的依赖,那么这样的树形结构就还算清晰明了(node_modules 的结构与 package.json 里的结构一一对应且层级结构明显)。
但如果我们的项目是个大项目,里面的依赖非常多(导致嵌套层级非常深),且不同的层级可能会引用同一个依赖(导致重复冗余),就不是我们想要的情形了。
当前版本:扁平化
于是,为了解决以上递归管理依赖带来的问题,npm 在 3.X 版本里做了一次更新,引入了扁平化管理(dedupe)的方式。dedupe 是 dedeplicated 的缩写,即 duplicates were remove,把重复的移除。
扁平化管理的思路就是首先遍历 package.json 文件下dependencies
和devDependencies
字段里的依赖,作为依赖树的根节点;然后在每个根节点依赖下面都会有其依赖的依赖,作为其子节点;npm 会开启多进程从每个根节点开始逐步往下寻找更深层次的节点。而 package.json 文件下dependencies
和devDependencies
字段里的依赖会被安装在 node_modules 根目录下。在遍历这些依赖的时候,如果发现有重复的依赖模块(重复:模块名相同且 semantic version 兼容;这里的兼容,是指语义化版本都会有一段版本允许范围,如果两个依赖的版本号是在这个范围交际里就说明是兼容;比如依赖 X 依赖于依赖 Y@^1.0.0,而依赖 Z 依赖于依赖 Y@^1.1.0,则 Y@^1.1.0 就是兼容版本),就直接将其丢弃。
但是如果仅仅这样,其实也有风险。在大项目中,很有可能会碰到依赖 A 依赖于依赖 C-1.0 版本,依赖 B 依赖于依赖 C-2.0 版本;而在执行npm install
命令的时候,会按照 package.json 里面的依赖顺序依次解析,因此依赖 C-1.0 和依赖 C-2.0 的在文件里的放置顺序会导致 Node_modules 的依赖结构产生变化。而且为了让开发者可以使用最新的依赖包,package.json 文件里通常只会锁定大版本(即文件里依赖如果是^1.1.0
版本,npm 就会去仓库中获取符合 1.x.x 形式的最新版本),因此某些依赖包小版本更新后,也会造成依赖结构的改变。所以,为了解决 npm install 命令导致的这种不确定问题,npm 5.x 版本里还新增了 package-lock.json 文件。
package-lock.json 文件可以保证每次执行 npm install 后生成的 node_modules 目录结构一定是完全相同的。下图就是 package-lock.json 中其中一个依赖的信息,有 name-包名,version-包的版本号,dependencies-和 node_modules 中包结构一一对应的对象,resolved-包具体的安装来源,integrity-包的 hash 值,requires-对应子依赖的依赖:
注:并不是所有的子依赖都有 dependencies 这个属性,只有子依赖的依赖和当前已安装在根目录的 Node_modules 中的依赖起了冲突之后,才会有这个属性。
置于为何说 package-lock.json 文件 和 node_modules 目录结构是一一对应的。还是举刚刚前面提及的那个依赖冲突导致依赖结构产生变化的例子,“依赖 A 依赖于依赖 C-1.0 版本,依赖 B 依赖于依赖 C-2.0 版本”,此时因为 package-lock.json 文件的存在,我们会把依赖 C-1.0 版本安装在依赖 A 的 node_modules 目录下(对应依赖 A 在 package.json 文件里的dependencies
属性),依赖 C-2.0 版本安装在根目录下。这可以保障每次安装生成的依赖目录结构保持相同。
package-lock.json 文件还有个优点,就是它会缓存每个包的具体版本和下载链接,在后期再去 install 的时候,就不需要再去远程仓库进行查询操作了,减少了大量网络请求。
几种不同的 install 命令下载方式
npm install xxx #(XXX 是某依赖包)安装依赖模块至项目 node_modules 目录下,不会修改 package.json 文件里的内容
npm install -g xxx #安装依赖模块到全局(而不是项目 node_modules 目录下),不会将该依赖模块写到 package.json 文件里的
dependencies
和devDependencies
字段里npm install --save xxx #安装依赖模块到项目 node_modules 目录下,并将依赖写入到 package.json 文件里的
dependencies
字段中;该依赖是开发和生产环境里都需要的npm install --save-dev xxx #安装依赖模块到项目 node_modules 目录下,并将依赖写入到 package.json 文件里的
devDependencies
字段中
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/11fae099777b65e0635d4027a】。文章转载请联系作者。
评论