Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Webpack tree shaking with dead code elimination work on node_modules?

Considering this Webpack 3.8.1 config.

// common
module.exports = {
        context: path.resolve(__dirname, './src'),
        entry: [
            'whatwg-fetch',
            './index'
        ],
        output: {
            path: path.resolve(__dirname, 'build/assets'),
            publicPath: '/assets/',
            filename: 'main.js'
        },
        plugins: [
            new CleanWebpackPlugin(['build']),
        ],
        module: {
            rules: [{
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                }
            }, {
                test: /\.(scss|css)$/,
                use: [{
                    loader: 'style-loader'
                }, {
                    loader: 'css-loader'
                }, {
                    loader: 'sass-loader'
                }],
            }, {
                test: /\.(png|jpg|gif|woff2|woff)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192
                        }
                    }
                ]
            }]
        }
    };

//prod
module.exports = merge(common, {
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('production')
        }),
        new UglifyJSPlugin()
    ],
    devtool: 'none'
});

and this Babel 6.26.0 config

{
  "presets": [
    [
      "env",
      {
        "modules": false,
        "targets": {
          "browsers": [
            ">1%"
          ]
        }
      }
    ], [
      "react"
    ]
  ],
  "plugins": [
    "transform-class-properties",
    "transform-export-extensions",
    "transform-object-rest-spread",
    "react-hot-loader/babel"
  ]
}

I was expecting that tree shaking alongside with dead code elimination of UglifyJS should work in a way that enables me to write named imports from index.es.js modules, for example Material-UI-Icons and unused ones get removed from the bundle.

import {Menu} from 'material-ui-icons';

This library does indeed reexport ES6 modules defined in package.json as "module": "index.es.js".

Yet my bundle size increased by 0.5MB after importing single icon. When I changed it to

import Menu from 'material-ui-icons/Menu;

bundle size got reduced again with only this icon imported.

Is there something wrong in my configuration, or do I missunderstand how tree shaking works and does not apply to this scenario?

like image 219
B.Gen.Jack.O.Neill Avatar asked Nov 17 '17 11:11

B.Gen.Jack.O.Neill


1 Answers

So after some additional digging, I found the cause / temporary solution / solution. Basically, because ES Modules can have side-effects, Webpack nor UglifyJS can safely (per specification) remove unused re-exports typically found in index.es.js or similar "module" entry point.

For now, there are some ways around it. Either you can manually import only necessary modules, or you can use babel-plugin-direct-import.

Good news is that Webpack 4 adds support for pure modules thru the side-effects flag. When library author marks it as pure, tree shaking and minification will work as expected. I also recommend to read this nice summary about ESM specification support in NodeJS.

For now I would recommend to manually work thru your bundle using this wonderfull visualizer and decide how to handle each large dependency on its own.

like image 183
B.Gen.Jack.O.Neill Avatar answered Oct 06 '22 23:10

B.Gen.Jack.O.Neill