(翻译)yarn 是如何提升依赖和处理包冲突

1,349 阅读6分钟

原文

如何进行依赖提升

yarn 通过递归解析从 package.json 中将所有依赖转换为依赖树。一个未经过优化的依赖树可能会非常复杂,依赖关系会有很多层级。一些包会在依赖树中出现很多次,可能会是互相冲突的版本。yarn 通过使用提升来删除尽可能多的节点来优化(去重)依赖树。

yarn 的官方文档里面写:没有一种方法可以决定如何去转换树,不同的包管理器会做出不同的判断(有些针对包流行度,大小,最高版本等等进行了优化)。出于这个原因,无法保证最终的提升结果,除非在 清单文件里始终可以访问到那些包。

文档里没有描述 yarn 的提升算法,但是提到了某些确定的提升保证,这些保证表明不应该在 node_modules 文件目录做一些特殊的布局。一些很小的依赖修改都会导致 node_modules 布局出现很大的变化。

另外需要注意的事,只有包的版本号在语义版本控制意义上兼容时才能被提升,也就是它们有相同的主版本号(但不是 0)

使用 yarn why 和 yarn.lock 去理解提升

一旦安装完成,你就可以使用 yarn why 去找到 yarn 具体是如何处理特定的包。

例如(注:下面都是 yarn v1):

# 执行
yarn why @maiertech/gatsby-theme-posts-core

# 输出
[1/4] Why do we have the module "@maiertech/gatsby-theme-posts-core"...?
[2/4] Initialising dependency graph...
[3/4] Finding dependency...
[4/4] Calculating file sizes...
=> Found "@maiertech/gatsby-theme-posts-core@0.8.0"
info Reasons this module exists
   - "@maiertech#gatsby-theme-digital-garden" depends on it
   - Hoisted from "@maiertech#gatsby-theme-digital-garden#@maiertech#gatsby-theme-posts-core"
info Disk size without dependencies: "108KB"
info Disk size with unique dependencies: "4.18MB"
info Disk size with transitive dependencies: "164.24MB"
info Number of shared dependencies: 374
Done in 1.61s.

上面的输出告诉了我们,安装的版本是 0.8.0,是 @maiertech#gatsby-theme-digital-garden 的依赖项,而且被提升了,因为只在整个项目中使用了一次。

再看另一个例子:

[1/4] Why do we have the module "@maiertech/gatsby-theme-pages-core"...?
[2/4] Initialising dependency graph...
[3/4] Finding dependency...
[4/4] Calculating file sizes...
=> Found "@maiertech/gatsby-theme-pages-core@0.5.0"
info Has been hoisted to "@maiertech/gatsby-theme-pages-core"
info This module exists because it’s specified in "dependencies".
info Disk size without dependencies: "76KB"
info Disk size with unique dependencies: "8.15MB"
info Disk size with transitive dependencies: "76KB"
info Number of shared dependencies: 379
=> Found "@made-up-scope/gatsby-theme-base#@maiertech/gatsby-theme-pages-core@0.4.0"
info This module exists because "@made-up-scope#gatsby-theme-base" depends on it.
info Disk size without dependencies: "72KB"
info Disk size with unique dependencies: "8.14MB"
info Disk size with transitive dependencies: "72KB"
info Number of shared dependencies: 379
Done in 6.32s.

这次 yarn 在依赖树中找到多个节点,一个是 package.json 指定的 0.5.0版本,另一个是 @made-up-scope/gatsby-theme-base 的依赖中指定的 0.4.0版本。0.5.0 的版本被提升了,因为两个的主版本号都是0,所以不能代替,需要同时存在两个版本。

yarn.lock

...
"@maiertech/gatsby-theme-pages-core@^0.4.0":
  version "0.4.0"
  resolved "https://registry.yarnpkg.com/@maiertech/gatsby-theme-pages-core/-/gatsby-theme-pages-core-0.4.0.tgz#d7226567f882c009c51415361666c90449637712"
  integrity sha512-ohzfpaL6Q4hZX0AZ8hS0tUqY+w6I6mr+cyYFTDqqAMPY3GBZPexFFgxCCspd1sAMJqaWgBmcDH6BkQYDXVymDA==
  dependencies:
  ...
"@maiertech/gatsby-theme-pages-core@^0.5.0":
  version "0.5.0"
  resolved "https://registry.yarnpkg.com/@maiertech/gatsby-theme-pages-core/-/gatsby-theme-pages-core-0.5.0.tgz#f8cc30886b85e635b3b0b952d6f488cfcb20f0ce"
  integrity sha512-x+A8idApFORR5vD8aTTDEqYi83OvcZL9Tdt168Cih9o+UbYWXb8ne+T+V7klHjnZ63UL0R4F908vo3yTeIVbNw==
  dependencies:
  ...
...

注意 yarn.lock 没有告诉我们哪些包被提升了。

再看一个例子:

