Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate modules - NOT solvable by `npm dedupe` #5593

Closed
tomhicks-bsf opened this issue Aug 29, 2017 · 31 comments
Closed

Duplicate modules - NOT solvable by `npm dedupe` #5593

tomhicks-bsf opened this issue Aug 29, 2017 · 31 comments

Comments

@tomhicks-bsf
Copy link

@tomhicks-bsf tomhicks-bsf commented Aug 29, 2017

Do you want to request a feature or report a bug?
Bug

What is the current behavior?
Two identical sub-dependencies are being included twice in the build.

If the current behavior is a bug, please provide the steps to reproduce.
projectA depends on libA@^2.0.0, depA and depB.
depA and depB both depend on libA@^1.0.0

This results in a directory structure like this:

projectA
- node_modules
-- libA
-- depA
---- node_modules/libA
-- depB
---- node_modules/libA 

When the project is built, included in the bundle are:

libA@2.0.0
depA
depB
libA@1.0.0
libA@1.0.0

What is the expected behavior?
The included modules should be:

libA@2.0.0
depA
depB
libA@1.0.0

Please mention other relevant information such as the browser version, Node.js version, webpack version and Operating System.
webpack@3.5.5

I have run npm dedupe but it can't fix the problem. This is understandable, because npm doesn't have anywhere to "put" this lower-version dependency that is shared by both depA and depB. Previously I believe this was solved with the DedupePlugin which has since been turned into a no-op.

@sokra
Copy link
Member

@sokra sokra commented Aug 29, 2017

Why is this a bug?

webpack behaves like spec'ed. You installed the package twice, it's included twice.

@tomhicks-bsf
Copy link
Author

@tomhicks-bsf tomhicks-bsf commented Aug 29, 2017

Thing is, I didn't choose to install the package twice, and there is no way for me to "fix" this at the package manager level, as you've suggested in other issue threads.

Wepback used to solve this problem in v1 with DedupePlugin, so this seems like a regression of sorts. The docs say that the DedupePlugin is no longer needed, which implies that it will do some kind of deduping of its own.

@tomhicks-bsf
Copy link
Author

@tomhicks-bsf tomhicks-bsf commented Aug 29, 2017

Furthermore, this has the potential to cause sudden bumps in the package size, without much warning.

Let's say that I have an app using a popular lib, like lodash. Lots of dependencies (let's say 20) use lodash and they're all using compatible versions, let's say everyone is reuqesting ~1.1.0. My app requires ~1.1.0, and everything is great - I have 1 copy of v1.1.X in my bundle.

Next day, a version 1.2 of lodash is released, and I update my app to use it. Now, when my app builds, I end up with 1 copy of v1.2 and 20 copies of v1.1.X.

Whilst that may not be a bug, and sure, webpack might be behaving like specced, it's not very helpful from a tool that is supposed to help with building optimised versions of application bundles for the web.

@sokra
Copy link
Member

@sokra sokra commented Aug 29, 2017

You can try yarn install --flat which prevent duplicate packages.

@tomhicks-bsf
Copy link
Author

@tomhicks-bsf tomhicks-bsf commented Aug 29, 2017

Oh, we're using npm...

Plus, looking at the docs for yarn install --flat this means you literally cannot have multiple versions of the same package. So if the dependency bump went over a major version, it still wouldn't work.

So yarn install --flat is not a solution for this problem, unfortunately.

@TheLarkInn
Copy link
Member

@TheLarkInn TheLarkInn commented Aug 29, 2017

@tomhicks-bsf Do you have a suggestion on how we are to handle this with your information in mind? DedupePlugin cannot work with HarmonyModules. Plus it was a pretty hacky plugin to begin with and we will not resurface that kind of work back into webpack.

For the time being I'm personally happy to assist you in working with the module author to help PR the sub dependency so that it is at a similar stable version. Otherwise, we are happy to listen to ideas for how we might accomplish handling this.

@evan-scott-zocdoc
Copy link

