写点什么

运行 npm install 命令的时候会发生什么?

  • 2022 年 4 月 22 日
  • 本文字数:2152 字

    阅读完需:约 7 分钟

本文分享自华为云社区《运行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 文件下dependenciesdevDependencies字段里的依赖,作为依赖树的根节点;然后在每个根节点依赖下面都会有其依赖的依赖,作为其子节点;npm 会开启多进程从每个根节点开始逐步往下寻找更深层次的节点。而 package.json 文件下dependenciesdevDependencies字段里的依赖会被安装在 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 命令下载方式

  1. npm install xxx #(XXX 是某依赖包)安装依赖模块至项目 node_modules 目录下,不会修改 package.json 文件里的内容

  2. npm install -g xxx #安装依赖模块到全局(而不是项目 node_modules 目录下),不会将该依赖模块写到 package.json 文件里的dependenciesdevDependencies字段里

  3. npm install --save xxx #安装依赖模块到项目 node_modules 目录下,并将依赖写入到 package.json 文件里的dependencies字段中;该依赖是开发和生产环境里都需要的

  4. npm install --save-dev xxx #安装依赖模块到项目 node_modules 目录下,并将依赖写入到 package.json 文件里的devDependencies字段中


点击关注,第一时间了解华为云新鲜技术~​

发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
运行npm install命令的时候会发生什么?_node.js_华为云开发者社区_InfoQ写作社区