8

I have a setup where a library's index.js (main entry point) exports everything in the lib... so its setup like this:

export * from "./mod1"
export * from "./mod2"
// etc...

(take a look)

When I import 1 of the exported libraries from that entry point, looks like Webpack is not able to tree-shake the output. running webpack -p is actually including the entire library in the bundle, although only one export was imported. Runing webpack (non-production bundle) does show unused harmony export .... throughout the file (167 times), but why are they not being dropped?

I have a test setup showing this problem here: https://github.com/purtuga/webpack-bundle-test

Hoping someone (smarter than me :) ) can help identify what I am doing wrong.

/Paul

Paul T.
  • 608
  • 1
  • 6
  • 11
  • Have you found a better solution to this? I'm facing the exact same problem. – Apidcloud Mar 22 '19 at 10:54
  • 1
    I have sense moved to Webpack 4 and I think I'm no longer having this issue - but: I have not circled back around to it to validate (using the test project I setup). If I have some time, I will do that and report back here. – Paul T. Mar 23 '19 at 13:57
  • I have opened a new post with my problem: https://stackoverflow.com/questions/55320774/webpack-tree-shaking-not-working-between-packages – Apidcloud Mar 24 '19 at 04:37
  • 1
    Thanks @Apidcloud . I will follow that one as well and have also shared your question on Twitter to see if we get a broader community to look at it. – Paul T. Mar 25 '19 at 14:17

2 Answers2

2

I cloned your "webpack-bundle-test" and here is what i did.

  1. Copied "common-micro-libs" src folder from https://github.com/purtuga/common-micro-libs.
  2. Edited the code in mod3.js to: import { objectExtend } from "./common-micro-libs" export default mod3 = objectExtend({}, { text: "module 3" });

  3. I ran webpack locally and found 167 "unused harmony" comments.

  4. With webpack -p i got the following results: enter image description here
  5. Changed code in mod3.js to import objectExtend from "./common-micro-libs/jsutils/objectExtend.js" export default mod3 = objectExtend({}, { text: "module 3" });.
  6. With the above change i could see the following reduction in bundle sizeenter image description here

I believe when importing dependencies, it is better to import only the necessary function/component from the library and this will be packed more effectively in the bundle. I am not able to expalin why so, though i followed the same principle in importing utilities in my project when using lodash and it works fine. Can you run the tests and let me know?

Priyesh Diukar
  • 2,032
  • 1
  • 15
  • 19
  • Hi Priyesh - thanks for the response. I been importing dependencies from the single `lib-name/src/.../` file for a while - so I know that works. But I was trying to keep the right side of `from` short by only using the library package name and have the named imports be retrieved from there (`import { foo } from "lib-name"` - and thats when I found this issue - for which I'm looking to understand Why its happening. I'm not sure who is at fault: is it webpack in the way it defined unused `harmony` imports in the bundle (thus Uglify is not able to drop them) or is it Uglify? Thanks for trying – Paul T. Feb 17 '18 at 15:59
  • Have you found a better way? I'm having the same issue :/ – Apidcloud Mar 22 '19 at 10:58
  • 1
    In [webpack 4's treeshaking](https://webpack.js.org/guides/tree-shaking/) you can not set the "sideEffects" property in your package.json. These provides hints to webpack compiler on "dead code" – Priyesh Diukar Apr 03 '19 at 10:52
1

When you bundle the application without transformations (like Babel) and minification (like UglifyJS), you get: unused harmony export.

Now Webpack 2+ only marks code unused and doesn’t export it inside the module. It pulls everything and leaves unused code for minification libraries.

You can use UglifyJS with babel for this. UglifyJS doesn’t support the new language features of Javascript ES2015+ yet. You will need Babel to transpile the code to ES5 and then use UglifyJS to clean up the unused code.

You will need .babelrc file with:

We have to tell the preset (in our case babel-preset-env) to skip the module transpilation.

    {
      "presets": [
        ["env", {
          "loose": true,
          "modules": false
        }]
      ]
    }

and corresponding webpack config:

    module: {
      rules: [
        { test: /\.js$/, loader: 'babel-loader' }
      ]
    },
    
    plugins: [
      new webpack.LoaderOptionsPlugin({
        minimize: true,
        debug: false
      }),
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: true
        },
        output: {
          comments: false
        },
        sourceMap: false
      })
    ]

OR

Babili is a better option since Babili will remove unused code before transpilation. It is much easier to spot unused classes before downleveled to ES5. Tree-shaking will also work for class declarations, not just functions.

You will need:

npm install [babili][3] [babili-webpack-plugin][4] --save-dev

Use the following plugin in your webpack config, like so:

    plugins: [
      new BabiliPlugin()
    ]

There is also an optimzied way to use babili as a preset. You can refer their site to use it as a preset for babel-loader.

Dmitry Shvedov
  • 3,169
  • 4
  • 39
  • 51
Priyesh Diukar
  • 2,032
  • 1
  • 15
  • 19
  • thank you for your response... But this does not help. Yes, I do understand how tree shaking is actually accomplished.. and all that you describe above is true with my example (take a look a the test git repo I provided) - so I was expecting the "dead code" to be dropped when the package was minified (`webpack -p`) - but thats the not the case... I'm really stumped on this one... – Paul T. Feb 16 '18 at 14:45