How module bundlers like webpack, rollup, handle two different versions of same dependency?

If, module1 depends on [email protected]

and module2 depends on [email protected]

Which version of button-component is loaded in final bundle?

What steps can we take to avoid duplicate modules in final build?

Answer

TL;DR: It is not the bundler. It is Mostly the package manager (NPM, etc.) that is responsible for providing dependencies which bundlers simply follow.

It is not really the bundler itself that working here. For third party dependencies, we are generally using NPM and thus node_modules in general.

Now job of the NPM is to keep dependency tree as flat as possible. Imagine following graph where your-code depends on module1 and module2. And, module1 itself is internally dependent on module2.

If your-code and module1 uses same dependency, then npm will keep it flat under top-level node_modules folder and as such bundler (Webpack/Rollup) will pick single version of the module

Now imagine the other scenario:

Here your-code is using module2 with version 2.0.0 but module1 is using different version. That is a breaking change. In this case, npm will not keep it flat and install [email protected] in you node_modules folder while [email protected] will be installed in a node_modules folder of module1. The Bundler will pick both the version when it is bundling the code.

But it is not all that script. Usually if dependencies only differ at patch version i.e. last digit of semver, then npm will pick only one and ignore the other. Further, this resolution of dependencies also depends upon the NPM versioning model. You can specify lenient version of dependency using ^1.0.5 or ~1.0.5. That also affect if NPM will install modules separately or keep it flat.

You can use bundler analyzer or equivalent to detect duplicate dependencies. When NPM was first released, it could not handle this and thus Bower was born to help developers with flat dependencies so that bundlers would pick only one version of dependency. But latest NPM is reasonably good enough for most cases and bower is no longer used.


Finally, I said Mostly because it is generally the package manager. But, in case of bare imports, you can teach/override bundler to specifically resolve from one and only one version. For example, consider Webpack resolution config below:

resolve: {
  // see below for an explanation
  alias: {
    preact: path.resolve('node_modules', 'preact')
  }
}

So, if you accidentally have preact more than once in your dependencies graph, then you can force Webpack to always use specific preact import from one specific folder.