@evan-scott-zocdoc evan-scott-zocdoc commented Aug 29, 2017

Would IgnorePlugin pointed at the second version of the package work?

@tomhicks-bsf
Copy link
Author

@tomhicks-bsf tomhicks-bsf commented Aug 29, 2017

@tomhicks-bsf
Copy link
Author

@tomhicks-bsf tomhicks-bsf commented Aug 29, 2017

It's clear from a few other threads I've read that you don't see vanilla webpack as "the npm bundler" which is fair enough. I don't know much beyond npm in the JS world so I don't know exactly what other problems exist.

As far as npm/yarn are concerned, however, incompatible versions of modules should be treated as totally different modules, as though they had different names. This is why the yarn install --flat solution will not work - that assumes that you can resolve a dependency to a single module by its name alone.

So, my suggestion for this would be to use a plugin or loader that is package-manager aware.

NPM and yarn will do their best to choose a single version of a dependency and reference that, but in this case it simply isn't possible. It would require some big changes to that system for it to allow multiple places for "shared" dependencies. One "top-level" place for shared dependencies isn't enough.

This means that in order for webpack to map from node_modules->bundle(s).js it will need to know how to share dependencies that are not shared in the file system. This basically means inspecting the package.json file of like-named dependencies to compare specific version numbers. Maybe this was how the DedupePlugin worked, and might be considered hacky. Another way would be to hash the file contents of any import (and every file system import referenced therein, come to think of it :/ ).

I can imagine some kind of NpmDedupePlugin that inspects packages and rewrites imports to solve this. But then again this has probably already been considered and probably isn't that simple...

@tomhicks-bsf
Copy link
Author

@tomhicks-bsf tomhicks-bsf commented Aug 29, 2017

I will put together a repo that demonstrates this issue in its simplest form. Give me a day to do this.

@cbengtson85
Copy link

@cbengtson85 cbengtson85 commented Nov 6, 2017

@tomhicks-bsf did you ever put together a demo repo, definitely interested in this issue.

@david0178418
Copy link

@david0178418 david0178418 commented Dec 7, 2017

Has there been any additional movement on this discussion in another ticket?

@terencechow
Copy link

@terencechow terencechow commented Dec 8, 2017

Has anyone got anywhere from this? Looking at my webpack stats I have the below stats (truncated to only show > 50kb id, name & size).

As you can see, I'm importing immutable js 3 times into webpack. This seems really unnecessary. Does this mean my webpack minimized bundle has 3 immutable.js' in it? Note that all of them are using the same version, hence the identical file size.

How can I fix this? Will forking those repos and making them 'peer-dependencies' result in deduplication?

id name size
280 './node_modules/react-dom/cjs/react-dom.production.min.js' 92733
679 './node_modules/moment/moment.js' 130196
690 './node_modules/draft-js/node_modules/immutable/dist/immutable.js' 142526
936 './node_modules/immutable/dist/immutable.js' 142526
1044 './node_modules/ua-parser-js/src/ua-parser.js' 50016
1184 './node_modules/draft-js-block-breakout-plugin/node_modules/immutable/dist/immutable.js' 142526
@RoboBurned
Copy link
Contributor

@RoboBurned RoboBurned commented Dec 8, 2017

@terencechow in your case when you have the same version of dependency this can be fixed by removing node_modules directory and installing modules again with npm or yarn command. Both commands will place immutable to /node_modules and this package will not be duplicated in sub-directories.

For other cases when we have 3 copies of dependency v1 and 3 copies of v2 I wrote a small plugin
https://github.com/RoboBurned/dedup-resolve-webpack-plugin
Note: It is completely untested and draft. Was used only in one project. It can brake all.

@franjohn21
Copy link
Contributor

@franjohn21 franjohn21 commented Dec 15, 2017

It doesn't look like it was ever provided, so I created a repo that shows a simple example of this issue: https://github.com/franjohn21/dedupe-example

FWIW the code being webpacked in that example does not use harmony modules, so ideally webpack 2/3 could maintain parity with webpack 1.

