Back to Home

npm Dependency Bloat: 3 Causes and Solutions

The article breaks down three reasons for npm dependencies growth: legacy environment support, atomic architecture, and outdated ponyfills. Describes duplication issues, supply chain risks, and cleanup recommendations. Material aimed at middle/senior developers.

Three Reasons for 'Bloat' of npm Dependencies in JS
Advertisement 728x90

Why npm Dependency Trees Bloat: Three Key Reasons

Dependency trees in JavaScript projects often grow due to outdated packages and redundant platform features. This leads to unnecessary download costs, version conflicts, and supply chain vulnerabilities. Let's explore three typical scenarios that explain this situation and how to fix them.

Legacy Environment Support and Realm-Specific Issues

Many packages like is-string or hasown exist for compatibility with ancient runtimes, such as ES3 (IE6/7, early Node.js). They implement missing methods: Array.prototype.forEach, Array.prototype.reduce, Object.keys, Object.defineProperty.

Additional motivations:

Google AdInline article slot
  • Global namespace protection: Node imports primordials (e.g., Map) to prevent breakage from overrides. Packages like math-intrinsics re-export Math.* for similar purposes.
  • Realm compatibility: instanceof doesn't work between iframe and parent (different RegExp constructors). An alternative is Object.prototype.toString.call(val) === '[object RegExp]', as used in chai or is-string for new String(val).
const shebangRegex = /^#!(.*)/;
export default shebangRegex;

For most mid- to senior-level developers, this is overkill: modern Node (not older than 10 years) and browsers don't require such hacks. Recommendation: check your browserslist and remove unnecessary polyfills.

Atomic Architecture: From Theory to Problems

The philosophy of 'atomic' packages involves breaking code into minimal blocks for reuse. An example is the dependency graph of execa with packages like shebang-regex (5 lines of code).

Typical atomic utilities:

Google AdInline article slot
  • arrify: Array.isArray(val) ? val : [val]
  • slash: path slash normalization
  • cli-boxes: JSON with UI element borders
  • path-key: env['PATH'] || env['Path']
  • onetime: one-time function call
  • is-wsl: process.platform === 'linux' && /microsoft/.test(os.release())
  • is-windows: process.platform === 'win32

In theory, this simplifies CLI building. In practice:

  • One-time use: shebang-regex is only used by shebang-command (same author); cli-boxes — only by boxen/ink.
  • Hoisting duplicates: In nuxt, duplicates like is-docker, is-stream, is-wsl (2 versions each). Inline code duplicates for free, without npm overhead.
  • Supply chain risks: More packages mean more vulnerabilities. Compromising one maintainer (as happened last year) affected hundreds of dependencies.

Solution: inline simple functions, use tree-shaking, and pnpm with strict hoisting.

Ponyfills That Have Outlived Their Era

Ponyfills are polyfills without mutating globals, imported directly. Useful for libraries: @fastly/performance-observer-polyfill allows using PerformanceObserver without altering the environment.

Google AdInline article slot

Problem: they aren't removed after native support arrives.

Examples of outdated ponyfills (with weekly downloads):

  • globalthis — for globalThis (supported since 2019, 49M downloads/week)
  • indexof — for Array.prototype.indexOf (since 2010, 2.3M/week)
  • object.entries — for Object.entries (since 2017, 35M/week)

In eslint-plugin-react, half the graph consists of legacy polyfills. For libraries: rely on consumer-side polyfills (core-js). Check caniuse.com before adding.

Key Takeaways

  • Optimize legacy: 90% of packages don't need ES3/ES5 polyfills; use typeof instead of is-string.
  • Atomicity harms: Inline 5-line functions to reduce duplicates and attack surface.
  • Audit ponyfills: Remove packages for features supported for >5 years in target runtime.
  • Tools: Use npm ls --depth=0, depcheck, npm-check-updates for analysis.
  • e18e initiative: Focus on cleaning up outdated deps for performance.

Practical Cleanup Steps

  • Run npm dedupe and npm prune.
  • Add overrides to package.json for forced hoisting.
  • Use esbuild/vite with tree-shaking.
  • Monitor bundle size with webpack-bundle-analyzer.

This can reduce bundle size by 20-50% without losing functionality.

— Editorial Team

Advertisement 728x90

Read Next