webpack: webpack@4 Tree Shaking doesn't eliminate code between multiple entry points in same configuration object.

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

Bug.

What is the current behavior?

By way of example, I have:

  • util.js: ES exports two methods: red and blue via two scenarios: one-file (all code within one file) and re-export (methods re-exported from external files)
  • app1.js: Imports and uses only red
  • app2.js: Imports and uses only blue

Expected: If I build webpack bundles for both apps, app1 will have red and blue will be eliminated. Conversely, app2 will have blue and red will be eliminated:

Actual: Depends on config

  1. Array of Configs: If there is an array of configurations with a single entry point each for app1.js and app2.js the correct unused methods are dropped.
  2. Multiple Entry Points: If there is a single configuration object with multiple entry points for both app1.js and app2.js, then neither blue nor red are eliminated in either bundle. I would consider this a bug. Perhaps there is some known thing for “multiple entry points get any esnext exports used by any other entry point”, but that seems weird.

If the current behavior is a bug, please provide the steps to reproduce.

We made a repository with passing and failing mocha tests to show the situation: https://github.com/FormidableLabs/webpack-tree-shaking-multiple-entry-points.

Reproduction:

$ git clone https://github.com/FormidableLabs/webpack-tree-shaking-multiple-entry-points.git
$ cd webpack-tree-shaking-multiple-entry-points
$ yarn install
$ yarn run build
$ yarn run test

The test output is:

  tree shaking in webpack4
    array
      one-file
        ✓ app1 should have red, not blue
        ✓ app2 should have blue, not red
      re-export
        ✓ app1 should have red, not blue
        ✓ app2 should have blue, not red
    multiple-entries
      one-file
        1) app1 should have red, not blue
        2) app2 should have blue, not red
      re-export
        3) app1 should have red, not blue
        4) app2 should have blue, not red


  4 passing (22ms)
  4 failing

The failing ones show that the ES methods that should be dropped aren’t.

What is the expected behavior?

See above section on current behavior.

If this is a feature request, what is motivation or use case for changing the behavior?

Nope, it’s a bug.

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

$ node --version
v8.4.0

$  $ yarn list webpack
yarn list v1.3.2
└─ webpack@4.0.0-beta.1

$ uname -a
Darwin small.lan 16.7.0 Darwin Kernel Version 16.7.0: Thu Jan 11 22:59:40 PST 2018; root:xnu-3789.73.8~1/RELEASE_X86_64 x86_64

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 52
  • Comments: 52 (13 by maintainers)

Most upvoted comments

Happy issue 2nd year anniversary! Any update?

@vankop – Yay! 🎉

I upgraded from beta 22 to 24 in https://github.com/FormidableLabs/webpack-tree-shaking-multiple-entry-points/tree/chore/webpack5

diff --git a/package.json b/package.json
index 4c06edc..3f6ffa3 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
     "mocha": "^8.0.1",
     "pify": "^5.0.0",
     "uglifyjs-webpack-plugin": "^2.2.0",