We consistently see higher (>10%) bundle sizes in production apps at NerdWallet when migrating from webpack 1 -> webpack 3 mostly due to this issue.

@maciej-gurban
Copy link

@maciej-gurban maciej-gurban commented Feb 9, 2018

Wouldn't solution the to this issue be rather trivial in nature? In the directory structure @tomhicks-bsf provided, the same library version is installed twice, and while you may disagree whether this should be done, there should be nothing complex about handling this scenario and bundling in the duplicated library only once.

All it would take for this to work is for webpack to check for modules' contents to verify whether two (or more) imported modules are in fact the same.

My blind guess is that path at which the module is being resolved in currently taken into account, and this is why the same version of a library would be bundled in twice instead of once.


Looks like it indeed used to work as indicated by the docs at https://github.com/webpack/docs/wiki/optimization#deduplication

@Moghul
Copy link

@Moghul Moghul commented Feb 19, 2018

I'd also love some sort of solution for this - I've got several packages with the same dependency and it's being loaded twice.

yarn install --flat has removed the duplicates from the subdirectories but they're still duplicated in the bundle.

@benthemonkey
Copy link
Contributor

@benthemonkey benthemonkey commented May 18, 2018

I was able to achieve the desired effect described in this issue with some creative use of the NormalModuleReplacementPlugin. Note that I tested this solution with webpack 3.

My project depends on immutable@3.8.1, draft-js and draft-js-plugins-editor. draft-js and draft-js-plugins-editor both depend on immutable@~3.7.4. Here's how I deduped immutable so that only two versions are included in my output:

new NormalModuleReplacementPlugin(/^immutable$/, resource => {
	if (resource.context.includes('node_modules/draft-js-plugins-editor')) {
		resource.request = 'draft-js/node_modules/immutable';
	}
});

I'm basically instructing webpack that any call to import ... from 'immutable' inside draft-js-plugins-editor should be replaced with import ... from 'draft-js/node_modules/immutable'.

@JKillian
Copy link

@JKillian JKillian commented May 22, 2018

