Search code examples
node.jsnpmpackage.jsonyarnpkg-v2

How can I prevent Yarn from installing duplicate copies of identical packages


Using Yarn 3 aka Berry.

I'm having trouble with Yarn wanting to install multiple copies (not versions) of packages. That's trouble for me because these packages have stateful modules, so they might be initialized with some state in one copy of the package but then when I try to access those values Node reaches into the other copy of the package.

For example, imagine that package C has some stateful module I only want one copy of.

Code for A/package.json

{
  "version": "1.0.0",
  "name": "A",
  "dependencies": {
    "C": "1.0.0"
  }
}

Code for B/package.json

{
  "version": "1.0.0",
  "name": "B",
  "dependencies": {
    "C": "1.0.0"
  }
}

Project package.json file

{
  "version": "1.0.0",
  "name": "project",
  "dependencies": {
    "A": "1.0.0",
    "B": "1.0.0",
    "C": "1.0.0"
  }
}

Project's node_modules after running a yarn install

node_modules/
  - A
    - node_modules
      - C
        - package.json (v1.0.0)
        - <pkg code>
  - B
    - node_modules
      - C
        - package.json (v1.0.0)
        - <pkg code>
  - C
    - package.json (v1.0.0)
    - <pkg code>

I have used yarn's resolutions field to make sure the 'C' package has a resolution to the same version. I have tried yarn dedupe but that only seems to affect packages of different versions.

Is there anything I can do to change the node_modules structure to just install C at the top level only?


Solution

  • The only way I've been able to solve this is with Plug'n'Play (pnp). It's not well documented on the Yarn site, but there's a whitepaper for the feature available online that explains that perfect hoisting is guaranteed.

    What that means is that every package, except for packages with any peerDependencies will be instantiated exactly once.

    If a package has any peerDependencies then it's instantiated once for each peer dependency. The reasoning is explained in the whitepaper, but just know that if you have a package you need exactly 1 copy of (e.g. anything with state) then you can remove any peer deps and switch to pnp to solve this problem.

    That's a big change, but it's the only way I've made this work.