docs(metro-plugin-duplicates-checker): add section about deduping (#2941)

This commit is contained in:
Tommy Nguyen 2024-02-05 17:13:46 +01:00 коммит произвёл GitHub
Родитель 7aae48f047
Коммит f82018c955
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
2 изменённых файлов: 170 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,2 @@
---
---

Просмотреть файл

@ -61,3 +61,171 @@ checkForDuplicatePackages(mySourceMap, {
| bannedModules | string[] | `[]` | List of modules that are banned. |
| ignoredModules | string[] | `[]` | List of modules that can be ignored. |
| throwOnError | boolean | `true` | Whether to throw when duplicates are found. |
## Resolving Duplicates
So you have duplicates in your bundle, now what? Depending on your specific
needs, we have several options.
Let's use a specific example:
```
error @fluentui-react-native/text (found 2 copies)
warn 0.21.14 /~/node_modules/@fluentui-react-native/text
warn 0.22.7 /~/node_modules/@fluentui-react-native/link/node_modules/@fluentui-react-native/text
```
This one occurs because `@fluentui-react-native/link` declares a dependency on
`@fluentui-react-native/text` using a wide version range, `>=0.21.14 <1.0.0`.
Our project depends on `@fluentui-react-native/text@^0.21.14`, but package
managers will typically still try to resolve `>=0.21.14 <1.0.0` instead of using
the existing resolution. This results in the duplicate error we see above.
From here, we have several options:
- [Manual Dedupe](#manual-dedupe)
- [Using Tools](#using-tools)
- [Help Metro Resolve Correct Version](#help-metro-resolve-correct-version)
- [Last Resort: Force Resolution](#last-resort-force-resolution)
### Manual Dedupe
This method is error-prone, especially if you have a lot of duplicates. It works
if you have very few entries. It varies a lot depending on the package manager
you're using.
The project in our example uses Yarn Classic: Open `yarn.lock` and look for
`@fluentui-react-native/text`:
```yaml
"@fluentui-react-native/text@0.21.14", "@fluentui-react-native/text@^0.21.14":
version "0.21.14"
resolved "https://registry.yarnpkg.com/@fluentui-react-native/text/-/text-0.21.14.tgz#04918a9558770ec551cbdac87ca1534bfccaeffb"
integrity sha1-BJGKlVh3DsVRy9rIfKFTS/zK7/s=
dependencies:
"@fluentui-react-native/adapters" ">=0.11.3 <1.0.0"
"@fluentui-react-native/framework" "0.11.10"
"@fluentui-react-native/interactive-hooks" ">=0.24.12 <1.0.0"
"@fluentui-react-native/theme-tokens" ">=0.25.4 <1.0.0"
"@fluentui-react-native/tokens" ">=0.21.6 <1.0.0"
"@uifabricshared/foundation-compose" "^1.14.12"
tslib "^2.3.1"
"@fluentui-react-native/text@>=0.21.14 <1.0.0":
version "0.22.7"
resolved "https://registry.yarnpkg.com/@fluentui-react-native/text/-/text-0.22.7.tgz#bd11768d3cd69337ad2ec4be76ee88d6749ca24f"
integrity sha1-vRF2jTzWkzetLsS+du6I1nScok8=
dependencies:
"@fluentui-react-native/adapters" ">=0.12.0 <1.0.0"
"@fluentui-react-native/framework" "0.13.6"
"@fluentui-react-native/interactive-hooks" ">=0.25.7 <1.0.0"
"@fluentui-react-native/theme-tokens" ">=0.26.5 <1.0.0"
"@fluentui-react-native/tokens" ">=0.22.5 <1.0.0"
"@uifabricshared/foundation-compose" "^1.14.20"
tslib "^2.3.1"
```
We can see there are two entries, one being resolved to 0.21.14 and the other to
0.22.7. In this case, since we want to keep 0.21.x and the version ranges just
happens to be satisfied by this. We can merge the two entries like below:
```yaml
"@fluentui-react-native/text@0.21.14", "@fluentui-react-native/text@>=0.21.14 <1.0.0", "@fluentui-react-native/text@^0.21.14":
version "0.21.14"
resolved "https://registry.yarnpkg.com/@fluentui-react-native/text/-/text-0.21.14.tgz#04918a9558770ec551cbdac87ca1534bfccaeffb"
integrity sha1-BJGKlVh3DsVRy9rIfKFTS/zK7/s=
dependencies:
"@fluentui-react-native/adapters" ">=0.11.3 <1.0.0"
"@fluentui-react-native/framework" "0.11.10"
"@fluentui-react-native/interactive-hooks" ">=0.24.12 <1.0.0"
"@fluentui-react-native/theme-tokens" ">=0.25.4 <1.0.0"
"@fluentui-react-native/tokens" ">=0.21.6 <1.0.0"
"@uifabricshared/foundation-compose" "^1.14.12"
tslib "^2.3.1"
```
Now we should only have one copy of `@fluentui-react-native/text`.
Our example is relatively simple. Sometimes you have to go further up the
dependency chain and dedupe dependees.
Fortunately, we can use tools in most cases.
### Using Tools
If you're using Yarn Classic, there is a tool, [`yarn-deduplicate`][], for
deduplicating everything in `yarn.lock`. You can run it like so:
```sh
npx yarn-deduplicate
```
By default, it will try to dedupe to the highest version. In our example,
however, we want to keep using 0.21.x. We should also limit the number of
packages that get deduped to make it easier to review later. `yarn-deduplicate`
provides many options, but we'll be using the `fewer` strategy and the
`--scopes` flag to only target `@fluentui-react-native` packages:
```sh
npx yarn-deduplicate --strategy fewer --scopes @fluentui-react-native
```
You can read more about `yarn-deduplicate` and available options here:
https://github.com/scinos/yarn-deduplicate#readme
If you're using modern Yarn or other package managers, check out their built-in
dedupe command:
- **Yarn:** https://yarnpkg.com/cli/dedupe
- **npm:** https://docs.npmjs.com/cli/commands/npm-dedupe
- **pnpm:** https://pnpm.io/cli/dedupe
### Help Metro Resolve Correct Version
If, for some reason, you cannot dedupe the package, you can instead configure
Metro to resolve the correct version by telling it which version to use (with
[`resolver.extraNodeModules`][]) and blocking every other copy (with
[`resolver.blockList`][]):
```js
module.exports = {
resolver: {
extraNodeModules: {
"@fluentui-react-native/text":
"/~/my-project/node_modules/@fluentui-react-native/text",
},
blockList: [
/(?<!\/~\/my-project)\/node_modules\/@fluentui-react-native\/text\/.*/,
],
},
};
```
You don't have to write these entries manually. We have helper functions in
[`@rnx-kit/metro-config`][] for generating them dynamically.
### Last Resort: Force Resolution
As a last resort, if everything else fails, you can manually override package
resolutions. We don't recommend this solution because it forces a single version
in all workspaces. For example, if you have two projects that have nothing to do
with each other, they will both be forced to use the same version even though
they could be using different versions. It also adds maintenance overhead
because you will have to manually update the version as dependees update theirs.
If you've decided that you have no other options, you can find the appropriate
documentation for your package manager below:
- **Yarn:** https://yarnpkg.com/configuration/manifest#resolutions
- **npm:** https://docs.npmjs.com/cli/v10/configuring-npm/package-json#overrides
- **pnpm:** https://pnpm.io/package_json#pnpmoverrides
<!-- References -->
[`@rnx-kit/metro-config`]:
https://github.com/microsoft/rnx-kit/blob/main/packages/metro-config/README.md#ensuring-a-single-instance-of-a-package
[`resolver.blockList`]: https://metrobundler.dev/docs/configuration/#blocklist
[`resolver.extraNodeModules`]:
https://metrobundler.dev/docs/configuration/#extranodemodules
[`yarn-deduplicate`]: https://github.com/scinos/yarn-deduplicate