tree-shaking with lodash-es

See #1612 (comment).
Currently tree-shaking lodash-es modules is not possible with webpack 2 beta.

First clone into node_modules:

git clone -b es --depth=1 ./node_modules/lodash-es


npm i -g webpack@beta


npm i babel-loader@^5 babel-core@^5

with test.js:

import { chunk } from 'lodash-es';
console.log(chunk([1,2,3,4], 2));


webpack ./test.js ./bundle.js -p --display-chunks --display-modules --display-origins --module-bind js=babel-loader?blacklist[]=es6.modules


> gzip-size bundle.js | pretty-bytes
30.41 kB

While the output of import { chunk } from 'lodash-es/array/chunk'

> gzip-size bundle.js | pretty-bytes
1.19 kB

Related to rollup/rollup#45

Author: Fantashit

5 thoughts on “tree-shaking with lodash-es

  1. Not babels fault. I noticed the issue, but it’s a bit difficult.

    We can optimize it, but this would be an unsafe optimization. The ES6 spec says that all dependencies must be loaded even if the exports are not used.

  2. It would be nice if there were standards support for a “weak reexport” that would say “I’m exporting this thing, but it doesn’t need to be evaluated unless someone uses it”. This would not change the semantics of loading except that a loader could use it as a signal to do more aggressive optimization. Nothing prevents loaders from evolving this on their own as a comment or annotation or external configuration, but if there’s value to it then having it in the language instead of being loader-specific would be better for everyone.

    Obviously, if a naive weak reexport targets a module with default side-effects then you might see inconsistent behavior, but I don’t think that risk means that a well-structured library like lodash-es shouldn’t have the option to declare its own safety. In SystemJS where all this happens in the browser, it’s rather important that reexports not be eagerly fetched, but there’s no way to avoid that today without drilling down to the individual files (import {x} from 'lodash-es/x') which is quite a bit less pleasant syntactically than plucking names out of a single lodash import (import {x,y,z} from 'lodash-es'). Webpack doesn’t have to be as concerned with when/if the dependency gets fetched (not that reading hundreds of lodash modules that it doesn’t need to is a great thing), but it still would benefit from a clear signal that it can exclude something from the bundle.

  3. This is still the case in beta.25.

    EDIT: I see now that all this was discussed before way in the middle of the thread; sorry for the noise 🙁

    While webpack 2 will, correctly, not re-export any of the unused top-level re-exports, the __webpack_require__ calls are left in. While this will keep correctness as all the side-effects of the modules (such as requiring other modules and calling their functions) will still happen, it is not deletable by uglify (the entry point from @cspotcode‘s repo, the same module after uglify without mangling).

  4. @LKay if you’re using Babel make sure it isn’t transpiling the ES6 import into require. Webpack needs to see the import and exports statements to do tree-shaking. If you’re using babel-preset-env you can turn off the transpiling like this:

      "presets": [
        ["env", {
          "modules": false
  5. Playing with lodash-es and tree shaking, i noticed a bundle size bigger than expected.

    This occurs because /lodash-es/lodash.default.js is being bundled in some cases even when not used (directly or indirectly).

    To test try:

    import {filter} from 'lodash-es'

    Will create a minified 16.5 kb bundle. lodash.default.js is bundled although is not imported by filter or its dependencies.

    Webpack reports the reason as “harmony side effect evaluation”

    If i import functions not used by ‘lodash.default.js’ to configure chaining like head, tree shaking works fine with a <1kb bundle.

    So i would say that somehow the sideEffects flag used by lodash-es is not being considered at all

Comments are closed.