-    "webpack": "^5.0.0-beta.22",
+    "webpack": "^5.0.0-beta.24",
     "webpack-cli": "^3.3.1"
   },
   "scripts": {

and finally had the comparison tests pass (for array of configs which always worked vs single configs which never did until now):

  tree shaking in webpack4
    array
      one-file
        ✓ app1 should have red, not blue
        ✓ app2 should have blue, not red
      re-export
        ✓ app1 should have red, not blue
        ✓ app2 should have blue, not red
    multiple-entries
      one-file
        ✓ app1 should have red, not blue
        ✓ app2 should have blue, not red
      re-export
        ✓ app1 should have red, not blue
        ✓ app2 should have blue, not red

Thanks!

Or to restate the bug in the simplest terms:

module.exports = {
  entry: {
    app1: "./app1.js",
    app2: "./app2.js"
  }
}

If app1 uses only ESM import red, and app2 uses only ESM import blue, with both entry points in the above configuration, both built entry point bundles contain the code of both red and blue.

My original error reproduction repository encompassed both “one-file” tree-shaking and “multiple file re-exports”. I just updated to:

"webpack": "^4.30.0",
"webpack-cli": "^3.3.1"

and everything still fails tree-shaking for both of those as expected: https://github.com/FormidableLabs/webpack-tree-shaking-multiple-entry-points (see src/one-file vs. src/re-export)

Would love a webpack core team member to see if there’s anything that can be done about this very, very longstanding issue. Thanks!

Sorry it’s convoluted – the scenarios array is purely for my convenience to make sure that the exports coming from the same file vs. other imported files doesn’t make any difference. (The scenarios confirm there is indeed no difference – we get DCE failures both ways).

Single Config Fails DCE

Here’s what doesn’t work and what I’m guessing is the preferred way to declare entry points:

module.exports = {
  entry: {
    app1: "./app1.js",
    app2: "./app2.js"
  }
}

That fails for DCE / tree shaking.

Array Config Works for DCE

This is what works, although I don’t like it:

module.exports = ["app1", "app2"].map((name) => ({
  entry: {
    [name]: `./${name}.js`
  }
}));

And if indeed this version de-optimizes and everything then we probably have an even bigger bug as you now have to choose between (1) DCE not working completely and (2) other de-optimizations 😉

We also see this issue (in webpack 4.17.0) and I agree, that most of the non SPA apps should suffer from this issue.

I’m wondering if it should end up at least in webpack 5.0 milestone, if the problem is in webpack core and can not be easily fixed.

Also stumbled on this.

@sokra on #5954 mentions to use multiple compilation passes, but that’s absurd.

For instance, I’m using webpack-manifest-plugin and optimization.runtimeChunk and multi compilation messes it all up.

On top of that, as mentioned by @loveky in #5954, with multiple compilations, there is also a problem with vendor extraction.
Multi compilation entry points will result in multiple vendor bundles.
It raises a question, what if rules for extraction are dynamic and lead to different vendor bundles (which webpack 4 with splitChunks actually has by default)? All of a sudden dead code elimination becomes dead code generation.

This seems to be a critical bug to me.

Sadly, the project we are working on is just being started to be rebuilt, we are strictly tied to multi page approach. The frontend is tightly coupled to backend and vice versa. Not to mention that current frontend is legacy AF. We are incrementally working on core updates, but that means that we are tied to multi page application and therefore multiple entry points. 😦

P.S.
It adds up enormously if using lots of modules, especially third party ones. And from what I’ve gathered, TS and DCE have been created in response to the observed pattern of heavily modular/CBD approach and code reusability.
How it is now, it kind of beats the purpose.

P.P.S.
Haha, skimmed through the code to find what could cause this, but damn webpack is deep. 😄 Will give it another shot later on too. This MUST get resolved!
Unless everyone is working with micro-services and have separated EVERYTHING, I feel like this, in reality, affects more people than they know.
For instance, one entry for client-side, one for administration; both depend on the same base set of modules, but not strictly 1:1… I can see how code from administration spills into client-side and vice versa, not nice. That’s just one extra use case.

$ node --version
v10.1.0
$ npm list webpack
/Users/tomo/projects/mu-laravel-webpack4-typescript-react
└── webpack@4.12.0

Should work with webpack@next. Feel free to report new issue with reproducible repo.

@TrySound but then you would need to support webpack and rollup builds within apps, which would be very unconvenient

plus, it is not only HMR which rollup is missing, there are many webpack goodies like different kinds of imports, like resolve weak etc which allows server side rendering together with code splitting which would not be possible otherwise. for instance - https://www.smooth-code.com/open-source/loadable-components/docs/server-side-rendering/ - which requires webpack plugin to work

I am sure there are much more other webpack features we don’t even consider, so again, using rollup for apps is not an option for me and for many people

The issues also seems to occur when chunk-splitting. Even if there is only one entry in the webpack config.

// chunk
export function a() { ... };
export function b() { ... };
// app.js
...
if (condition) {
  import('a').then(module => module.a());
}

The generated chunk file contains both a and b

This bug still very much exists as of Webpack 5.36.1 when setting optimization.runtimeChunk to 'single'.

@theKashey pity, Rollup doesn’t have all Webpack features I guess, especially advanced HMR, so I cannot switch

Anyway, without it code splitting is rather useless because usually vendor files are much bigger than app files and really splitting vendor modules would gain the biggest benefits. Wondering whether this feature is on Webpack roadmap?

It’s a really big problem for many people. Fixing this bug can make internet faster

To add a data point for this:

We have 258 entry points in one of our applications and compiling them using one-config-object-per-entry (rather than one config with the entry field resolving to all entry points) reduces our total asset size by 697KB (714030 bytes).

( getting ahead of @sokra 's auto-closing bot on this one )

Unfortunately, after upgrading my bug reproduction samples to webpack@3.5.2 the bug still very much still exists. I’m guessing it’s still a fundamental issue to webpack core.

The awkward thing with this issue, is that it’s likely many folks out their aren’t actually auditing their bundles to check that tree shaking is working as expected. (And in all fairness it’s hard to do outside of trivial examples that you can manually inspect like I have here.)

/cc @TheLarkInn (would love some issue love on this one…)