We've run in to this issue also, where we need to have two major versions of a library included in our bundle but don't want to have multiple copies beyond those two. In our case, our app was using version 1.x of a large library (let's call it lib) and also depended on many packages, let's say a, b, and c, which also depended on 1.x of lib. When we bumped our app to 2.x, all of the sudden each of a, b, and c all pull in their own copy lib@1.x which made out bundle size increase by multiple megabytes.

@benthemonkey's solution looks clever, I'll have to give it a shot, but in the end it's a somewhat brittle hack that introduces complexity to the build. Some sort of officially supported solution to this issue (or a well-supported plugin) could be very beneficial.

Thanks for sharing your plugin work @RoboBurned, curious if other people have had any luck with it so far?

@maciej-gurban
Copy link

@maciej-gurban maciej-gurban commented May 22, 2018

I've solved it by using yarn workspaces. Does exactly what's expected - dedupe where versions match, but bundle different versions when they don't.

@JKillian
Copy link

@JKillian JKillian commented Jun 5, 2018

@maciej-gurban that's reasonable in some cases, but in other cases yarn workspaces doesn't resolve this issue. Say you have nine dependencies, three each depending on version 1.x, 2.x, and 3.x of some-lib. There's nothing yarn can do to not at a very minimum pull in three copies of one of those versions of some-lib.

@benthemonkey tried out your solution and it worked for me, though I'm a little scared what will happen when paths/versions of things change or someone else has to discover what's going on haha

@MikeRalphson
Copy link

@MikeRalphson MikeRalphson commented Jan 6, 2019

I seem to have this issue when using webpack in a mono-repo managed by lerna. Various packages within the mono-repo depend on the same version of the module yaml and it appears to be being included in the bundle more than once. Any advice would be welcomed.

@esr360
Copy link

@esr360 esr360 commented Jan 16, 2019

Was any official resolution to this issue ever suggested?

@mvayngrib
Copy link

@mvayngrib mvayngrib commented Mar 10, 2019

we've been using this in our react-native project: https://github.com/tradle/dedupe-deps
I've separated it out into a separate module in case anyone else wants to use it. It dedupes on the filesystem to avoid issues with symlinks/hard links/deduplication support during bundling

@petermikitsh
Copy link

@petermikitsh petermikitsh commented Apr 17, 2019

The least disruptive solution for me has been to take advantage of Webpack's resolve.alias option. I only have under a handful of large duplicated dependencies, so manual intervention works, but certainly isn't scalable.

I understand that Webpack's DedupePlugin has been deprecated/removed for awhile, but I don't understand what would have been too hacky about it. If I have two imports with the same name, and both package.json are the same version, and the file size and contents are identical... it seems like Webpack could safely deduplicate this without consequence.

What makes the problem even worse, though, is the difficulty in its identification. duplicate-package-checker-webpack-plugin won't detect multiple copies of the same version of a package, as discussed in darrenscerri/duplicate-package-checker-webpack-plugin#21.

So to really know this is occurring, you need to periodically inspect your stats.json with a tool like https://webpack.github.io/analyse/, sort by file size, and manually inspect. Since npm dedupe won't work in all cases, it seems reasonable to shift the responsibility to webpack.

@paglias
Copy link

@paglias paglias commented Oct 2, 2019

Any update on this one? We're seeing the same behavior and it's resulting in 600kb of duplicated code which is too much to be acceptable in our case

@sheijne
Copy link

@sheijne sheijne commented Oct 24, 2019

Having the same problem, got a mono repo with multiple packages, all packages depend on one core package which contains most of the dependencies. However after building every entry point as well as the vendor chunk contains the chunk that was supposed to be split, rendering splitChunks useless.

I would say this is a huge bug since the only real reason to split chunks is caching... I want to split a dependency from the rest of my bundle so it doesn't have to be redownloaded every time my bundle changes. However instead of reducing this problem webpack is now actually increasing it...

Result should be:
vendor.js
dep.js
app1.js
app2.js
app3.js

Reality is:
vendor.js
dep.js
dep.js
app1.js
dep.js
app2.js
dep.js
app3.js
dep.js

Then there are 2 projects, project 1 contains app1 and app2, project 2 contains app3. Now when I'm download project 1 in the browser I am actually including (and therefore downloading) dep.js FOUR times instead of one. Twice in vendor.js, once in app1.js, once in app2.js. When I download project 2 it includes dep.js THREE times, twice in vendor.js, once in app3.js.

Seems to me that this is a bug; in no way or form should webpack ever increase bundle size compared to the source (aside from boilerplating and such which is negligible). With this kind of behavior it actually makes no sense to use splitChunks because it's actually increasing my bundle and chunk sizes. I am inclined to remove it entirely and find some other solution. The only possibility I can still imagine is by splitting every single package from node_modules to a seperate file with the name and version of the package..

FYI: In my case these are the exact same module (only one version exists in the filesystem), only difference is that they are shared by multiple packages and therefore symlinked to those packages.

@burlakko
Copy link

@burlakko burlakko commented Dec 2, 2019

It actually seems to be NPM bug, not Webpack's.
Try yarn install instead of npm install.
Worked for me.

alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue May 19, 2020
Webpack relies on package managers to do module hoisting and doesn't have any deduping logic since version 4.

However relaying on package manager has a number of short comings, such as when having the same library with the same version laid out in different parts of the node_modules tree.

Example:
```
/node_modules/tslib@2.0.0
/node_modules/library-1/node_modules/tslib@1.0.0
/node_modules/library-2/node_modules/tslib@1.0.0
```

In the above case, in the final bundle we'll end up with 3 versions of tslib instead of 2, even though 2 of the modules are identical.

Webpack has an open issue for this webpack/webpack#5593 (Duplicate modules - NOT solvable by `npm dedupe`)

With this change we add a custom resolve plugin that dedupes modules with the same name and versions that are laid out in different parts of the node_modules tree.
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue May 19, 2020
Webpack relies on package managers to do module hoisting and doesn't have any deduping logic since version 4.

However relaying on package manager has a number of short comings, such as when having the same library with the same version laid out in different parts of the node_modules tree.

Example:
```
/node_modules/tslib@2.0.0
/node_modules/library-1/node_modules/tslib@1.0.0
/node_modules/library-2/node_modules/tslib@1.0.0
```

In the above case, in the final bundle we'll end up with 3 versions of tslib instead of 2, even though 2 of the modules are identical.

Webpack has an open issue for this webpack/webpack#5593 (Duplicate modules - NOT solvable by `npm dedupe`)

With this change we add a custom resolve plugin that dedupes modules with the same name and versions that are laid out in different parts of the node_modules tree.
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue May 19, 2020
Webpack relies on package managers to do module hoisting and doesn't have any deduping logic since version 4.

However relaying on package manager has a number of short comings, such as when having the same library with the same version laid out in different parts of the node_modules tree.

Example:
```
/node_modules/tslib@2.0.0
/node_modules/library-1/node_modules/tslib@1.0.0
/node_modules/library-2/node_modules/tslib@1.0.0
```

In the above case, in the final bundle we'll end up with 3 versions of tslib instead of 2, even though 2 of the modules are identical.

Webpack has an open issue for this webpack/webpack#5593 (Duplicate modules - NOT solvable by `npm dedupe`)

With this change we add a custom resolve plugin that dedupes modules with the same name and versions that are laid out in different parts of the node_modules tree.
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue May 20, 2020
Webpack relies on package managers to do module hoisting and doesn't have any deduping logic since version 4.

However relaying on package manager has a number of short comings, such as when having the same library with the same version laid out in different parts of the node_modules tree.

Example:
```
/node_modules/tslib@2.0.0
/node_modules/library-1/node_modules/tslib@1.0.0
/node_modules/library-2/node_modules/tslib@1.0.0
```

In the above case, in the final bundle we'll end up with 3 versions of tslib instead of 2, even though 2 of the modules are identical.

Webpack has an open issue for this webpack/webpack#5593 (Duplicate modules - NOT solvable by `npm dedupe`)

With this change we add a custom resolve plugin that dedupes modules with the same name and versions that are laid out in different parts of the node_modules tree.
filipesilva added a commit to angular/angular-cli that referenced this issue May 20, 2020
Webpack relies on package managers to do module hoisting and doesn't have any deduping logic since version 4.

However relaying on package manager has a number of short comings, such as when having the same library with the same version laid out in different parts of the node_modules tree.

Example:
```
/node_modules/tslib@2.0.0
/node_modules/library-1/node_modules/tslib@1.0.0
/node_modules/library-2/node_modules/tslib@1.0.0
```

In the above case, in the final bundle we'll end up with 3 versions of tslib instead of 2, even though 2 of the modules are identical.

Webpack has an open issue for this webpack/webpack#5593 (Duplicate modules - NOT solvable by `npm dedupe`)

With this change we add a custom resolve plugin that dedupes modules with the same name and versions that are laid out in different parts of the node_modules tree.
@nomad3k
Copy link

@nomad3k nomad3k commented May 22, 2020

I had to use resolve.alias to get around this issue.

    resolve: {
      extensions: [ '.js', '.jsx' ],
      alias: {
        'react': path.resolve(__dirname, '../../node_modules/react/')
      }
    },
@Davidhanson90
Copy link

@Davidhanson90 Davidhanson90 commented Jun 12, 2020

Are there any updates on this issue? Given webpack is about building optimized bundles it boggles the mind removing duplicate code is not part of that process.

We are suffering the same problems expressed here. The hashing approach seems like a nice way to resolve this but would need to be performant. @TheLarkInn does webpack do any other dedupe process?

@vankop
Copy link
Member

@vankop vankop commented Aug 9, 2020

duplicate of #985

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.