webpackbabeljswebpack-3tree-shaking

Webpack 3, Babel and Tree shaking not working


I'm trying to find a way to tree-shake my modules and use Babel with Webpack.

If I take the example code from the webpack documentation (https://webpack.js.org/guides/tree-shaking/) and run it, the modules/functions/other exports that are not used are marked as unused harmony exports, which is the expected outcome. After running webpack with the -p argument (production), webpack uses UglifyJS to remove the dead and unused code (to tree-shake).

Now, if I add babel-loader to my webpack config file, my ES2015 modules are transpiled but now are not marked as unused exports anymore.

So for example:

math.js

export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

app.js (my entry file)

import {square} from './math.js'

Running this through webpack WITHOUT babel-loader, the cube function will get marked as unused and removed after compiling for production (-p).

Running this through webpack WITH babel-loader, the cube function will not be marked as unused and will stay in the compiled bundle.

What am I missing?

Edit

Here's a demo repo that can reproduce the situation

https://github.com/Milanzor/babel-and-treeshaking-question

Update

If I add a .babelrc:

{
  "presets": [
    ["@babel/preset-env", {
      "useBuiltIns": "entry",
      "debug": true,
      "targets": {
        "browsers": ["last 2 versions"]
      }
    }]
  ]
}

I get the same result, but if I add modules: false to the preset-env options, Babel doesn't compile the modules to ES5 and Webpack marks the modules as unused again.

Conclusion

I need to find a way to tell Webpack that my modules are transpiled with Babel, or I need to find a way to tell Babel to scan for unused codes itself.


Solution

  • Webpack's built-in tree shaking works on ES6 module syntax only. If you're using Babel's defaults settings, Babel will compile ES6 modules to CommonJS modules, leaving nothing for Webpack to work with.

    Generally people using Webpack will want to pass modules: false to the preset that they are using for ES6 (probably preset-env?), thus doing

    {
      presets: [
        ['env', { modules: false }],
      ],
    }
    

    alternatively you could consider using a plugin like https://github.com/indutny/webpack-common-shake to enable tree-shaking for CommonJS modules.

    Update

    If you're using Babel 7 (and thus @babel/preset-env), the modules option is now automatically false when used in Webpack, so this explicit configuration should no longer be needed.