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.
左琳,微医前端技术部前端开发工程师。身处互联网浪潮之中,热爱生活与技术。
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
modulesto
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-loaderfor 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
sideEffectsfield 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
sideEffectsto
false(or provide an explicit list) in
package.jsonand use
# PUREcomments 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
WeDoctor Frontend Technology
Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.