依赖反转 依赖注入_欢迎来到依赖地狱

依赖反转 依赖注入

This post has been a collaboration between Noviny, who has provided the deep lore around how this all works, and Sarah Federman, who has helped craft it into something understandable and readable.

这篇文章是NovinySarah Federman之间的合作, Noviny提供了有关这一切工作原理的深刻知识, Sarah Federman帮助将其制作成了可以理解和理解的东西。

So you have a project, and you’ve just realized you aren’t depending on the versions of dependencies that you thought were. Maybe you’ve realized you have 4 versions of one package installed, or your bundle size is 10x what you think it should be. It’s time to learn about how lockfiles affect your dependency resolution!

因此,您有一个项目,并且您刚刚意识到自己并不依赖于您以前认为的依赖项版本。 也许您已经意识到您已经安装了4个版本的一个软件包,或者您的捆绑包大小是您预期的10倍。 现在该了解锁定文件如何影响您的依赖性解析!

A light warning before we begin: Quick fixes to these problems always have the potential for unintended side-effects, and thoroughly solving these problems is slow, manual, and requires a large amount of systemic knowledge. The costs are often higher than the rewards. However, it is good to learn these things so that if and when a problem arises, you have the tools to help.

在我们开始之前的一个警告 快速解决这些问题总是会产生意想不到的副作用,而彻底解决这些问题比较缓慢,需要人工,并且需要大量的系统知识。 成本通常高于报酬。 但是,最好学习这些东西,以便在出现问题时有帮助的工具。

Note: For simplicity, this article focuses on the yarn package manager for its examples, however most are compatible with any package manager. If you are using npm, you can substitute yarn.lock for package.lock .

注意:为简单起见,本文重点介绍yarn管理器的示例,但是大多数都与任何卷装管理器兼容。 如果使用npm ,则可以用yarn.lock代替package.lock

场景 (The Scenario)

We have a project that depends on pkg-a and pkg-c. Pkg-a and pkg-c both depend on pkg-b.

我们有一个项目取决于 pkg- apkg -cPkg -apkg -c都取决于pkg -c pkg- b

Following normal best practices, we are using a yarn.lock.

按照正常的最佳做法,我们正在使用yarn.lock

When we first install pkg-a and pkg-c into our project, both of their package.json's specify that they depend on "pkg-b": "^2.0.0". At the time of our install, the latest version of pkg-b on npm is 2.1.0. In order to understand what happens now, we need to understand a little of how yarn lock works.

当我们第一次安装pkg -a pkg -c进入我们的项目,它们的两个package.json都指定它们依赖于"pkg-b": "^2.0.0" 。 在我们安装时,npm上pkg -b的最新版本是2.1.0 。 为了了解现在发生的情况,我们需要了解一些纱线锁的工作原理。

纱锁101 (Yarn locks 101)

The basic premises of yarn locks is that each package that is used in our repository has an entry in our yarn.lock. An entry for a package tells us, given a semver range, which package version will end up being used. Every package has an entry in your yarn.lock, including dependencies of dependencies.

纱线锁的基本前提是,我们存储库中使用的每个程序包在yarn.lock都有一个条目。 给定一个semver范围,一个软件包条目会告诉我们最终将使用哪个软件包版本。 每个包在yarn.lock都有一个条目,包括依赖项的依赖项。

An entry can also specify multiple version ranges if packages specify different (but compatible) ranges:

如果软件包指定了不同(但兼容)的范围,则条目也可以指定多个版本范围:

If there are packages that depend on the same packages but at incompatible ranges, we will install multiple versions, and it will look something like this:

如果存在依赖于相同软件包但范围不兼容的软件包,我们将安装多个版本,其外观如下所示:

yarn.lock如何在安装时工作 (How yarn.lock works on install)

So back to our example. We’ve cloned a fresh repo and ran yarn install, which generated a yarn.lock file. Our project depends on pkg-a, and pkg-c, which both depend on pkg-b. Our yarn.lock now looks like this:

