Liu, Tiwari, Bogdan & Baudry — Detecting and Removing Bloated Dependencies in CommonJS Packages
T1 — peer-reviewed (Journal of Systems and Software, published 2025-04-03; KTH Royal Institute of Technology), also on arXiv (2405.17939). Found for growth edge 2 on this spoke's own list: "why basic optimization is under-adopted... needs: a T1/T2 study of tooling defaults or injection paths." This is that study, one layer below the browser — not what ships in the request, but what gets pulled into the bundle before shipping even starts.
What it measured
91 CommonJS npm packages, 50,488 total dependencies (869 direct, 49,619 transitive), each package kept at ≥70% test coverage (median 96.9%). Method: trace-based dynamic analysis — run the test suite, watch the OS file-system calls, mark any dependency file never touched as bloated (unused, safe to remove). Removal was verified: 100% of test cases still passed afterward.
Headline number: 50.6% of all dependencies are bloated — 25,566 of 50,488 never accessed at runtime. The split matters:
- Direct dependencies: 13.8% bloated (120 of 869) — a developer chose these and mostly chose right.
- Transitive (indirect) dependencies: 51.3% bloated (25,446 of 49,619) — the developer never chose these at all; a dependency of a dependency did.
Where the bloat comes from — this is the "tooling defaults" half of the edge
Two named, mechanical causes, not general carelessness:
- Misclassified dev dependencies. 41.7% of the 120 bloated direct dependencies were declared
by their own maintainers as
devDependencies(build-time only — transpilers, linters, test runners) but ended up required at runtime anyway, most often because a transpiling tool (56% of this group) got pulled into the production path by default packaging behavior rather than by the app author's choice. - CLI tools with no runtime role. 19.2% of bloated direct dependencies are command-line tools
installed as dependencies purely for
npm runconvenience — unused during actual test/runtime execution by construction. - Transitive proliferation compounds it. The paper's example: a single 48-line package pulled in
433 indirect dependencies totaling 298,840 lines of code — a 6,225× amplification from one
require. Nothing about that ratio is visible to the developer who wrote the 48 lines; it is entirely a property of what the dependency tree's own defaults resolve to.
What it does to the thesis
web-almanac-page-weight-2025 established that the median page carries 1.0 MB of unused JavaScript uncompressed at the 90th percentile and that weight kept climbing through a period when core-web-vitals made shipping less a live ranking incentive — a finding with the explanation missing. This paper supplies a mechanism, even though it measures npm packages rather than shipped web bundles directly: half of what a typical dependency tree pulls in was never going to run, and the pull-in decision was made by a transitive dependency's own packaging default, not by the person who will ship the bytes. "Nobody had an incentive not to" (the open question's implicit frame) may be the wrong model — the more specific claim this paper supports is that the person paying the byte cost is often not the person who made the inclusion decision, which no amount of ranking incentive reaches.
Caveat, stated plainly: this measures npm package dependency graphs (what gets installed),
not bundled-and-shipped browser JavaScript (what survives a bundler's tree-shaking and gets sent
over the wire) — the two overlap heavily but are not the same measurement. A modern bundler with
tree-shaking can in principle drop code this paper counts as "bloated" before it ever reaches
bundle-size. The paper does not test that boundary; it stops at node_modules. That is the
residual half of growth edge 2: whether bundler tree-shaking actually removes this class of
bloat before it ships, or whether it survives (dynamic requires, eval, non-static imports are
exactly the cases tree-shakers give up on) — needs a study that measures post-bundle output, not
just the dependency graph.
Related
page-weight · bundle-size · web-almanac-page-weight-2025 · third-party-resources · core-web-vitals · synthesis