Can Webpack tree-shake to remove imports already removed by tree-shaking

Viewed 878

Can Webpack be configured so that its tree-shaking is smart enough to remove imports depended on by only other tree-shaken exports?

Example:

entry.js

import { apple } from './appleBanana';
console.log(apple());

appleBanana.js

import { mango, orange } from './mangoOrange';

export function apple() {
  console.log('apple');
  mango();
}

export function banana() {
  console.log('banana');
  orange();
}

mangoOrange.js

export function mango() {
  console.log('mango');
}

export function orange() {
  console.log('orange');
}

webpack.config.js optimization

optimization: {
  concatenateModules: false,
  usedExports: true,
  sideEffects: true
}

When compiling with the above, we get:


/***/ "./client/entry/Test.ts":
/*!******************************!*\
  !*** ./client/entry/Test.ts ***!
  \******************************/
/*! no exports provided */
/*! all exports used */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _appleBanana__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./appleBanana */ "./client/entry/appleBanana.ts");

console.log(Object(_appleBanana__WEBPACK_IMPORTED_MODULE_0__[/* apple */ "a"])());

/***/ }),

/***/ "./client/entry/appleBanana.ts":
/*!*************************************!*\
  !*** ./client/entry/appleBanana.ts ***!
  \*************************************/
/*! exports provided: apple, banana */
/*! exports used: apple */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return apple; });
/* unused harmony export banana */
/* harmony import */ var _mangoOrange__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./mangoOrange */ "./client/entry/mangoOrange.ts");

function apple() {
  console.log('apple');
  return Object(_mangoOrange__WEBPACK_IMPORTED_MODULE_0__[/* mango */ "a"])();
}
function banana() {
  console.log('banana');
  return Object(_mangoOrange__WEBPACK_IMPORTED_MODULE_0__[/* orange */ "b"])();
}

/***/ }),

/***/ "./client/entry/mangoOrange.ts":
/*!*************************************!*\
  !*** ./client/entry/mangoOrange.ts ***!
  \*************************************/
/*! exports provided: mango, orange */
/*! exports used: mango, orange */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return mango; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return orange; });
function mango() {
  console.log('mango');
}
function orange() {
  console.log('orange');
}

/***/ })

/******/ });

Note that both mango and orange are marked as used, even though banana() (which calls orange()) got removed by tree-shaking. As a result, in the Terser/Uglify minified bundle, they both get included.

Is there a way to make Webpack or Terser/UglifyJS smarter and also remove imports to symbols that are not used in the post-tree-shaking?

1 Answers

It will remove them, what you are seeing is not a production bundle (due to comments & not minified code).

In dev builds, webpack just marks that there are unused exports/imports with a comment, like in your example bundle.

Tree-shaking is a process that will be applied only at production build, it removes these unused tokens from the bundle.

Just try to run build in production mode, and check the minified bundle, you should expect that banana & orange will not exists in it.

Related