metro-plugin-duplicates-checker
@rnx-kit/metro-plugin-duplicates-checker
checks for duplicate packages in your
bundle.
Usage
There are several ways to use this package.
The recommended way is to add it as a plugin in your metro.config.js
using
@rnx-kit/metro-serializer
:
const { makeMetroConfig } = require("@rnx-kit/metro-config");
+const {
+ DuplicateDependencies,
+} = require("@rnx-kit/metro-plugin-duplicates-checker");
+const { MetroSerializer } = require("@rnx-kit/metro-serializer");
module.exports = makeMetroConfig({
serializer: {
+ customSerializer: MetroSerializer([DuplicateDependencies()]),
},
});
You can also check for duplicate packages after a bundle is created:
const {
checkForDuplicatePackagesInFile,
} = require("@rnx-kit/metro-plugin-duplicates-checker");
checkForDuplicatePackagesInFile(pathToSourceMapFile, {
ignoredModules: [],
bannedModules: [],
});
If you have a source map object, you can pass that directly to
checkForDuplicatePackages()
:
const {
checkForDuplicatePackages,
} = require("@rnx-kit/metro-plugin-duplicates-checker");
checkForDuplicatePackages(mySourceMap, {
ignoredModules: [],
bannedModules: [],
});
Options
Key | Type | Default | Description |
---|---|---|---|
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
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
:
"@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:
"@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:
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:
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
):
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: