4

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?

incaren
  • 191
  • 1
  • 6

1 Answers1

0

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.

felixmosh
  • 32,615
  • 9
  • 69
  • 88
  • You're right in that `banana` will be removed, but `orange` will not be since it's not marked as unused! Do you know of a way to make Webpack smart enough that it'll also remove `orange`? – incaren Feb 25 '21 at 20:23
  • This depends if module concatenation optimization applied. If your TS config generates commonjs modules, it won't be applied. Try to set the module property to esnext in your tsconfig file. – felixmosh Feb 25 '21 at 23:34
  • I've already done so (i.e. I use @babel/preset-env with modules set to `false`), but let me see if tsc generates different code in this case! – incaren Feb 27 '21 at 16:35
  • Did you tried to change the `module` field in tsconfig file? – felixmosh Mar 01 '21 at 09:24