A bundle stats visualizer turns a webpack or rollup build report into a treemap so you can see at a glance which modules dominate your JavaScript bundle. Large dependencies are the most common cause of slow page loads, and a flat list of sizes is hard to scan. This tool renders module sizes as proportional rectangles, coloured by chunk, entirely in your browser.
How it works
The visualizer first parses your JSON and auto-detects its shape. A webpack stats.json exposes a modules array where each entry has a name, a size in bytes, and a list of chunks; the tool maps chunk IDs to their human names. A rollup-plugin-visualizer export is a tree of nodes carrying value or renderedLength sizes and children, which the tool walks down to the leaves. A plain object mapping module paths to byte counts is also accepted.
Once it has a flat list of modules with sizes, it builds the layout using the squarified treemap algorithm of Bruls, Huizing, and van Wijk. The algorithm fills the canvas row by row, choosing whether to add the next module to the current row based on which option keeps the rectangles closest to square — the “worst aspect ratio” heuristic. The result is a tiling where every tile’s area is exactly proportional to its module’s size and the whole area sums to the canvas.
How to generate the input file
Webpack:
webpack --profile --json > stats.json
Or from your webpack.config.js:
module.exports = {
stats: {
modules: true,
entrypoints: true,
chunkModules: true,
},
};
Run the build and capture the stdout output as stats.json. The file can be large (several megabytes for a complex app), which is expected.
Rollup with rollup-plugin-visualizer:
import { visualizer } from "rollup-plugin-visualizer";
export default {
plugins: [
visualizer({ template: "treemap", filename: "stats.json", open: false }),
],
};
After building, stats.json in your project root contains the tree structure this tool accepts.
Reading the treemap
Tile size = module size. The area of each rectangle is directly proportional to that module’s byte count in the bundle. A tile that covers 20% of the canvas means that module is 20% of the total bundle size.
Colour = chunk. Different chunks (main, vendor, lazy routes) are shown in different colours so you can immediately see which chunk is heaviest. Common findings:
- A
vendorchunk dominated by a handful of large packages (moment.js, lodash, chart libraries). - Route-level chunks that are unexpectedly large because a dependency was not properly lazy-loaded.
- Duplicate modules appearing in multiple chunks because shared code was not extracted into a common chunk.
Hover for exact figures. The tooltip shows the module’s path, its size in bytes and as a percentage of the total, and which chunk it belongs to.
What to do with the results
Once you identify the largest tiles, the typical optimisation paths are:
- Replace heavy libraries. If
moment.jsdominates, considerdate-fnsordayjs, which are a fraction of the size for most use cases. - Tree-shake properly. If you see individual lodash methods imported (for example,
lodash/cloneDeep) but the whole lodash bundle is still present, check your import style —import { cloneDeep } from 'lodash'imports the entire library;import cloneDeep from 'lodash/cloneDeep'does not. - Lazy-load routes. If page-specific components appear in the main bundle, move them behind
import()dynamic imports so they only load when needed. - Deduplicate. If the same dependency appears in both the main chunk and a lazy chunk, configure
splitChunks(webpack) ormanualChunks(Rollup) to extract the shared module into its own chunk.
Everything runs locally, so private builds stay on your machine.