Search code examples
javascriptwebpackwebpack-4tree-shaking

why does webpack 4 include the whole file for single named import?


Given the following two files :

//a.js
export const X = /*#__PURE__*/ "X";

const a = function () {
  return "About a!";
};
a.foo = function () {
  return "foo";
};
export default a;

and

//main.js
import { X } from "./a";
import { Y } from "./b";

console.log(X, Y);

I have added

  • "sideEffect": false to package.json
  • optimization.usedExports: true to webpack.config.js

but running webpack -p still includes the whole a.js file in the bundle. Rollup only includes X from a.js (demo)

Removing the a.foo definition "fixes" the issue but is not a solution outside the demo repository. I have read the tree-shaking documentation page multiple times, read [countless issues about tree shaking on the webpack issue tracker, I still cant figure out the following :

  • Why is the /*#__PURE__*/ marker on X not being used ?
  • Is there a way to configure webpack to only include X from a.js ?

You can also checkout the sandbox repository to see the full webpack configuration and the corresponding next.js/react issue (see pages/index.jsx) which led me to this specific issue.


Solution

  • After raising the issue on webpack's issue tracker ~sokra provided an answer:

    Seems like a single compression pass in terser is not enough to compress this code.

    The default number of passes is 1 but once raised to 3, a.js is no longer fully inlined. This is how this can be achieved at the cost of a longer build time.

    const TerserWebpackPlugin = require("terser-webpack-plugin");
    
    /** @type {import("webpack").Configuration} */
    module.exports = {
        optimization: {
            minimizer: [
                new TerserWebpackPlugin({
                    terserOptions: {
                        compress: {
                            passes: 3
                        }
                    }
                })
            ]
        }
    }