Skip to content

npm 和 yarn有哪些不一样的地方?

Posted on:2024年8月10日 at 17:06

早期的npm

其实在最早期的npm版本(npm v2),npm的设计可以说是非常的简单,在安装依赖的时候会将依赖放到 node_modules文件中。同时,如果某个直接依赖A依赖于其他的依赖包B,那么依赖B会作为间接依赖,安装到依赖A的文件夹node_modules中,然后可能多个包之间也会有出现同样的依赖递归的,如果项目一旦过大,那么必然会形成一棵巨大的依赖树,依赖包会出现重复,形成嵌套地狱

那么我们如何去理解”嵌套地狱”呢?

那么这样的重复问题会带来什么后果呢?

那么, 后面的版本是如何一步步进行优化的呢?后面会陆续的揭晓。

npm or yarn 开发中的一点疑惑

你在实际的开发会不会出现这样的一些情况:

  1. 当你项目依赖出现问题的时候,我们会不会是直接删除 node_modules 和 lockfiles依赖,再重新 npm install,删除大法是否真的好用?这样的使用方案会不会带来什么问题?
  2. 把所有的依赖包都安装到dependencies中,对devDependencies 不区分会不会有问题?
  3. 一个项目中,你使用 yarn,我使用npm,会不会有问题呢?
  4. 还有一个问题,lockfiles 文件 我们提交代码的时候需不需要提交到仓库中呢?

npm的安装机制和核心原理

我们可以先来看看 npm 的核心目标

Bring the best of open source to you, your team and your company.

意思是 给你和你的团队、你的公司带来最好的开源库和依赖。 通过这句话,我们可以了解到 npm 最重要的一点就是安装和维护依赖。那么,让我们先来看一看npm的安装机制是怎样的呢?

npm的安装机制

下面我们会通过一个流程图来具体学习npm install的安装机制

npm install执行之后,首先会检查和获取 npm的配置,这里的优先级为:

项目级的.npmrc文件 > 用户级的 .npmrc文件 > 全局级的 .npmrc > npm内置的 .npmrc 文件

然后检查项目中是否有 package-lock.json文件

最后,生成 package-lock.json 文件。

其实,在我们实际的项目开发中,使用npm作为团队的最佳实践: 同一个项目团队,应该保持npm 版本的一致性

从上面的安装流程,不知道大家注意到了一点没有,在实际的项目开发中,如果每次都去安装对应依赖时,如果相关的依赖包体积过大或者是依赖于网络,无疑会增加安装的时间成本。那么,缓存在这里的就是一个解决问题的好办法。

yarn的出现

yarn 是一个由FacebookGoogleExponentTilde构建的新的JavaScript包管理器。它的出现是为了解决历史上npm的某些不足(比如npm对于依赖的完整性和一致性的保证,以及npm安装过程中速度很慢的问题)

当npm还处于v3时期的时候,一个叫yarn的包管理工具横空出世。在2016年,npm还没有package-lock.json文件,安装的时候速度很慢,稳定性很差,yarn的出现很好的解决了一下的一些问题:

我们可以来看一下 yarn.lock的结构:

"@babel/cli@^7.1.6", "@babel/cli@^7.5.5":
  version "7.8.4"
  resolved "http://npm.in.zhihu.com/@babel%2fcli/-/cli-7.8.4.tgz#505fb053721a98777b2b175323ea4f090b7d3c1c"
  integrity sha1-UF+wU3IamHd7KxdTI+pPCQt9PBw=
  dependencies:
    commander "^4.0.1"
    convert-source-map "^1.1.0"
    fs-readdir-recursive "^1.1.0"
    glob "^7.0.0"
    lodash "^4.17.13"
    make-dir "^2.1.0"
    slash "^2.0.0"
    source-map "^0.5.0"
  optionalDependencies:
    chokidar "^2.1.8"

熟悉npm的package-lock.json文件的朋友,可能一眼就看到了一些不同; package-lock.json采用的是JSON的结构,而yarn并没有采用这种结构,而是一种自定义的标记方式;我们可以看出新的自定义的方式,也同样保持了高度的可读性。

相比于npm,Yarn另一个显著的区别就是yarn.lock的子依赖的版本不是固定的版本。这其实就说明了一个问题:一个单独的yarn.lock的问题并不能确定node_modules的文件结构,还需要package.json的配合。

yarn的安装机制

上面一小节我们对npm的安装机制有了一些基本的了解,现在让我们先来简单的看一下Yarn的安装理念。

简单来说, Yarn的安装大致分为5个步骤:

检测(checking) ---> 解析包(Resolving Packages) ---> 获取包(Fetching) ---> 链接包(Linking Packages) ---> 构建包(Building Packages)

那么接下来我们要开始具体分析这些过程中都做了哪些事情:

检测包

这一步,最主要的目的就是检测我们的项目中是否存在npm相关的文件,比如package-lock.json等;如果有,就会有相关的提示用户注意:这些文件可能会存在冲突。在这一步骤中 也会检测系统OS, CPU等信息。

解析包

这一步会解析依赖树中的每一个包的信息:

首先呢,获取到首层依赖: 也就是我们当前所处的项目中的package.json定义的dependenciesdevDependenciesoptionalDependencies的内容。

紧接着会采用遍历首层依赖的方式来获取包的依赖信息,以及递归查找每个依赖下嵌套依赖的版本信息,并将解析过的包和正在进行解析包呢用Set数据结构进行存储,这样就可以保证同一版本范围内的包不会进行重复的解析:

总之,经过解析包这一步之后呢,我们就已经确定了解析包的具体版本信息和包的下载地址。

获取包

这一步首先我们会检查缓存中是否有当前依赖的包,同时呢将缓存中不存在的包下载到缓存的目录中。但是这里有一个小问题需要大家思考一下:

比如: 如何去判断缓存中有当前的依赖包呢?

其实呢,在Yarn中会根据 cacheFolder+slug+node_modules+pkg.name 生成一个路径;判断系统中是否存在该path,如果存在证明已经有缓存,不用重新下载。这个path也就是依赖包缓存的具体路径。

那么对于没有命中的缓存包呢?在 Yarn 中存在一个Fetch队列,按照具体的规则进行网络请求。如果下载的包是一个file协议,或者是相对路径,就说明指向一个本地目录,此时会调用Fetch From Local从离线缓存中获取包;否则调用 Fetch From External 获取包,最终获取的结果使用 fs.createWriteStream 写入到缓存目录。

链接包

我们上一步已经把依赖放到了缓存目录,那么下一步,我们应该要做什么事情呢?是不是应该把项目中的依赖复制到node_modules目录下呢,没错;只不过此时需要遵循一个扁平化的原则。复制依赖之前, Yarn会先解析 peerDepdencies,如果找不到符合要求的peerDepdencies的包,会有 warning提示,并最终拷贝依赖到项目中。

imagepng

构建包

如果依赖包中存在二进制包需要进行编译,那么会在这一步进行。

原文转自:https://fe.ecool.fun/topic/afe26637-d92a-43be-a4e4-e55b1a6d3d02