美文网首页
yarn安装依赖及扁平化原理

yarn安装依赖及扁平化原理

作者: 大嵩的格洛米 | 来源:发表于2021-08-24 13:08 被阅读0次

    缓存

    yarn 会将安装过的包缓存下来,这样再次安装相同包的时候,就不需要再去下载, 而是直接从缓存文件中直接copy进来。
    可以通过命令查看yarn的全局缓存目录。
    yarn cache dir

    image.png
    查看对应文件夹下的缓存
    image.png

    离线镜像

    如果你以前安装过某个包,再次安装时可以在没有任何互联网连接的情况下进行。yarn的离线镜像是为了在无网络情况下使用的,是在本地维护了一个镜像,默认是不开启的。

    Lock

    我们需要保证在不同的机器上安装包能获得相同的结果,所以就有了锁的概念。
    yarn 在安装期间,只会使用当前项目的yarn.lock文件(即顶级yarn.lock文件),会忽略任何依赖里面的 yarn.lock 文件。
    yarn 安装依赖的过程


    image.png

    「Checking」
    在正式安装前,yarn会做一些check工作,会检查是否有npm的一些配置文件(Shrinkwrap,npm lockfile),如果有,会提示用户避免存在这些文件,可能会导致冲突。之后会去检查一些Manifest,包括os,cpu,engines,模块兼容等配置项。

    「Resolving Packages」
    解析包的信息,在这一步,会解析出依赖树中每个包的具体版本信息

    「Fetching Packages」
    获取依赖包,这一步,会对缓存中没有的包进行下载,将对应package下载到缓存目录下,完成这一步,代表着依赖树中需要的所有包都存在缓存当中了

    「Linking Packages」
    这一步,是将缓存中的对应包扁平化的安装到项目的依赖目录下(一般为node_modules)

    「Building Packages」
    对于一些二进制包,需要进行编译,在此时进行

    Resolving Packages

    image.png

    Resolving Packages,主要是解析依赖树中的每个package的包版本信息。

    首先,从当前项目的package.json中获取首层依赖,首层依赖包括dependences、devDependences、optionalDependences

    遍历首层依赖,调用find方法获取依赖包的版本信息,然后递归调用find,查找每个依赖下的dependence中依赖的版本信息。在解析包的同时使用一个Set(fetchingPatterns)来保存已经解析和正在解析的package。

    在具体解析每个package时,首先会根据其name和range(版本范围)判断当前package是否为被解析过(即resolved)(通过判断是否存在于上面维护的set中,即可确定是否已经解析过)

    对于未解析过的包,首先尝试从lockfile中获取到精确的版本信息, 如果lockfile中存在对于的package信息,获取后,标记成resolved(已解析)。如果lockfile中不存在该package的信息,则向registry发起请求获取满足range的已知最高版本的package信息,获取后,将当前package标记为resolved

    对于已解析过的包,则将其放置到一个延迟队列(delayedResolveQueue)中先不处理

    当依赖树的所有package都递归遍历完成后,再遍历delayedResolveQueue,在已经解析过的包信息中,找到最合适的可用版本信息

    Resolving Packages 结束后,我们就确定了依赖树中所有package的具体版本,以及该包地址等详细信息。

    Fetching Packages

    image.png

    Fetching Packages,主要是对缓存中没有的package进行下载。

    已经在缓存中存在的package,是不需要重新下载的,所以第一步先过滤掉本地缓存中已经存在的package。过滤过程是根据cacheFolder+slug+node_modules+pkg.name生成一个path,判断系统中是否存在该path,如果存在,证明已经有缓存,不用重新下载,将它过滤掉。

    维护一个fetch任务的queue,根据Resolving Packages中解析出的包下载地址去依次获取包。

    在下载每个包的时候,首先会在缓存目录下创建其对应的缓存目录,然后对包的reference地址进行解析。

    如果reference是file协议,或者是相对路径,则说明其指向的是本地目录(即离线镜像),调用fetchFromLocal从离线缓存中获取包,否则调用fetchFromExternal到外部(registry) 获取包。

    将获取的package文件流通过fs.createWriteStream写入到缓存目录下,缓存下来的是.tgz压缩文件,再解压到当前目录下

    下载解压完成后,更新lockfile文件

    Linking Packages

    image.png

    经过Fetching Packages后,我们本地缓存中已经有了所有的package,接下来就是如何将这些package复制到我们项目中的node_modules下。

    在复制包之前,会先解析peerDependences,如果找不到匹配的peerDependences,进行warning提示

    之后对依赖树进行扁平化处理,生成要拷贝到的目标目录dest

    对扁平化后的目标dest进行排序(使用localeCompare本地排序规则)

    根据flatTree中的dest(要拷贝到的目标目录地址),src(包的对应cache目录地址)中,执行将copy任务,将package从src拷贝到dest下。

    在Fetching Packages中,最核心的就是如何生成扁平化的dest目录。
    下面假设我们有如下的依赖关系树:


    image.png

    那么,如果我们没有进行扁平化的话,安装后的目录如下(#:代表安装到其前面package的node_modules下):


    image.png

    可以想象在安装A#D包(即在A的node_modules下安装D)的时候,因为根node_modules下只安装了A、B、C,没有D包,所以D是可以安装到根node_modules下的。

    同理,在安装A#C时,由于根node_modules下已经存在C,所以这里不能够提升,只能将C安装到A的node_modules下。

    在整个目录的提升过程中,会通过一个map来维护提升后的安装目录,在对每个安装路径进行分析时,会判断其是否存在于这个map中,来判断其安装的位置是否可以进行提升。

    同理,其他的包安装过程也是上面这样,下面以最复杂的A#D#B#F来说明:

    image.png

    这样,我们扁平化后的安装目录就变成了:

    image.png

    然后按照我们上面的第3步,使用localeCompare本地排序规则进行排序,结果为:


    image.png

    小结

    安装过程到这里就结束啦,现在回过头来看看「工作中常见的问题 」,是否还有疑惑呢?下面给出答案哈,如果还有疑惑欢迎留言讨论~

    安装依赖出现问题时,删除yarn.lock,再重新install,这样操作有风险吗?

    「有风险」

    开发时,我把所有依赖都安装到dependence或devDependence中,会有问题吗?

    「普通工程中,只是不规范,不会导致问题;如果是提供给其他项目的依赖包,会有问题」

    项目和这个项目的某个依赖都依赖了同一个包,这个包会被多次安装、重复打包吗? 「看版本范围是否在一个范围内,同一个版本范围,不会重复,否则会」
    开发同一个项目,有的同学使用yarn,有的同学使用npm,有问题吗?

    「有,锁文件不同,可能导致最终安装版本不一致」

    原文链接:https://blog.csdn.net/yuqing1008/article/details/107328920

    相关文章

      网友评论

          本文标题:yarn安装依赖及扁平化原理

          本文链接:https://www.haomeiwen.com/subject/byddiltx.html