相爱相杀的npm和yarn

406 阅读7分钟

npm的核心目标:Bring the best of open source to you, your team and your company。说人话就是管理开源库。

先看几个问题:

一. 项目中同时用了npm和yarn会有啥问题?为什么会出现这个问题?

npm和yarn的底层设计和安装机制不同, 导致分别使用yarn和npm安装依赖时的版本可能不同, 导致构建失败并抛出错误。

二. 执行npm install后发生了什么?

1. 首先获取配置信息,优先级为: 项目级.npmrc文件 > 用户级 > 全局 > npm内置的默认配置

2. 然后检查项目中有无package-lock.json文件:

A. 如有, 检查lock文件和package.json文件的依赖版本是否完全一致, 一致则使用lock文件, 发起请求, 从缓存或者网络中加载依赖; 不一致则根据npm的不同版本有不同的处理逻辑。

B. 如无, 则根据package.json递归构建依赖树, 根据依赖树加载资源。在加载的时候会先检查是否有缓存:

a. 如有, 将缓存内容解压到node_modules中。

b. 如无, 远程加载npm包资源,确认包的完整性后, 将包添加到缓存中和解压到node_modules中。

最后根据package.json生成package-lock.json文件。 值得注意的是, npm充分利用了缓存机制自身的安装规范(扁平化原则、最大可复用原则等)提升了包的安装速度和效率。

三. 为啥package.json不能确定唯一的依赖树?

  1. 不同版本的npm的安装策略和算法不同。

  2. npm install 将根据package.json文件中的semver-range version更新依赖,导致每一次的依赖版本安装可能会不一致。详见以下链接中的语义化版本(Semantic Versioning)blog.csdn.net/mutouren_ab…

事实上,这也是为啥出现lock文件的原因

四. npx有啥作用?

  1. 可以便捷地调用依赖模块中的api,而不需要在package.json中定义相关script。如: npx eslint --init npx eslint xx.js 原理:npx可以自动去node_modules/.bin文件中和环境你变量$PATH中检查命令是否存在。
  2. 不需要全局安装模块依赖就可以进行调用。如: npx crate-react-app yourapp 原理:先安装模块依赖,使用后再删除此依赖。

