Frontend Development 18 min read

How Webpack and Rollup Implement Tree-Shaking: A Deep Dive

This article compares the tree‑shaking mechanisms of Rollup and Webpack, explains the three stages Webpack uses (UglifyJS, BabelMinify, Terser), details side‑effects handling, configuration tips, and performance benchmarks, providing practical guidance for optimizing bundle size in modern JavaScript projects.

WeDoctor Frontend Technology
WeDoctor Frontend Technology
WeDoctor Frontend Technology
How Webpack and Rollup Implement Tree-Shaking: A Deep Dive
左琳,微医前端技术部前端开发工程师。身处互联网浪潮之中,热爱生活与技术。

Preface

If you have read the Rollup series article "Where does dead code go? Project weight reduction with Rollup's Tree-shaking", you are already familiar with tree‑shaking. For readers unfamiliar with tree‑shaking, spend five minutes on the introductory article "What is tree‑shaking?".

Webpack did not support tree‑shaking originally, but added it in its 2.x version after seeing Rollup's impressive bundle size reduction. Does Webpack implement tree‑shaking using the same principle as Rollup?

This question motivates the present article.

Tree‑shaking Implementation Mechanism

After scanning official documentation and many articles, I discovered that Webpack’s tree‑shaking implementation differs from Rollup’s and even has multiple approaches.

Early Webpack configurations were complex, leading to the nickname "Webpack configuration engineer". Although Webpack 4 claims zero‑configuration, advanced bundling still requires explicit setup. Below we explore how Webpack achieves tree‑shaking.

Tree‑shaking – Rollup VS Webpack

rollup analyzes the program flow during compilation and, thanks to ES6 static modules (exports and imports cannot be changed at runtime), can determine which code is needed at bundle time.

webpack marks unused code but does not remove it itself; the actual removal is performed by compression tools such as UglifyJS, Babili, or Terser, which read Webpack’s output and delete dead code before final minification.

Now let’s examine the three stages Webpack uses for tree‑shaking.

Webpack’s Three Tree‑shaking Stages

Stage 1: UglifyJS

Webpack marks code → Babel transpiles to ES5 → UglifyJS compresses and deletes dead code.

UglifyJS does not support ES6+; Babel must first compile to ES5.

Configure Babel to avoid transforming ES6 module syntax (set

modules

to

false

) so that Webpack can retain module information.

Mark side‑effect‑free code as

pure

(e.g.,

@__PURE__

) to allow UglifyJS to drop unused classes.

<code>.babelrc
{
  "presets": [
    ["env", {
      "loose": true,
      "modules": false
    }]
  ]
}
</code>
<code>webpack.config.js
module: {
  rules: [
    { test: /\.js$/, loader: 'babel-loader' }
  ]
},
plugins: [
  new webpack.LoaderOptionsPlugin({
    minimize: true,
    debug: false
  }),
  new webpack.optimize.UglifyJsPlugin({
    compress: { warnings: true },
    output: { comments: false },
    sourceMap: false
  })
]
</code>

Stage 2: BabelMinify (Babili)

BabelMinify (formerly Babili) is a Babel‑based compressor that integrates UglifyJS‑like compression without an extra transpilation step, producing smaller bundles when targeting modern browsers.

Two usage patterns exist: a dedicated Babili plugin or a Babel preset. The preset can be combined with

babel-loader

for faster builds.

Stage 3: Terser

Terser is an ES6+ aware parser and compressor. Webpack 5 bundles Terser by default, making it the preferred choice for production builds.

UglifyJS is no longer maintained and lacks ES6+ support.

Webpack’s built‑in TerserPlugin automatically removes dead code.

Webpack’s Tree‑shaking Process

Marking Imports

Webpack marks import statements with

/* harmony import */

and distinguishes used exports (

/* harmony export ([type]) */

) from unused ones (

/* unused harmony export [FuncName] */

).

Runtime Marking Example

<code>const exportsType = module.getExportsType(
  chunkGraph.moduleGraph,
  originModule.buildMeta.strictHarmonyModule
);
runtimeRequirements.add(RuntimeGlobals.require);
const importContent = `/* harmony import */ ${optDeclaration}${importVar} = __webpack_require__(${moduleId});`;
if (exportsType === "dynamic") {
  runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport);
  return [
    importContent,
    `/* harmony import */ ${optDeclaration}${importVar}_default = /*#__PURE__*/${RuntimeGlobals.compatGetDefaultExport}(${importVar});`
  ];
}
</code>

Marking Exports (Used vs Unused)

<code>// generate property getters
const fn = RuntimeGlobals.definePropertyGetters;
return Template.asString([
  "// define getter functions for harmony exports",
  `${fn} = ${runtimeTemplate.basicFunction("exports, definition", [
    `for (var key in definition) {`,
    Template.indent([
      `if (${RuntimeGlobals.hasOwnProperty}(definition, key) && !${RuntimeGlobals.hasOwnProperty}(exports, key)) {`,
      Template.indent(["Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });"]),
      "}"
    ]),
    "}"
  ])}`
]);
</code>

Handling Side Effects

"Side effects" refer to code that executes special behavior on import (e.g., polyfills) rather than merely exporting values.

Webpack treats files with side effects as non‑shakable to avoid breaking applications. To enable tree‑shaking for such files, configure the

sideEffects

field in

package.json

:

<code>{
  "name": "your-project",
  "sideEffects": false
}
</code>

Alternatively, provide an array of paths that do have side effects.

Inline comments like

/*# PURE */

can mark specific function calls as side‑effect‑free, allowing Webpack (with

optimization.innerGraph: true

) to drop them.

Summary

If you develop a JavaScript library, prefer Rollup and publish an ES6 module entry in

package.json

.

When using Webpack, especially older versions, prioritize the Terser plugin for compression.

Avoid writing code with side effects; use ES2015 module syntax.

Set

sideEffects

to

false

(or provide an explicit list) in

package.json

and use

# PURE

comments to force removal of dead code.

Ensure a dead‑code‑removing compressor (e.g., Terser) is integrated into the Webpack build.

References

How to use tree‑shaking in Webpack 2

Your Tree‑Shaking Is Useless

UglifyJS Chinese Manual

Webpack 4 Tree Shaking Ultimate Guide

Webpack Documentation – Tree‑shaking

JavaScriptbundle optimizationWebpackTree ShakingRollupuglifyjsterser
WeDoctor Frontend Technology
Written by

WeDoctor Frontend Technology

Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.