回到我们的例子。 我们已经克隆了一个新的repo并运行yarn install ,生成了yarn.lock文件。 我们的项目取决于pkg- apkg- c 这两个都取决于pkg- b 。 我们的yarn.lock现在看起来像这样:

What this says is that when yarn encounters a package that specifies it wants pkg-b@^2.0.0, the version that will be used will be exactly 2.1.0.

这就是说,当yarn遇到一个指定要pkg-b@^2.0.0的程序包时,将使用的版本恰好是2.1.0

更新软件包时, yarn.lock会发生什么情况 (What happens to your yarn.lock when you update a package)

Exciting! A new version of pkg-a is available and we want to update our repository to use it. We use yarn upgrade or yarn upgrade-interactive to update to the new version of pkg-a. There is no new version of pkg-c available, so we leave it as is.

令人兴奋! 有新版本的pkg- a ,我们想更新我们的存储库以使用它。 我们使用yarn upgradeyarn upgrade-interactive来更新到新版本的pkg- a 。 没有可用的新版本的pkg- c ,因此我们将其保留pkg- c

As part of this new version, the author of pkg-a decided to update their dependency on pkg-b from ^2.0.0 to ^2.1.0. So pkg-a now specifies they depend on pkg-b@^2.1.0 and pkg-c stays the same, specifying that it depends on pkg-b@2.0.0. At this point in time, the newest version of pkg-b on npm is 2.2.0, not 2.1.0 as it was when we originally installed.

作为此新版本的一部分, pkg- a的作者决定将其对pkg- b的依赖关系从^2.0.0^2.1.0 。 因此, pkg- a现在指定它们依赖于pkg-b@^2.1.0pkg- c保持不变,并指定它依赖于pkg-b@2.0.0 。 此时,npm上pkg- b的最新版本是2.2.0 ,而不是最初安装时的2.1.0

The semver ranges of both dependencies on pkg-b are still compatible, as it was only a minor update. What do you think will happen in our yarn.lock?

pkg- pkg- b的两个依赖项的semver范围仍然兼容,因为这只是一个较小的更新。 您认为我们的yarn.lock会发生什么?

If you expect pkg-a and pkg-b to continue to share the same version, you’d expect the yarn.lock to look something like this:

如果您希望pkg- apkg- b继续共享同一版本,则希望yarn.lock看起来像这样:

实际发生了什么 (What actually happens)

There is no existing yarn.lock entry for pkg-b@^2.1.0, which is what the new pkg-a specifies in its package.json, so yarn will create a new entry for ^2.1.0 which will use the latest compatible version on npm. Yarn won't change the version that the previous entry was using, because yarn wants to protect us from bugs arising from consuming new updates. Our yarn.lock now looks something like this:

没有现成的yarn.lock条目pkg-b@^2.1.0 ,这是什么新pkg- a指定它的package.json,那么纱线将创建一个新条目^2.1.0将使用最新的npm上的兼容版本。 Yarn不会更改上一个条目使用的版本,因为yarn希望保护我们免受使用新更新而引起的错误的影响。 我们的yarn.lock现在看起来像这样:

This is normally fine, and helps to protect us against bugs. However, it can mean that bundle sizes increase over time, especially in the case of larger dependencies (such as with a heavy editor package).

通常这很好,并有助于保护我们免受错误侵害。 但是,这可能意味着捆绑包的大小会随着时间的推移而增加,尤其是在依赖关系较大的情况下(例如带有沉重的编辑器包)。

如果我们删除yarn.lock然后安装yarn怎么办? (What if we delete our yarn.lock and then yarn install?)

If we delete our yarn.lock and then run yarn install, any packages that depend on pkg-b will update to the latest version available on npm that is compatible. However, it will also do this for any other packages across our repository, possibly introducing unintended bugs.