yarn why v1.22.5
[1/4] Why do we have the module "browserslist"...?
[2/4] Initialising dependency graph...
[3/4] Finding dependency...
[4/4] Calculating file sizes...
=> Found "browserslist@4.16.3"
info Reasons this module exists
   - "gatsby" depends on it
   - Hoisted from "gatsby#browserslist"
   - Hoisted from "gatsby#autoprefixer#browserslist"
   - Hoisted from "gatsby#gatsby-legacy-polyfills#core-js-compat#browserslist"
   - Hoisted from "gatsby#babel-preset-gatsby#@babel#preset-env#@babel#helper-compilation-targets#browserslist"
   - Hoisted from "gatsby#optimize-css-assets-webpack-plugin#cssnano#cssnano-preset-default#postcss-colormin#browserslist"
   - Hoisted from "gatsby#optimize-css-assets-webpack-plugin#cssnano#cssnano-preset-default#postcss-merge-rules#browserslist"
   - Hoisted from "gatsby#optimize-css-assets-webpack-plugin#cssnano#cssnano-preset-default#postcss-minify-params#browserslist"
   - Hoisted from "gatsby#optimize-css-assets-webpack-plugin#cssnano#cssnano-preset-default#postcss-normalize-unicode#browserslist"
   - Hoisted from "gatsby#optimize-css-assets-webpack-plugin#cssnano#cssnano-preset-default#postcss-reduce-initial#browserslist"
   - Hoisted from "gatsby#optimize-css-assets-webpack-plugin#cssnano#cssnano-preset-default#postcss-merge-rules#caniuse-api#browserslist"
   - Hoisted from "gatsby#optimize-css-assets-webpack-plugin#cssnano#cssnano-preset-default#postcss-merge-longhand#stylehacks#browserslist"
info Disk size without dependencies: "124KB"
info Disk size with unique dependencies: "3.81MB"
info Disk size with transitive dependencies: "3.81MB"
info Number of shared dependencies: 5
Done in 0.96s.

yarn 告诉我们 browserslistgatsby 的直接依赖,还在依赖树中出现了好几次,所有的指定版本都和 4.16.3 兼容,因此 yarn 提升了 4.16.3 版本,yarn.lock 里面显示的 browserslist 版本都和4.16.3 兼容。

yarn.lock

...
browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.12.2, browserslist@^4.14.5, browserslist@^4.16.1:
  version "4.16.3"
  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
  integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
  dependencies:
    caniuse-lite "^1.0.30001181"
    colorette "^1.2.1"
    electron-to-chromium "^1.3.649"
    escalade "^3.1.1"
    node-releases "^1.1.70"
...

再看另一个例子,把 browserslist 版本固定到 4.16.2,这改变了很多东西:

[1/4] Why do we have the module "browserslist"...?
[2/4] Initialising dependency graph...
[3/4] Finding dependency...
[4/4] Calculating file sizes...
=> Found "browserslist@4.16.2"
info Has been hoisted to "browserslist"
info This module exists because it’s specified in "dependencies".
info Disk size without dependencies: "124KB"
info Disk size with unique dependencies: "3.81MB"
info Disk size with transitive dependencies: "3.81MB"
info Number of shared dependencies: 5
=> Found "gatsby#browserslist@4.16.3"
info This module exists because "gatsby" depends on it.
info Disk size without dependencies: "124KB"
info Disk size with unique dependencies: "3.81MB"
info Disk size with transitive dependencies: "3.81MB"
info Number of shared dependencies: 5
...
8 more instances omitted.
...
=> Found "stylehacks#browserslist@4.16.3"
info This module exists because "gatsby#optimize-css-assets-webpack-plugin#cssnano#cssnano-preset-default#postcss-merge-longhand#stylehacks" depends on it.
info Disk size without dependencies: "124KB"
info Disk size with unique dependencies: "3.81MB"
info Disk size with transitive dependencies: "3.81MB"
info Number of shared dependencies: 5
Done in 1.96s.

yarn 提升了 4.16.2 版本(固定的版本),在依赖树中还有其他实例(共10个)。yarn 存储了4.16.3 版本的拷贝,这意味着将一个依赖固定到一个特定版本会导致node_module 布局出现巨大变化。现在有10个节点是 4.16.3 版本的拷贝还有一个提升节点是 4.16.2 版本的拷贝(原来是一个提升节点是 4.16.3 版本的拷贝)。这个结果说明实际 node_modules 布局变化很可能不是你预想的那样。

yarn.lock

...
browserslist@4.16.2:
  version "4.16.2"
  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.2.tgz#f79d67cd37e8d80ff0835fe7bc456e406fb1582c"
  integrity sha512-oi5WJ1XukqFwgGsMxja1dySAzyWaXZqWSEWDedulO5M63JDw1rgGQbegfVZvxQyXLwkHm44xUbLsgP8C1iHeNg==
  dependencies:
    ...

browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.12.2, browserslist@^4.14.5, browserslist@^4.16.1:
  version "4.16.3"
  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
  integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
  dependencies:
    ...
...

总结

  • yarn 总是会尽可能的提升包来减少依赖树上的节点

  • 如果使用 ^ 指定依赖项版本,那么提升效果会最好,而越多使用固定版本,越会限制 yarn 提升包

  • 可以结合 yarn whyyarn.lock 来解决依赖冲突问题

  • 提升可能会隐藏缺失的依赖项,因为提升的依赖可能是项目任何地方导入的。如果你忘记在 package.json 中声明一个依赖然后导入它,碰巧在另一个节点中提升了这个依赖,它依然可以工作

  • yarn 始终遵循package.json 中的声明,即使在你导入一个依赖,yarn 始终会提供一个和指定版本兼容的版本

中英文对应

  • 提升 hoist

  • 依赖 dependencies

  • 依赖树 dependencies tree

  • packages

  • 清单文件 manifests package.json

  • 布局 layout

  • 主版本 major version