Understanding Tree‑shaking in Rollup and Webpack: Mechanisms, Configurations, and Comparison
This article explains the purpose and ES6‑module basis of tree‑shaking, compares the implementation details of Rollup and Webpack, provides configuration and code examples, and shows real‑world bundle size and build‑time results to help developers choose the right bundler for optimal output.
Rollup and Webpack are two of the most widely used bundlers in modern front‑end projects; both rely on tree‑shaking to eliminate dead code and reduce the final bundle size, which is increasingly important for performance‑critical applications.
Purpose of Tree‑shaking
The sole goal of tree‑shaking is to remove three categories of dead code: unreachable code, code whose execution result is never used, and code that only writes to variables without reading them, thereby enabling true on‑demand imports.
Why Tree‑shaking Depends on ES6 Modules
ES6 modules are static: they can only appear at the top level, import specifiers must be string literals, and imports are immutable. This static nature allows reliable analysis, unlike CommonJS where require is dynamic.
// CommonJS example
if (hasRequest) {
const utils = require('utils');
}With ES6 you can import only the needed symbols, e.g. import { request } from 'utils'; , and the import statement itself cannot be placed inside conditional statements.
// ES6 import example
import { request } from 'utils';Webpack 5.x Tree‑shaking Mechanism
Webpack 2 introduced built‑in support for ES2015 (harmony) modules and side‑effects detection via the sideEffects field in package.json . Webpack 5 adds the terser-webpack-plugin for code compression. During compilation Webpack marks imports/exports and lets the optimizer drop unused parts.
1
? `/* unused harmony exports ${joinIterableWithComma(this.unusedExports)} */\n`
: this.unusedExports.size > 0
? `/* unused harmony export ${first(this.unusedExports)} */\n`
: "";
const definitions = [];
const orderedExportMap = Array.from(this.exportMap).sort(([a], [b]) => a < b ? -1 : 1);
// mark harmony export
for (const [key, value] of orderedExportMap) {
definitions.push(`\n/* harmony export */ ${JSON.stringify(key)}: ${runtimeTemplate.returningFunction(value)}`);
}
const definePart = this.exportMap.size > 0
? `/* harmony export */ ${RuntimeGlobals.definePropertyGetters}( ${this.exportsArgument}, {${definitions.join(",")}
/* harmony export */ });\n`
: "";
return `${definePart}${unusedPart}`;
}Rollup Tree‑shaking Mechanism
Rollup uses a pipeline of static analysis: it builds a module graph from the entry file, generates an AST with Acorn, marks each node as used or unused, and finally emits code with MagicString, stripping dead code.
{
"name": "rollup",
"version": "2.77.2",
"description": "Next-generation ES module bundler",
"main": "dist/rollup.js",
"module": "dist/es/rollup.js",
"typings": "dist/rollup.d.ts",
"bin": { "rollup": "dist/bin/rollup" },
"devDependencies": {
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-commonjs": "^22.0.1",
"acorn": "^8.7.1",
"magic-string": "^0.26.2"
// ...
}
}Rollup Advantages
Exports pure ES modules.
Performs program‑flow analysis to more accurately detect side‑effects.
Case Studies
Import without usage cannot be eliminated import pkgjson from '../package.json'; export function getMeta(version) { return { lver: version || pkgjson.version }; } The whole package.json object is bundled.
Global variable mutation prevents removal window.utm = 'a.b.c'; Even if utm is never read, the statement stays because it may affect the global scope.
Vue 3 Tree‑shaking Optimizations
Vue 2’s global API Vue.nextTick cannot be tree‑shaken because it is not exported individually. Vue 3 refactors such APIs to enable proper dead‑code elimination.
Final Comparison
Configuration snippets for both bundlers are shown below.
Webpack config (simplified):
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: path.join(__dirname, 'src/index.ts'),
output: { filename: 'webpack.bundle.js' },
module: { rules: [ { test: /\.(js|ts|tsx)$/, exclude: /(node_modules|bower_components|lib)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /\.tsx?$/, use: 'ts-loader', exclude: /(node_modules|lib)/ } ] },
resolve: { extensions: ['.tsx', '.ts', '.js'] },
optimization: { usedExports: true },
plugins: [ new webpack.optimize.ModuleConcatenationPlugin() ]
};Rollup config (simplified):
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
import babel from "rollup-plugin-babel";
import json from "rollup-plugin-json";
import { uglify } from 'rollup-plugin-uglify';
export default {
input: "src/index.ts",
output: [{ file: "lib/index.cjs.js", format: "cjs" }],
treeshake: true,
plugins: [ json(), typescript(), resolve(), commonjs(), babel({ exclude: "node_modules/**", runtimeHelpers: true, sourceMap: true, extensions: [".js", ".jsx", ".es6", ".es", ".mjs", ".ts", ".json"] }), uglify() ]
};Measured bundle sizes and build times:
Tool
Before (KB)
After (KB)
Build Time
Webpack 5.52.0
46 KB
44 KB
4.8 s
Rollup 1.32.1
24 KB
18 KB
3.7 s
Conclusion
Rollup generally produces smaller bundles and faster builds for this SDK example, but the actual impact depends on project size, side‑effects configuration, and usage patterns. Understanding how each bundler marks and removes unused code helps developers write more tree‑shakable modules.
References
Vite Features and Partial Source Analysis
Rollup Source Code Analysis
Rollup Tree‑shaking Documentation
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
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.