参考答案:
其实在最早期的npm版本(npm v2),npm的设计可以说是非常的简单,在安装依赖的时候会将依赖放到 node_modules文件中。同时,如果某个直接依赖A依赖于其他的依赖包B,那么依赖B会作为间接依赖,安装到依赖A的文件夹node_modules中,然后可能多个包之间也会有出现同样的依赖递归的,如果项目一旦过大,那么必然会形成一棵巨大的依赖树,依赖包会出现重复,形成嵌套地狱。
那么我们如何去理解"嵌套地狱"呢?
那么这样的重复问题会带来什么后果呢?
windows系统下删除node_modules文件,出现删除不掉的情况那么, 后面的版本是如何一步步进行优化的呢?后面会陆续的揭晓。
你在实际的开发会不会出现这样的一些情况:
node_modules 和 lockfiles依赖,再重新 npm install,删除大法是否真的好用?这样的使用方案会不会带来什么问题?dependencies中,对devDependencies 不区分会不会有问题?yarn,我使用npm,会不会有问题呢?lockfiles 文件 我们提交代码的时候需不需要提交到仓库中呢?我们可以先来看看 npm 的核心目标
Bring the best of open source to you, your team and your company.
意思是 给你和你的团队、你的公司带来最好的开源库和依赖。 通过这句话,我们可以了解到 npm 最重要的一点就是安装和维护依赖。那么,让我们先来看一看npm的安装机制是怎样的呢?
下面我们会通过一个流程图来具体学习npm install的安装机制

npm install执行之后,首先会检查和获取 npm的配置,这里的优先级为:
项目级的.npmrc文件 > 用户级的 .npmrc文件 > 全局级的 .npmrc > npm内置的 .npmrc 文件
然后检查项目中是否有 package-lock.json文件
如果有,检查 package-lock.json和 package.json声明的依赖是否一致:
package-lock.json中的信息,从网络或者缓存中加载依赖。如果没有,那么会根据package.json递归构建依赖树,然后就会根据构建好的依赖去下载完整的依赖资源,在下载的时候,会检查有没有相关的资源缓存:
node_modules文件中。node_modules中。最后,生成 package-lock.json 文件。
其实,在我们实际的项目开发中,使用npm作为团队的最佳实践: 同一个项目团队,应该保持npm 版本的一致性。
从上面的安装流程,不知道大家注意到了一点没有,在实际的项目开发中,如果每次都去安装对应依赖时,如果相关的依赖包体积过大或者是依赖于网络,无疑会增加安装的时间成本。那么,缓存在这里的就是一个解决问题的好办法。
yarn 是一个由Facebook、Google、Exponent和Tilde构建的新的JavaScript包管理器。它的出现是为了解决历史上npm的某些不足(比如npm对于依赖的完整性和一致性的保证,以及npm安装过程中速度很慢的问题)
当npm还处于v3时期的时候,一个叫yarn的包管理工具横空出世。在2016年,npm还没有package-lock.json文件,安装的时候速度很慢,稳定性很差,yarn的出现很好的解决了一下的一些问题:
确定性: 通过yarn.lock等机制,即使是不同的安装顺序,相同的依赖关系在任何的环境和容器中,都可以以相同的方式安装。(那么,此时的npm v5之前,并没有package-lock.json机制,只有默认并不会使用 npm-shrinkwrap.json)
采用模块扁平化的安装模式: 将不同版本的依赖包,按照一定的策略,归结为单个版本。以避免创建多个版本造成工程的冗余(目前的npm也有相同的优化)
网络性能更好: yarn采用了请求排队的理念,类似于并发池连接,能够更好的利用网络资源;同时也引入了一种安装失败的重试机制。
采用缓存机制,实现了离线模式 (目前的npm也有类似的实现)
我们可以来看一下 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的配合。
上面一小节我们对npm的安装机制有了一些基本的了解,现在让我们先来简单的看一下Yarn的安装理念。
简单来说, Yarn的安装大致分为5个步骤:

检测(checking) ---> 解析包(Resolving Packages) ---> 获取包(Fetching) ---> 链接包(Linking Packages) ---> 构建包(Building Packages)
那么接下来我们要开始具体分析这些过程中都做了哪些事情:
检测包
这一步,最主要的目的就是检测我们的项目中是否存在npm相关的文件,比如package-lock.json等;如果有,就会有相关的提示用户注意:这些文件可能会存在冲突。在这一步骤中
也会检测系统OS, CPU等信息。
解析包
这一步会解析依赖树中的每一个包的信息:
首先呢,获取到首层依赖: 也就是我们当前所处的项目中的package.json定义的dependencies、devDependencies、optionalDependencies的内容。
紧接着会采用遍历首层依赖的方式来获取包的依赖信息,以及递归查找每个依赖下嵌套依赖的版本信息,并将解析过的包和正在进行解析包呢用Set数据结构进行存储,这样就可以保证同一版本范围内的包不会进行重复的解析:
yarn.lock中获取版本信息,并且标记为已解析yarn.lock中没有找到包A, 则向Registry发起请求获取满足版本范围内的已知的最高版本的包信息,获取之后将该包标记为已解析。总之,经过解析包这一步之后呢,我们就已经确定了解析包的具体版本信息和包的下载地址。

获取包
这一步首先我们会检查缓存中是否有当前依赖的包,同时呢将缓存中不存在的包下载到缓存的目录中。但是这里有一个小问题需要大家思考一下:
比如: 如何去判断缓存中有当前的依赖包呢?
其实呢,在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提示,并最终拷贝依赖到项目中。

构建包
如果依赖包中存在二进制包需要进行编译,那么会在这一步进行。
最近更新时间:2024-08-10

题库维护不易,您的支持就是我们最大的动力!