五. 多源依赖项目如何安装?

  1. 可以在package.json中的script中配置相关源切换钩子方法,在安装前先执行此npm命令。如: script: {preinstall: node ./bin/preinstall.js"} 如上,在preinstall.js中调用node的api逻辑判断匹配成功后,调用npm config set命令。
  2. 或者也可以使用镜像源管理工具比如nrm进行切换。

六. 公司部署的私有npm镜像有啥用?

因为使用npm下载依赖的速度比较慢,尤其是大型项目,因此严重影响CI/CD流程和本地开发效率。 而私有镜像后则可以确保npm服务高速稳定和安全。

yarn的出现是为了解决当时的npm版本的某些问题,比如安装包的完整性和一致性问题,速度慢等。而时至今日,最新版本的npm性能和体验已经有了很大的进步。

更好
  1. 确定性:yarn率先引入锁机制,以确保在任意容器下,不管安装顺序如何,依赖包都会以同样的方式安装。

  2. 扁平安装模式:yarn率先引入扁平安装模式来解决同一依赖包的多版本冗余安装问题。

更快
  1. yarn采用请求排队的理念充分利用了网络资源,同时引入了安装失败的重试机制。

  2. 采用缓存机制,率先实现了离线缓存。

yarn独特的安装机制 --

yarn安装主要有5个步骤:

  1. 检测包。比如是否存在package-lock,.json等npm文件以告警冲突。

  2. 解析包。解析依赖树中每一个包的版本信息,执行顺序如下:

首先通过package.json中的相关信息确定首层依赖的版本信息,然后遍历首层依赖,进一步获取子依赖的版本信息,在递归过程中,使用set数据结构以规避循环引用问题。

  1. 获取包

主要是检查缓存中是否有已经解析到的依赖包,如果没有就发起网络请求,其中,如果依赖包地址是一个file协议或者相对路径,则从本地离线缓存中获取包,最终写入缓存目录。

4.链接包

这一步遵循扁平化原则将项目中的依赖包复制到node_modules目录下。在此之前,yarn会先解析peerDependencies的内容以解决同一依赖多版本问题,如果这个包没有peerDependencies信息或者同一依赖的peerDependencies信息出现冲突,则warning提示。

  1. 构建包

如果依赖包中存在二进制的包,需要进行编译。

npm v2中糟糕的“依赖地狱” -- 项目中的A依赖,依赖于B依赖,B依赖于C依赖...

图文介绍:blog.csdn.net/mutouren_ab…

可想而知,这样的依赖树很容易产生冗余依赖且不利于排查解决问题,安装速度也很慢。

所以,在npm v3和yarn中,node_modules改成了扁平结构。

npm和yarn在使用上的区别:

  1. yarn.lock中没有明确子依赖的版本号,需要查看package.json。

  2. yarn默认使用prefer-online模式,优先使用网络数据,请求失败时再去请求本地缓存。而npm是用从缓存中加载依赖。

相互切换

可以使用synp等工具将yarn.lock文件转换Wiepackage-lock.json文件。

npm使用时的注意事项:
  1. 在npm v5.4.2之后的较新版本中,执行npm install之后,如果只有package.json文件,会据此生成package-lock.json文件,据此安装依赖;如果项目中存在package.json文件和package-lock.json文件,则以package-lock.json文件为准安装依赖,但有一种情况除外:在package.json的semver-range版本和package-lock.json的版本不兼容的时候,会先自动更新lock文件的版本,再根据package-lock.json安装依赖。

  2. 任何时候都不要修改package-lock.json文件,因为它是用来确保项目依赖安装的规范性、统一性和确定性的,以保证项目高效稳定运行的,如有需要,修改package.json文件即可。

  3. 如果package-lock.json文件出现冲突或问题,同时删除本地的package-lock.json文件和package.json文件,引入远程仓库的package-lock.json文件和package.json文件,再执行npm install即可。

yarn和npm面临的共同问题:

一. 啥时需要提交lock文件到仓库?

A. 如果你开发的是一个业务应用,建议提交lcok文件到代码版本仓库,用以保证所有相关成员在执行install命令后能得到一致的依赖。注意: 这里说的是一致,而不是完全相同!

B. 如果你开发的是一个开源基础库,建议不提交lock文件。因为这种库一般是被其它项目所依赖的,如果没有lock文件,就可以复用主项目node_modules中的包,提升性能。而如果这个库的部分依赖需要固定的某个版本,这个时候,如果提交lock文件,基于上述yarn和npm安装机制的解读,可能会造成某一依赖包的多个版本被下载的情况出现,增加依赖包的数量,降低性能。这时最好是通过定义peerDependencies(同版本依赖)的内容来解决。

二. 一些开源库会有针对核心依赖的更进一步的版本检查,如果不满足的话,会抛出错误提示并中止构建。

几种不同类型的依赖解析:

dependencies:项目依赖,这表示线上生产环境运行时需要用到比如element plus。当首层依赖中某个dependencies中的依赖包被下载时,这个包的dependencie下的包也会一并被下载。

devDependencies: 开发依赖,比如webpack中的各种loader等。

peerDependencies: 同版本依赖,表示包和包之间的宿主共生关系。

bundledDependencies: 捆绑依赖。

optionalDependencies: 可选依赖,一般不会用到。

总结: 基于现有的包安装规范,不管是npm还是yarn,包的安装顺序都会对依赖树造成较大的影响,尤其是对文件数造成较大的影响。所以,在首次安装依赖之后,如果是npm建议使用npm dedupe命令优化安装,更新安装包的结构,减少安装包的数量(yarn在安装依赖的最后会自动执行dedupe命令)