如果我们删除yarn.lock并运行yarn install ,则任何依赖于pkg- b软件包都将更新为npm上兼容的最新版本。 但是,它也会对我们存储库中的所有其他软件包执行此操作,可能会引入意外的错误。

您一直说“意外错误” (You keep saying “unintended bugs”)

Lockfiles are designed to keep us safe. Every time we change what version of a dependency is installed, we are running different code. If everyone has followed semver correctly, changing what is installed within semver-compatible ranges should be fine. However, errors are frequent enough that having a lockfile which ensures the same exact dependencies are installed every time will, in practicality, prevent us from encountering bugs due to dependencies changing.

锁定文件旨在确保我们的安全。 每次更改安装的依赖性版本时,我们都会运行不同的代码。 如果每个人都正确遵循了semver,则可以在与semver兼容的范围内更改安装的内容。 但是,错误的发生频率非常高,以至于拥有一个可确保每次都安装完全相同的依赖项的锁文件,实际上将防止我们因依赖项更改而遇到错误。

什么是重复数据删除 (What deduplication does)

Deduping or deduplicating is the practice of attempting to minimize shipping multiple versions of the same package, leading to smaller bundle sizes. If we were attempting to deduplicate, we would expect our yarn.lock to share versions of dependencies when their semver ranges were compatible. Yarn (v1) does this by default on install, but not on upgrades.

重复数据删除重复数据删除或正在尝试减少航运同一个包的多个版本,从而导致更小的尺寸捆绑的做法。 如果我们试图进行重复数据删除,那么我们希望我们的yarn.lock在它们的semver范围兼容时共享依赖版本。 默认情况下,Yarn(v1)在安装时执行此操作,但在升级时不执行此操作。

A powerful tool for accomplishing this is yarn-deduplicate. When we run yarn-deduplicate, it will change our yarn.lock to look like this (as we may have originally expected):

重复数据删除是实现此目的的强大工具。 当我们运行重复重复的纱线时,它将改变我们的yarn.lock看起来像这样(就像我们最初期望的那样):

This will only update entries for packages where we are currently shipping multiple compatible versions, leaving the rest of the entries as is. This is in contrast to deleting the yarn.lock, which will update all entries to the latest compatible version from npm in addition to the deduping that we want. Using yarn-deduplicate instead means we have a smaller surface area of introducing unintended bugs. You can also use yarn-deduplicate with the --packages or --scopes flags to dedupe specific packages or scopes (like @yourdesignsystem).

这只会更新我们当前正在运送多个兼容版本的软件包的条目,而其余条目保持不变。 这与删除yarn.lock相反,该文件将除了需要的重复数据删除yarn.lock ,还将所有条目从npm更新到最新的兼容版本。 相反,使用重复重复的纱线意味着我们可以引入较小的表面积来引入意外的错误。 您还可以将yarn-deduplicate与--packages--scopes标志一起使用,以对特定的程序包或范围进行重复数据删除(例如@yourdesignsystem )。

If you’re using yarn 2.2+, there is a command built in to the yarn CLI that is similar to yarn-deduplicate. The docs for that live here: https://yarnpkg.com/cli/dedupe. I haven’t played with this due to the non-backwards capability of yarn 2, so I can’t comment on how well it works, but it looks very close to yarn-deduplicate.

如果您使用的是yarn 2.2+,则会在yarn CLI中内置命令,该命令类似于yarn-deduplicate。 该文档位于此处: https : //yarnpkg.com/cli/dedupe 。 由于纱线2的非向后功能,我没有玩过它,所以我无法评论它的效果如何,但是看起来非常接近重复纱线。

使用纱线分辨率怎么样? (What about using yarn resolutions?)

Sometimes you will depend on a package which depends on another package, and when this package resolves to its highest semver compatible range, causes your code to error.

有时,您将依赖于一个程序包,而该程序包又依赖于另一个程序包,并且当该程序包解析到其最高的semver兼容范围时,将导致代码出错。

Here’s an example:

这是一个例子:

  • You depend on babel@^1.0.0

    您依赖babel@^1.0.0

  • Babel depends on foo@^1.0.0

    Babel取决于foo@^1.0.0

The latest of foo is 1.5.0 and when it is installed, our code breaks, but we know that if we install 1.4.9 instead, our code will run.

最新的foo1.5.0 ,安装时,我们的代码会中断,但是我们知道,如果安装1.4.9 ,我们的代码将运行。

We can’t set just the dependency of foo to foo@1.4.9, as babel controls its own dependencies. We can, however, use a yarn resolution in our package.json to force babel to use the version we want:

我们不能仅将foo的依赖项设置为foo@1.4.9 ,因为babel控制着它自己的依赖项。 但是,我们可以在package.json中使用纱线分辨率来强制babel使用所需的版本:

This will resolve babel’s version of foo to 1.4.9, separate to any other packages that depend on foo, no matter what range they specify.

这会将babelfoo版本解析为1.4.9 ,与依赖foo任何其他软件包分开,无论它们指定什么范围。

What if there are multiple packages using foo and we want to force all instances of them to resolve to 1.4.9?

如果有多个使用foo程序包并且我们要强制将它们的所有实例解析为1.4.9怎么办?

This method would resolve all instances of foo anywhere in our dependency tree to 1.4.9, and may introduce many other bugs. The only time we would want to do this is if we are trying to enforce a repository-wide singleton (such as react) and there is no other way to escape this.

此方法会将依赖树中任何位置foo所有实例解析为1.4.9 ,并且可能会引入许多其他错误。 我们唯一想做的是,如果我们要强制执行整个存储库范围内的单例(例如react),并且没有其他方法可以逃脱这种情况。

Do not use resolutions to deduplicate packages — this can force two packages that depend on incompatible semver ranges of a package (such as two different majors) to get the same version. This has a very high likelihood for causing hard-to-debug problems, often the kind of bugs that lead to painful node_modules spelunking.

不要用分辨率来重复数据删除包-这样可以强制将取决于封装(如两个不同专业)的不兼容semver范围两个包,以获得相同的版本。 这有一个非常 导致难以调试的问题的可能性很高,通常是导致痛苦的node_modules拼写的错误。

There is, however, one exception to the above rule, and it is an interesting hack.

但是,以上规则有一个例外,这是一个有趣的技巧。

临时纱线决议案 (The temporary yarn resolutions hack)

If we want to force a dependency’s dependency to resolve to a newer or specific version, there is an interesting way using yarn resolutions. This is not the intended usage of yarn resolution, but it technically can be used in this way. Let us continue with the previous example with our babel/foo package. Say we've set:

如果我们想强制某个依赖项的依赖项解析为更新的或特定的版本,则有一种使用纱线分辨率的有趣方法。 这不是yarn resolution的预期用途,但从技术上讲,可以这种方式使用。 让我们继续前面的例子,使用babel/foo包。 说我们设置了:

And we then run yarn install to update our yarn.lock. This, as we said above, is risky, as this means if we have different packages depending on different majors of foo, it would likely break because we force all versions to use exactly 1.4.9 no matter what they’ve specified in their package.json.

然后我们运行yarn install来更新yarn.lock 。 如上所述,这是有风险的,因为这意味着如果我们根据foo不同专业使用不同的软件包,则可能会中断,因为无论它们在软件包中指定了什么,我们都会强制所有版本1.4.9使用1.4.9 .json。

If we then delete the above resolutions field from our package.json and run yarn install again, dependencies of foo that are incompatible with 1.4.9 will be re-resolved to a compatible version. However, anything that was compatible with 1.4.9 will continue to install 1.4.9 instead of reverting back to the version we didn’t want.

如果我们然后从package.json中删除上述resolution字段并再次运行yarn install ,则与1.4.9不兼容的foo依赖项将重新解析为兼容版本。 但是,任何与1.4.9兼容的东西都将继续安装1.4.9而不是还原到我们不想要的版本。

