Treeshaking with webpack has come a long way. My favorite parts of webpack 4 are the ES module handling and tree shaking improvements.

Using two simple files:

//importer.js

import * as exporter from './exporter'

console.log(exporter.timesTwo(4)) // 8

and

// exporter.js

export const timesTwo = x => x * 2

export const plusOne = x => x + 1

we can see how tree shaking in webpack 4 behaves.

We need to install webpack 4 as well with npm install webpack webpack-cli -D and have a webpack.config.json that looks something like the following.

module.exports = {

entry: {

es: './importer'

},

output: {

filename: '[name]-bundle.js'

},

devtool: 'none',

module: {

rules: [

{

test: /\.js$/,

type: 'javascript/esm',

exclude: /node_modules/

}

]

}

}

Note the “type” field. Webpack’s version 4.0 changelog has this change tucked away slightly.

javascript/esm handles ESM more strictly compared to javascript/auto : Imported names need to exist on imported module Dynamic modules (non-esm, i. e. CommonJs) can only imported via default import, everything else (including namespace import) emit errors

Run webpack with node_modules/.bin/webpack-cli --mode=development . Down on line 76 of the exported file dist/es-bundle.js , we have

/***/ "./exporter.js":

/*!*********************!*\

!*** ./exporter.js ***!

\*********************/

/*! exports provided: timesTwo, plusOne */

/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict";

__webpack_require__.r(__webpack_exports__);

/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timesTwo", function() { return timesTwo; });

/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "plusOne", function() { return plusOne; });

// exporter.js

const timesTwo = x => x * 2

const plusOne = x => x + 1 /***/ }), /***/ "./importer.js":

/*!*********************!*\

!*** ./importer.js ***!

\*********************/

/*! no exports provided */

/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict";

__webpack_require__.r(__webpack_exports__);

/* harmony import */ var _exporter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./exporter */ "./exporter.js"); _exporter__WEBPACK_IMPORTED_MODULE_0__["timesTwo"](4) // 8

Nothing’s been shaken out yet because we’re still in development mode. Running a production build of webpack gets us this.

! function (e)

{

var r = {}; function t(n)

{

if (r[n]) return r[n].exports;

var o = r[n] = {

i: n,

l: !1,

exports:

{}

};

return e[n].call(o.exports, o, o.exports, t), o.l = !0, o.exports

}

t.m = e, t.c = r, t.d = function (e, r, n)

{

t.o(e, r) || Object.defineProperty(e, r,

{

configurable: !1,

enumerable: !0,

get: n

})

}, t.r = function (e)

{

Object.defineProperty(e, "__esModule",

{

value: !0

})

}, t.n = function (e)

{

var r = e && e.__esModule ? function ()

{

return e.default

} : function ()

{

return e

};

return t.d(r, "a", r), r

}, t.o = function (e, r)

{

return Object.prototype.hasOwnProperty.call(e, r)

}, t.p = "", t(t.s = 0)

}([function (e, r, t)

{

"use strict";

t.r(r);

console.log((e => 2 * e)(4));

}]);

exporter.js ‘s plusOne function has been dropped and timesTwo (a silly example) has been scope hoisted (see https://medium.com/webpack/brief-introduction-to-scope-hoisting-in-webpack-8435084c171f for more info).

Note: ES module exports are mangled by default (though not in dev mode) and that webpack no longer distinguishes between ‘binding’ and ‘immutable’ ES module exports, and it seems that immutable exports produce more compact code. binding exports are export const addOne = x => x + 1 and immutable exports are export function addOne(x) { return x + 1 } . For output, the two forms produce the same export bindings now (as opposed to Webpack 2’s beta).

One other neat thing about ES modules, is that if an import doesn’t exist as an export and the user is using javascript/esm as the mode in modules.rules, webpack will alert the user with a busted build.

import * as exporter from './exporter'

console.log(exporter.ohNoThisDoesNotExist(4)) // 8

Gives us a

ERROR in ./importer.js

3:12-41 "export 'ohNoThisDoesNotExist' (imported as 'exporter') was not found in './exporter'

@ ./importer.js

which is a nice improvement over the old warning behavior.