Like any time you are updating the package versions in your yarn.lock, this can create unintended consequences for other packages that depend on foo at a compatible range, so some caution and testing is still needed.

就像您在yarn.lock中更新软件包版本的任何时候一样,这可能会对在兼容范围内依赖foo其他软件包产生意想不到的后果,因此仍然需要谨慎和测试。

Thank you to Nicolas Ronsmans for bringing this alternate approach to our attention!

感谢Nicolas Ronsmans将这种替代方法引起我们的注意!

每种方法都需要权衡: (Each approach has trade-offs:)

纱线升级或互动的纱线升级: (Yarn upgrade or yarn upgrade-interactive:)

  • Pro: This strategy has the lowest likelihood of introducing bugs when upgrading a package.

    优点:升级包时,此策略引入错误的可能性最低。

  • Con: Version spread. You will more than likely end up bundling multiple copies of packages that could have been deduplicated, and your bundle size may grow over time.

    缺点:版本传播。 您很有可能最终会捆绑可能已重复数据删除的软件包的多个副本,并且捆绑软件的大小可能会随着时间的推移而增长。

删除您的yarn.lock ,然后安装纱线以重新生成它 (Delete your yarn.lock and then yarn install to regenerate it)

  • Pro: You’ll get the latest versions that satisfy semver ranges from npm, and packages that can share a range will do so.

    Pro:您将从npm获得满足semver范围的最新版本,并且可以共享范围的软件包也可以。

  • Con: This is basically the same as not having a yarn.lock, so your packages will end up running different code when you reinstall and may introduce unintended bugs.

    yarn.lock 这基本上与没有yarn.lock相同,因此,重新安装时,您的程序包最终将运行不同的代码,并且可能会引入意外的错误。

重复纱线 (Yarn-deduplicate)

  • Pro: You will end up with less versions being bundled, and it also includes flexible options. It does not require a network connection as it uses the highest versions you already have, and it can be used in a CI step if desired.

    优点:最终捆绑的版本会更少,而且还包括灵活的选项。 它使用所需的最高版本,因此不需要网络连接,并且可以根据需要在CI步骤中使用。

  • Con: Packages you did not upgrade will use new versions, which can create a higher burden for testing if those new packages have failed at using semver correctly. It also involves adding a new CLI tool to your repo, if that’s something you care about.

    缺点:您没有升级的软件包将使用新版本,这会给测试新软件包是否无法正确使用semver带来更大的负担。 如果您对此很在意,还需要在您的存储库中添加新的CLI工具。

  • It’s also important to note that your results will depend on how it is used. You can run the library automatically (on precommit, for example) or choose to dedupe specific packages or scopes, leaving it as a more manual process. There’s also different deduping strategy options. Read the docs for more information.

    同样重要的是要注意,您的结果将取决于其使用方式。 您可以自动运行该库(例如,在预先提交时),也可以选择对特定的程序包或作用域进行重复数据删除,而将其留作手动操作。 也有不同的重复数据删除策略选项。 阅读文档以获取更多信息。

* These things probably apply to yarn dedupe in yarn 2, which I have not tried.

*这些事情可能适用于纱线2中的yarn dedupe ,我没有尝试过。

临时纱线分辨率破解 (Temporary yarn resolutions hack)

  • Pro: You can target deduping on package-per-package basis without installing additional libraries.

    优点您可以针对每个软件包进行重复数据删除,而无需安装其他库。

  • Con: This is not an intended use of yarn resolutions, and may still create unintended consequences. You must also remember to delete the resolution after running yarn install else everything is a mess. It’s a manual process, so you’ll need to pay attention to your bundle size and yarn.lock to know if deduping is necessary and where.

    缺点:这不是纱线分辨率的预期用途,并且可能仍会产生意想不到的后果。 您还必须记得在运行yarn install后删除分辨率,否则一切都是一团糟。 这是一个手动过程,因此您需要注意束的大小和yarn.lock以了解是否需要进行重复数据删除以及在何处进行重复数据删除。

Bonus round!

奖金回合!

如何使用对等依赖项? (What about using Peer dependencies?)

Peer Dependencies are incredibly difficult, and as a whole should be avoided unless there is no alternative. Using them to help with deduplication will lead you down a rabbit hole of future problems.

对等依赖非常困难,除非没有其他选择,否则应总体上避免对等依赖。 使用它们来帮助进行重复数据删除将使您陷入未来问题的困境。

什么是对等依赖性? (What is a peer dependency?)

A peer dependency is a dependency that a package requires, but is not installed by the package that requires it. So if we have a package that has a peer dependency on react at ^16.0.0, it will not work unless react is also installed by the consumer. If we wish to use a package that has a peer dependency on react, our package.json will need to look something like this:

对等依赖项是程序包需要的依赖项,但不是由需要它的程序包安装的。 因此,如果我们有一个在^16.0.0处有对等依赖于react^16.0.0 ,那么除非使用者也安装了^16.0.0 ,否则它将无法工作。 如果我们希望使用一个对React有对等依赖的包,我们的package.json将需要看起来像这样:

If we are using 5 packages with a peerDependency on react, we still only get one copy, as none of them will install their own version of react.

如果我们使用5包用peerDependency的react ,我们仍然只得到一个副本,因为他们没有将安装自己版本的React。

The problem with peerDependencies is that you can only depend on one version, even if different packages you depend on have peerDependencies on the same package but specify different version ranges. Say myPackageThatDependsOnReact updates its peerDependency requirements to react@^17.0.0, and is depending on things that are broken by the major change. We have installed react 16.0.0 to satisfy the other packages' peerDependencies, so we are now stuck. We cannot upgrade to the newest version of myPackageThatDependsOnReact until all four other packages also update their peerDependency and code to be compatible with react 17.0.0. With a peerDependency, everything is beholden to the lowest major version of the peerDependency in our repository.

peerDependencies的问题在于,即使您依赖的不同程序包在同一程序包上具有peerDependencies,但指定了不同的版本范围,您也只能依赖一个版本。 假设myPackageThatDependsOnReact其peerDependency要求更新为react@^17.0.0 ,并取决于被重大更改破坏的事情。 我们已经安装了react 16.0.0来满足其他软件包的peerDependencies的要求,所以现在我们陷入了困境。 在所有其他四个软件包也都更新了它们的peerDependency和代码以使其与react 17.0.0兼容之前,我们无法升级到myPackageThatDependsOnReact的最新版本。 有了peerDependency,一切都将遵循我们存储库中peerDependency的最低主要版本。

React is good to have as a peerDependency as it is a “singleton”. This means that loading multiple copies of the react library within a react app will cause it to crash. It is required that only one version of react be resolved within an app, so using a peer dependency enforces this.

作为一个“ peerDependency”,React很好,因为它是“单个”。 这意味着在react应用程序中加载react库的多个副本将导致其崩溃。 要求仅在应用程序内解析一个版本的react,因此使用对等依赖项可以强制执行此操作。

结论 (Conclusion)

Congratulations, you have walked into Dependency Hell and made it out alive! We hope you enjoyed learning some deep lore around npm dependencies and how to manage them. Now people will ask you questions about it forever. Good news, you can now send them this article!

恭喜,您已经进入“依赖地狱”并活了下来! 我们希望您喜欢学习有关npm依赖关系的深层次知识以及如何管理它们。 现在人们会永远问您有关它的问题。 好消息,您现在可以向他们发送这篇文章!

Tldr; is to avoid these issues if you can, but if you can’t, make sure that you understand the tradeoffs.

Tldr; 是为了避免这些问题,但如果不能,请确保您了解这些折衷方案。

Cheers! ✨✨

干杯! ✨✨

翻译自: https://medium.com/@Sarah_federman/welcome-to-dependency-hell-754a896f0440

依赖反转 依赖注入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值