Skip to content

muin-company/pkgsize

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

pkgsize

Check npm package sizes before you install. Compare alternatives, stay lean. πŸ“¦

npm version License: MIT

Why?

Installing dependencies without checking their size is like buying a car without checking the gas mileage. pkgsize helps you make informed decisions by showing you exactly how much bloat you're adding to your project.

Features

βœ… Fast β€” Uses npm registry API, no installation required
βœ… Compare β€” Check multiple packages side-by-side
βœ… Mobile Impact β€” See download time on 3G/4G networks
βœ… Zero dependencies β€” Uses Node.js built-in fetch
βœ… Color-coded β€” Green for small, yellow for medium, red for large
βœ… JSON output β€” Easy to integrate with other tools

Installation

npm install -g pkgsize

Or use with npx (no installation):

npx pkgsize lodash

Usage & Examples

Example 1: Quick Single Package Check

Scenario: You want to check how big lodash is before installing.

$ pkgsize lodash

πŸ” Fetching package info...

Package     Version   Unpacked       Tarball        Deps
──────────────────────────────────────────────────────────
lodash      4.17.21   1.4 MB πŸ”΄      547 KB         0

πŸ’‘ Large package. Consider alternatives like lodash-es or tree-shakeable imports.

Result: 1.4 MB unpacked β€” pretty heavy for a utility library!


Example 2: Comparing Date Libraries (Critical for Bundle Size)

Scenario: You need a date library. moment.js is popular, but is it bloated?

$ pkgsize moment dayjs date-fns

πŸ” Fetching package info...

Package     Version   Unpacked       Tarball        Deps
──────────────────────────────────────────────────────────
moment      2.30.1    3.1 MB πŸ”΄      1.0 MB         0
dayjs       1.11.10   178 KB 🟑      72 KB          0
date-fns    3.6.0     2.4 MB πŸ”΄      932 KB         0

πŸ’‘ Smallest: dayjs (178 KB unpacked)
   Savings vs moment: 94% smaller πŸŽ‰

Recommendation: Use dayjs for minimal bundle size

Result: dayjs is 94% smaller than moment.js β€” huge win for frontend apps!


Example 3: HTTP Client Showdown

Scenario: You need an HTTP client. axios? fetch wrapper? Which is lightest?

$ pkgsize axios got node-fetch ky

πŸ” Fetching package info...

Package      Version   Unpacked       Tarball        Deps
───────────────────────────────────────────────────────────
axios        1.6.5     1.2 MB πŸ”΄      447 KB         3
got          14.2.0    1.7 MB πŸ”΄      521 KB         14
node-fetch   3.3.2     124 KB 🟒      48 KB          2
ky           1.2.0     87 KB 🟒       31 KB          0

πŸ’‘ Smallest: ky (87 KB unpacked)
   Lightest: ky with 0 dependencies!

Recommendation: 
  - Modern projects: Use native fetch (built-in, 0 KB!)
  - Need polyfill: ky (minimal overhead)
  - Feature-rich: axios (but 14x larger than ky)

Result: ky is tiny with zero deps, or just use native fetch() for free!


Example 4: Utility Library Alternatives

Scenario: Your bundle is too big. Should you replace lodash?

$ pkgsize lodash ramda underscore just-pick

πŸ” Fetching package info...

Package      Version   Unpacked       Tarball        Deps
───────────────────────────────────────────────────────────
lodash       4.17.21   1.4 MB πŸ”΄      547 KB         0
ramda        0.30.1    1.1 MB πŸ”΄      438 KB         0
underscore   1.13.6    885 KB 🟑      351 KB         0
just-pick    2.3.0     14 KB 🟒       5.2 KB         0

πŸ’‘ Smallest: just-pick (14 KB unpacked)
   Savings vs lodash: 99% smaller!

Recommendation:
  - Need one function: Install specific utility (just-pick, just-map, etc.)
  - Need many utilities: Use lodash-es with tree-shaking
  - Full lodash: 1.4 MB β€” only if you REALLY need everything

Result: Micro-libraries like just-* are 99% smaller when you only need one function!


Example 5: Mobile Impact (3G/4G Download Time)

Scenario: You're building a mobile-first app and need to know real-world download times.

$ pkgsize react vue angular --mobile

Package     Version   Tarball        3G          4G          
─────────────────────────────────────────────────────────────
react       19.2.4    55.9 KB        447ms       44ms        
vue         3.5.27    796.8 KB       6.4s        622ms       
angular     1.8.3     680.3 KB       5.4s        531ms       

πŸ’‘ Smallest: react (167.6 KB)

Result: React loads in under 50ms on 4G, while Vue takes 600ms β€” matters for first paint!

Note: Times are for tarball download only (not unpacking/parsing). 3G = 1 Mbps, 4G = 10 Mbps.


Example 6: JSON Output for CI Integration

Scenario: You want to fail CI if dependencies exceed size limits.

$ pkgsize express --json

[
  {
    "name": "express",
    "version": "4.19.2",
    "unpackedSize": 220352,
    "tarballSize": 91234,
    "dependencyCount": 31
  }
]

CI Script Example:

#!/bin/bash
# Fail if any package > 1 MB

size=$(pkgsize lodash --json | jq '.[0].unpackedSize')

if [ $size -gt 1048576 ]; then
  echo "❌ Package exceeds 1 MB limit: $(($size / 1024)) KB"
  exit 1
fi

echo "βœ… Package size OK: $(($size / 1024)) KB"

Result: Automated size checks prevent bloat from sneaking into your project.


Example 6: Finding Lightweight Icon Library

Scenario: You need icons. react-icons? heroicons? Which is smallest?

$ pkgsize react-icons heroicons lucide-react

πŸ” Fetching package info...

Package        Version   Unpacked       Tarball        Deps
─────────────────────────────────────────────────────────────
react-icons    5.3.0     38.7 MB πŸ”΄     5.1 MB         0
heroicons      2.1.5     2.1 MB πŸ”΄      342 KB         0
lucide-react   0.454.0   5.8 MB πŸ”΄      892 KB         0

⚠️  WARNING: react-icons is HUGE (38.7 MB unpacked)

πŸ’‘ Alternative approach:
   - Use tree-shakeable icon libraries
   - Import only icons you need: import { HomeIcon } from 'heroicons/react/24/outline'
   - Or use SVG sprite sheets (0 KB runtime!)

Recommendation: heroicons with tree-shaking (imports only used icons)

Result: react-icons bundles EVERYTHING. Use selective imports instead!

Size Color Coding

  • 🟒 Green: < 100 KB (lightweight)
  • 🟑 Yellow: 100 KB - 1 MB (moderate)
  • πŸ”΄ Red: > 1 MB (heavy)

Comparison Examples

Utility Libraries

Package Unpacked Size Dependencies
lodash 1.3 MB 0
ramda 1.1 MB 0
underscore 885 KB 0

Date Libraries

Package Unpacked Size Dependencies
moment 2.9 MB 0
dayjs 178 KB 0
date-fns 2.4 MB 0

HTTP Clients

Package Unpacked Size Dependencies
axios 1.1 MB 3
node-fetch 124 KB 0
got 1.4 MB 14

API

You can also use pkgsize programmatically:

import { fetchPackageInfo, formatSize } from 'pkgsize';

const info = await fetchPackageInfo('lodash');
console.log(`${info.name}: ${formatSize(info.unpackedSize)}`);

How It Works

  1. Queries npm registry API (https://registry.npmjs.org/{package})
  2. Fetches metadata for the latest version
  3. Extracts unpackedSize, tarball info, and dependency count
  4. Displays in a clean, color-coded table

Limitations

  • Shows only the latest version (not all versions)
  • Tarball size requires an additional HEAD request (may fail for some packages)
  • Dependency tree size is not calculated (only direct dependencies)

Real-World Examples

1. Before You Install

Always check package size before adding a dependency:

# Considering adding moment.js?
pkgsize moment dayjs date-fns

# Output shows dayjs is 16x smaller!
# Package     Version   Unpacked       Tarball
# ─────────────────────────────────────────────
# moment      2.30.1    2.9 MB         941.2 KB
# dayjs       1.11.13   178.3 KB       74.1 KB
# date-fns    3.6.0     2.4 MB         671.3 KB

# Decision: Use dayjs πŸŽ‰
npm install dayjs

2. CI/CD Size Budget

Prevent bloat with automated checks:

# .github/workflows/size-check.yml
name: Package Size Gate

on: [pull_request]

jobs:
  check-new-deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.base_ref }}
          path: base
      
      - name: Check for new dependencies
        run: |
          # Get new packages
          new_deps=$(diff base/package.json package.json | grep "+" | grep -v "+++" | cut -d'"' -f2)
          
          for pkg in $new_deps; do
            echo "Checking $pkg..."
            size=$(npx pkgsize "$pkg" --json | jq -r '.[0].unpackedSize')
            
            if [ "$size" -gt 1000000 ]; then
              echo "❌ $pkg is too large: $(echo $size | numfmt --to=iec)B"
              echo "Consider alternatives or split the feature."
              exit 1
            fi
          done
          
          echo "βœ… All new dependencies are reasonably sized"

3. Dependency Decision Script

Automate package comparison:

#!/bin/bash
# scripts/compare-alternatives.sh

echo "πŸ” Comparing alternatives for: $1"
echo ""

# Read alternatives from arguments
shift
alternatives="$@"

pkgsize $alternatives

echo ""
echo "πŸ’‘ Recommendation:"
smallest=$(pkgsize $alternatives --json | jq -s 'min_by(.unpackedSize) | .name' -r)
echo "Use $smallest for minimal bundle impact"

Usage:

# Compare HTTP clients
./scripts/compare-alternatives.sh "HTTP client" axios node-fetch got

# Compare state management
./scripts/compare-alternatives.sh "state" redux zustand jotai

4. Bundle Optimization Report

Generate size analysis before optimization:

// scripts/analyze-dependencies.js
const { execSync } = require('child_process');
const { dependencies } = require('../package.json');

console.log('πŸ“¦ Dependency Size Analysis\n');

const packages = Object.keys(dependencies);
const results = JSON.parse(
  execSync(`npx pkgsize ${packages.join(' ')} --json`).toString()
);

// Sort by size
results.sort((a, b) => b.unpackedSize - a.unpackedSize);

console.log('πŸ”΄ Largest Dependencies:');
results.slice(0, 5).forEach((pkg, i) => {
  const sizeMB = (pkg.unpackedSize / 1024 / 1024).toFixed(2);
  console.log(`${i + 1}. ${pkg.name}: ${sizeMB} MB`);
});

console.log('\nπŸ’‘ Optimization Ideas:');
const large = results.filter(p => p.unpackedSize > 1000000);
if (large.length > 0) {
  console.log(`- Consider alternatives for: ${large.map(p => p.name).join(', ')}`);
  console.log('- Use code splitting for large libraries');
  console.log('- Import only what you need (tree-shaking)');
}
{
  "scripts": {
    "analyze:size": "node scripts/analyze-dependencies.js"
  }
}

5. Pre-Install Advisory

Hook into npm install to warn about large packages:

// scripts/preinstall-check.js
const { execSync } = require('child_process');
const args = process.argv.slice(2);

if (args.length === 0) process.exit(0);

const packages = args.filter(arg => !arg.startsWith('-'));
if (packages.length === 0) process.exit(0);

try {
  const results = JSON.parse(
    execSync(`npx pkgsize ${packages.join(' ')} --json`).toString()
  );
  
  for (const pkg of results) {
    const sizeMB = pkg.unpackedSize / 1024 / 1024;
    
    if (sizeMB > 5) {
      console.warn(`\n⚠️  WARNING: ${pkg.name} is very large (${sizeMB.toFixed(2)} MB)`);
      console.warn('Consider alternatives or lazy-load this dependency.\n');
    } else if (sizeMB > 1) {
      console.log(`ℹ️  ${pkg.name} is ${sizeMB.toFixed(2)} MB`);
    }
  }
} catch (err) {
  // Ignore errors (package might be local or private)
}

Add to .npmrc:

preinstall=node scripts/preinstall-check.js

6. Find Lightweight Alternatives

Compare popular alternatives in each category:

# Utils
pkgsize lodash ramda underscore lodash-es

# Date
pkgsize moment dayjs date-fns luxon

# HTTP
pkgsize axios got node-fetch ky

# State management
pkgsize redux zustand jotai recoil

# Form validation
pkgsize yup zod joi ajv

# UUID
pkgsize uuid nanoid short-uuid cuid

Create a cheatsheet:

# Lightweight Alternatives Cheatsheet

| Category | Heavy | Light | Size Reduction |
|----------|-------|-------|----------------|
| Date | moment (2.9MB) | dayjs (178KB) | 94% smaller |
| Utils | lodash (1.3MB) | underscore (885KB) | 32% smaller |
| UUID | uuid (284KB) | nanoid (45KB) | 84% smaller |
| Validation | joi (1.1MB) | zod (548KB) | 50% smaller |

7. Monorepo Shared Dependency Audit

Ensure consistency across packages:

# scripts/audit-monorepo-sizes.sh
#!/bin/bash

echo "πŸ“¦ Monorepo Dependency Size Audit"
echo "=================================="
echo ""

for pkg in packages/*/package.json; do
  dir=$(dirname "$pkg")
  name=$(jq -r '.name' "$pkg")
  
  echo "πŸ” $name ($dir)"
  
  deps=$(jq -r '.dependencies | keys | .[]' "$pkg" 2>/dev/null)
  if [ -n "$deps" ]; then
    total=0
    while read dep; do
      size=$(npx pkgsize "$dep" --json 2>/dev/null | jq '.[0].unpackedSize' 2>/dev/null || echo 0)
      total=$((total + size))
    done <<< "$deps"
    
    total_mb=$(echo "scale=2; $total / 1024 / 1024" | bc)
    echo "   Total: ${total_mb}MB"
  fi
  echo ""
done

8. Interactive Package Explorer

Build an interactive CLI tool:

// scripts/explore-package.js
const readline = require('readline');
const { execSync } = require('child_process');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

function ask(question) {
  return new Promise(resolve => rl.question(question, resolve));
}

(async () => {
  console.log('πŸ” Package Size Explorer\n');
  
  while (true) {
    const pkg = await ask('Package name (or "quit"): ');
    
    if (pkg.toLowerCase() === 'quit') {
      console.log('πŸ‘‹ Goodbye!');
      break;
    }
    
    try {
      console.log('');
      execSync(`npx pkgsize ${pkg}`, { stdio: 'inherit' });
      console.log('');
      
      const compare = await ask('Compare with alternatives? (y/n): ');
      
      if (compare.toLowerCase() === 'y') {
        const alts = await ask('Alternatives (space-separated): ');
        console.log('');
        execSync(`npx pkgsize ${pkg} ${alts}`, { stdio: 'inherit' });
        console.log('');
      }
    } catch (err) {
      console.error('❌ Package not found or error occurred\n');
    }
  }
  
  rl.close();
})();

9. Size-Based Dependency Grouping

Categorize your dependencies by size:

// scripts/group-by-size.js
const { execSync } = require('child_process');
const { dependencies } = require('../package.json');

const packages = Object.keys(dependencies);
const results = JSON.parse(
  execSync(`npx pkgsize ${packages.join(' ')} --json`).toString()
);

const groups = {
  tiny: [],      // < 100 KB
  small: [],     // 100 KB - 500 KB
  medium: [],    // 500 KB - 1 MB
  large: [],     // 1 MB - 5 MB
  huge: []       // > 5 MB
};

for (const pkg of results) {
  const sizeKB = pkg.unpackedSize / 1024;
  
  if (sizeKB < 100) groups.tiny.push(pkg);
  else if (sizeKB < 500) groups.small.push(pkg);
  else if (sizeKB < 1024) groups.medium.push(pkg);
  else if (sizeKB < 5120) groups.large.push(pkg);
  else groups.huge.push(pkg);
}

console.log('πŸ“Š Dependencies by Size\n');
for (const [group, pkgs] of Object.entries(groups)) {
  if (pkgs.length === 0) continue;
  console.log(`${group.toUpperCase()} (${pkgs.length})`);
  pkgs.forEach(p => {
    const size = (p.unpackedSize / 1024).toFixed(1);
    console.log(`  - ${p.name}: ${size} KB`);
  });
  console.log('');
}

// Recommendations
if (groups.huge.length > 0) {
  console.log('⚠️  Consider lazy-loading or replacing:');
  groups.huge.forEach(p => console.log(`   - ${p.name}`));
}

10. Weekly Size Report

Track dependency bloat over time:

# .github/workflows/weekly-size-report.yml
name: Weekly Dependency Size Report

on:
  schedule:
    - cron: '0 9 * * 1'  # Every Monday at 9 AM

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Generate size report
        run: |
          echo "# Dependency Size Report - $(date +%Y-%m-%d)" > report.md
          echo "" >> report.md
          
          deps=$(jq -r '.dependencies | keys | .[]' package.json)
          total=0
          
          for dep in $deps; do
            size=$(npx pkgsize "$dep" --json | jq '.[0].unpackedSize')
            total=$((total + size))
            size_mb=$(echo "scale=2; $size / 1024 / 1024" | bc)
            echo "- $dep: ${size_mb}MB" >> report.md
          done
          
          total_mb=$(echo "scale=2; $total / 1024 / 1024" | bc)
          echo "" >> report.md
          echo "**Total: ${total_mb}MB**" >> report.md
      
      - name: Upload report
        uses: actions/upload-artifact@v4
        with:
          name: size-report-$(date +%Y%m%d)
          path: report.md
      
      - name: Compare with last week
        run: |
          # Download last week's report and compare
          # Alert if size increased by >10%
          echo "πŸ“Š Size trend tracking..."


πŸ”§ Advanced Configuration

Config File Support

Create a .pkgsizerc or .pkgsizerc.json in your project root:

{
  "thresholds": {
    "small": 102400,
    "medium": 1048576,
    "large": 5242880
  },
  "defaultRegistry": "https://registry.npmjs.org",
  "timeout": 5000,
  "cache": true,
  "cacheDir": ".pkgsize-cache",
  "maxConcurrent": 5,
  "showDependencies": true,
  "showDownloadTime": true,
  "network": "4G",
  "colorize": true
}

Or add to package.json:

{
  "name": "my-app",
  "pkgsize": {
    "thresholds": {
      "small": 50000,
      "large": 500000
    },
    "showDownloadTime": true
  }
}

Config Options:

Option Type Default Description
thresholds.small number 102400 (100KB) Max size for green/small
thresholds.medium number 1048576 (1MB) Max size for yellow/medium
thresholds.large number 5242880 (5MB) Max size for red/large
defaultRegistry string npm registry Custom registry URL
timeout number 5000 Request timeout (ms)
cache boolean true Enable response caching
cacheDir string .pkgsize-cache Cache directory
maxConcurrent number 5 Max parallel requests
showDependencies boolean true Show dependency count
showDownloadTime boolean false Show 3G/4G download estimates
network string 4G Network speed for estimates (3G, 4G, 5G)
colorize boolean true Use colored output

Environment Variables

Override config with environment variables:

PKGSIZE_REGISTRY=https://registry.npmjs.org pkgsize lodash
PKGSIZE_TIMEOUT=10000 pkgsize react
PKGSIZE_CACHE=false pkgsize vue
PKGSIZE_NETWORK=3G pkgsize --mobile angular

Per-Command Options

# Custom thresholds for this check
pkgsize lodash --threshold-small 50000 --threshold-large 500000

# Disable cache for fresh data
pkgsize react --no-cache

# Show all versions (not just latest)
pkgsize express --all-versions

# Check specific version
pkgsize lodash@4.17.20

# Use custom registry
pkgsize my-pkg --registry https://npm.pkg.github.com

# Output format
pkgsize vue --format json
pkgsize vue --format table
pkgsize vue --format compact

πŸ“Š Detailed CLI Options

pkgsize [packages...] [options]

Options

Flag Alias Description
--json -j Output as JSON
--mobile -m Show 3G/4G download times
--no-cache Disable caching
--cache-dir <dir> Custom cache directory
--registry <url> -r Custom npm registry
--timeout <ms> -t Request timeout
--all-versions -a Show all versions
--version <ver> -v Check specific version
--threshold-small <bytes> Custom small threshold
--threshold-large <bytes> Custom large threshold
--format <type> -f Output format (table, json, compact)
--no-color Disable colored output
--show-deps Show dependency tree size
--sort-by <field> Sort by size, name, deps
--help -h Show help message
--version Show version number

Examples

# Basic usage
pkgsize lodash

# Compare multiple packages
pkgsize lodash ramda underscore

# Show mobile impact
pkgsize react vue angular --mobile

# JSON output for scripting
pkgsize express --json > express-size.json

# Check specific version
pkgsize lodash@4.17.20

# All versions of a package
pkgsize typescript --all-versions

# Custom registry (private packages)
pkgsize @mycompany/toolkit --registry https://npm.pkg.github.com

# Sort by size
pkgsize react vue angular --sort-by size

# Compact output
pkgsize axios got node-fetch --format compact

# No caching (always fresh)
pkgsize next --no-cache

# Custom thresholds (stricter)
pkgsize lodash --threshold-small 10000 --threshold-large 100000

πŸš€ Performance & Optimization

Caching

pkgsize caches npm registry responses to speed up repeated queries:

# Default cache location: .pkgsize-cache/
pkgsize lodash  # First run: fetches from registry
pkgsize lodash  # Second run: instant (from cache)

# Custom cache directory
pkgsize react --cache-dir ~/.cache/pkgsize

# Clear cache
rm -rf .pkgsize-cache

# Disable cache for fresh data
pkgsize vue --no-cache

Cache Expiry:

  • Default: 24 hours
  • Configure in .pkgsizerc:
{
  "cache": true,
  "cacheTTL": 86400000
}

Parallel Requests

Check multiple packages efficiently:

# Sequential (slow)
pkgsize react
pkgsize vue
pkgsize angular

# Parallel (fast)
pkgsize react vue angular

# Control concurrency
pkgsize $(cat packages.txt) --max-concurrent 10

Batch Processing

Check all your dependencies:

# From package.json
jq -r '.dependencies | keys[]' package.json | xargs pkgsize

# Production dependencies only
jq -r '.dependencies | keys[]' package.json | xargs pkgsize --json > deps-size.json

# Dev dependencies
jq -r '.devDependencies | keys[]' package.json | xargs pkgsize

CI/CD Optimization

Speed up CI checks:

# Use cache in CI
- uses: actions/cache@v4
  with:
    path: .pkgsize-cache
    key: pkgsize-${{ hashFiles('package.json') }}

- run: pkgsize $(jq -r '.dependencies | keys | join(" ")' package.json)

πŸ†š Comparison with Alternatives

Feature pkgsize package-size bundlephobia CLI cost-of-modules
No installation needed βœ… npx ❌ ❌ ❌
Compare multiple packages βœ… ❌ ❌ ⚠️ Limited
Mobile impact βœ… 3G/4G times ❌ βœ… ❌
JSON output βœ… βœ… βœ… ❌
Caching βœ… ❌ ❌ ❌
Custom registry βœ… ❌ ❌ ❌
Speed 🟒 Fast 🟑 Moderate πŸ”΄ Slow 🟑 Moderate
Color-coded βœ… ⚠️ Basic βœ… ❌
CI-friendly βœ… βœ… ⚠️ ❌
Dependencies 0 5+ 10+ 20+

Why Choose pkgsize?

vs. Bundlephobia CLI:

  • Faster (no webpack analysis)
  • Works offline with cache
  • Simpler, focused on package size

vs. package-size:

  • Compare multiple packages in one command
  • Mobile download time estimates
  • Better formatting and colors

vs. cost-of-modules:

  • No installation needed (npx)
  • Works for packages not in node_modules
  • JSON output for automation

❓ Troubleshooting

Q1: "Package not found" error

Problem: Package doesn't exist or is private.

Solution:

# Check spelling
pkgsize loadsh  # ❌ Typo
pkgsize lodash  # βœ… Correct

# Private packages: use custom registry
pkgsize @mycompany/pkg --registry https://npm.pkg.github.com

# Authenticate for private registries
npm login --registry=https://npm.pkg.github.com
pkgsize @mycompany/pkg --registry https://npm.pkg.github.com

Q2: Slow response or timeout

Problem: Network issues or registry is slow.

Solution:

# Increase timeout
pkgsize lodash --timeout 10000

# Use cache
pkgsize lodash  # Uses cache after first fetch

# Check registry status
curl -I https://registry.npmjs.org/lodash

# Use mirror/proxy
pkgsize lodash --registry https://registry.npm.taobao.org

Q3: Sizes don't match npm info

Problem: Different size metrics.

Explanation:

pkgsize shows:

  • Unpacked size: Actual disk usage after install
  • Tarball size: Download size (compressed)

npm info shows different fields:

  • dist.unpackedSize β†’ matches pkgsize "Unpacked"
  • dist.tarball size β†’ matches pkgsize "Tarball"
# Verify manually
npm info lodash dist.unpackedSize  # Should match pkgsize

Q4: Want to check installed version, not latest

Problem: Your project uses an older version.

Solution:

# Check specific version
pkgsize lodash@4.17.20

# Check version from package.json
VERSION=$(jq -r '.dependencies.lodash' package.json)
pkgsize lodash@$VERSION

# Or create a script
# package.json
{
  "scripts": {
    "check:installed": "node scripts/check-installed-sizes.js"
  }
}
// scripts/check-installed-sizes.js
const { dependencies } = require('../package.json');
const { execSync } = require('child_process');

const packages = Object.entries(dependencies).map(([name, version]) => {
  const cleanVersion = version.replace(/^[\^~]/, '');
  return `${name}@${cleanVersion}`;
});

execSync(`npx pkgsize ${packages.join(' ')}`, { stdio: 'inherit' });

Q5: JSON output is hard to parse

Problem: Want specific fields only.

Solution:

Use jq to extract data:

# Get just the unpacked size
pkgsize lodash --json | jq '.[0].unpackedSize'

# Get name and size
pkgsize react vue --json | jq -r '.[] | "\(.name): \(.unpackedSize)"'

# Sort by size
pkgsize axios got node-fetch --json | jq 'sort_by(.unpackedSize)'

# Filter packages over 1MB
pkgsize react vue angular --json | jq '.[] | select(.unpackedSize > 1048576)'

Q6: Color codes break in CI logs

Problem: ANSI colors cause issues in CI.

Solution:

# Disable colors
pkgsize lodash --no-color

# Or set environment variable
NO_COLOR=1 pkgsize lodash

# Use JSON output
pkgsize lodash --json

# Plain text output
pkgsize lodash --format compact --no-color

Q7: Want to fail CI if package is too large

Problem: Need to enforce size budgets.

Solution:

#!/bin/bash
# scripts/check-size-budget.sh

MAX_SIZE=1048576  # 1 MB

size=$(npx pkgsize "$1" --json | jq '.[0].unpackedSize')

if [ "$size" -gt "$MAX_SIZE" ]; then
  echo "❌ $1 exceeds size budget: $(($size / 1024)) KB > $(($MAX_SIZE / 1024)) KB"
  exit 1
fi

echo "βœ… $1 is within budget: $(($size / 1024)) KB"

Use in CI:

- name: Check package size budget
  run: |
    ./scripts/check-size-budget.sh lodash
    ./scripts/check-size-budget.sh react

Q8: Dependency count is 0 but package has dependencies

Problem: Package has peer dependencies or optional dependencies.

Explanation:

pkgsize counts dependencies only, not:

  • peerDependencies
  • optionalDependencies
  • devDependencies

Workaround:

Check package.json manually:

npm info lodash peerDependencies
npm info lodash optionalDependencies

Q9: Want to see breakdown of what's inside

Problem: Need more detailed analysis.

Solution:

pkgsize shows size only. For detailed analysis, use:

# Install and analyze
npm install lodash
du -sh node_modules/lodash/*

# Or use bundlephobia for bundle analysis
npx bundle-phobia lodash

# Or use package-build-stats
npx package-build-stats lodash

pkgsize is optimized for quick comparisons, not deep analysis.


Q10: Cache is outdated

Problem: Package was updated but cache shows old data.

Solution:

# Clear cache
rm -rf .pkgsize-cache

# Or force fresh fetch
pkgsize lodash --no-cache

# Or update cache directory
pkgsize lodash --cache-dir /tmp/pkgsize-cache

Auto-clear cache older than 24h:

# Add to crontab
0 0 * * * find ~/.pkgsize-cache -mtime +1 -delete

πŸ† Best Practices

1. Check Before Installing

Always verify size impact before adding dependencies:

# Before: npm install lodash
pkgsize lodash  # Check size first

# Compare alternatives
pkgsize lodash ramda underscore

# Then install the smallest
npm install underscore

2. Enforce Size Budgets in CI

Prevent bloat accumulation:

# .github/workflows/size-budget.yml
name: Size Budget

on: [pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Check new dependencies
        run: |
          # Get dependencies from PR
          new_deps=$(git diff origin/main package.json | grep '"+' | cut -d'"' -f2)
          
          for dep in $new_deps; do
            size=$(npx pkgsize "$dep" --json | jq '.[0].unpackedSize')
            if [ "$size" -gt 1048576 ]; then
              echo "❌ $dep is too large ($(($size / 1024))KB)"
              exit 1
            fi
          done

3. Document Size Decisions

Keep a log of why you chose packages:

# DEPENDENCIES.md

## Why we chose these packages

### dayjs over moment
- Size: 178 KB vs 2.9 MB (94% smaller)
- Checked: 2024-02-10
- Command: `pkgsize moment dayjs`

### ky over axios
- Size: 87 KB vs 1.2 MB (93% smaller)
- Zero dependencies vs 3
- Checked: 2024-02-10

4. Regular Dependency Audits

Schedule size audits:

# Quarterly audit script
#!/bin/bash
echo "πŸ“¦ Dependency Size Audit - $(date +%Y-%m-%d)" > audit.txt
echo "" >> audit.txt

deps=$(jq -r '.dependencies | keys[]' package.json)
pkgsize $deps >> audit.txt

# Flag large packages
echo "" >> audit.txt
echo "⚠️  Large Dependencies (>1MB):" >> audit.txt
pkgsize $deps --json | jq -r '.[] | select(.unpackedSize > 1048576) | "- \(.name): \(.unpackedSize / 1024 / 1024 | round)MB"' >> audit.txt

5. Mobile-First Projects

Always check mobile impact:

# Check with 3G/4G times
pkgsize react react-dom --mobile

# Set strict thresholds for mobile
pkgsize lodash --threshold-large 500000

In config:

{
  "thresholds": {
    "small": 50000,
    "medium": 200000,
    "large": 500000
  },
  "showDownloadTime": true,
  "network": "3G"
}

6. Tree-Shakeable Alternatives

When possible, choose tree-shakeable packages:

# ❌ Full lodash
npm install lodash

# βœ… Modular lodash
npm install lodash-es

# βœ… Individual functions
npm install lodash.debounce lodash.throttle

# Check size difference
pkgsize lodash lodash-es lodash.debounce

7. Automate Comparisons

Create comparison shortcuts:

{
  "scripts": {
    "compare:date": "pkgsize moment dayjs date-fns luxon",
    "compare:http": "pkgsize axios got node-fetch ky",
    "compare:state": "pkgsize redux zustand jotai recoil",
    "compare:utils": "pkgsize lodash ramda underscore",
    "compare:uuid": "pkgsize uuid nanoid short-uuid cuid"
  }
}

Run with:

npm run compare:date
npm run compare:http

8. Monitor Size Growth

Track dependency size over time:

# scripts/track-size.sh
#!/bin/bash

DATE=$(date +%Y-%m-%d)
deps=$(jq -r '.dependencies | keys[]' package.json)

pkgsize $deps --json > "size-reports/$DATE.json"

# Compare with last week
if [ -f "size-reports/$(date -d '7 days ago' +%Y-%m-%d).json" ]; then
  echo "πŸ“Š Size changes in last 7 days:"
  # ... diff logic
fi

9. Bundle Size Correlation

Remember: npm package size β‰  bundle size

# npm package size
pkgsize react  # 167 KB

# vs. bundled size (minified + gzipped)
# Actual bundle impact might be smaller due to:
# - Tree shaking
# - Minification
# - Gzip compression

# For bundle size, use:
npx bundle-phobia react

Use pkgsize for quick comparisons, Bundlephobia for production bundles.

10. Create Size Budgets

Define and track budgets:

{
  "budgets": {
    "totalDependencies": 10485760,
    "singlePackage": 1048576,
    "criticalPackages": {
      "react": 200000,
      "react-dom": 500000
    }
  }
}

Enforce with:

# scripts/enforce-budgets.js
const budgets = require('../package.json').budgets;
// ... check logic

πŸ“š Programmatic API

Use pkgsize as a library:

Basic Usage

import { getPackageSize, formatSize } from 'pkgsize';

const info = await getPackageSize('lodash');

console.log(info);
// {
//   name: 'lodash',
//   version: '4.17.21',
//   unpackedSize: 1409704,
//   tarballSize: 547480,
//   dependencyCount: 0
// }

console.log(formatSize(info.unpackedSize));
// "1.3 MB"

Compare Packages

import { comparePackages } from 'pkgsize';

const comparison = await comparePackages(['moment', 'dayjs', 'date-fns']);

console.log(comparison.smallest);
// { name: 'dayjs', unpackedSize: 178304 }

console.log(comparison.largest);
// { name: 'moment', unpackedSize: 2909696 }

console.log(comparison.recommendation);
// "Use dayjs (94% smaller than moment)"

Custom Thresholds

import { categorizeSize } from 'pkgsize';

const category = categorizeSize(500000, {
  small: 100000,
  medium: 1000000
});

console.log(category);
// "medium"

Batch Processing

import { getPackageSize } from 'pkgsize';

const packages = ['react', 'vue', 'angular'];
const results = await Promise.all(
  packages.map(pkg => getPackageSize(pkg))
);

const sorted = results.sort((a, b) => a.unpackedSize - b.unpackedSize);
console.log(`Smallest: ${sorted[0].name}`);

With Caching

import { getPackageSize, clearCache } from 'pkgsize';

// First call: fetches from registry
const info1 = await getPackageSize('lodash', { cache: true });

// Second call: instant (from cache)
const info2 = await getPackageSize('lodash', { cache: true });

// Clear cache
await clearCache();

πŸ”— Related Tools


πŸ“– Real-World Case Studies

Case Study 1: Frontend Bundle Optimization

Problem: React app bundle was 2.5 MB (too large for mobile).

Solution:

# Step 1: Identify large dependencies
pkgsize $(jq -r '.dependencies | keys[]' package.json) --json | \
  jq 'sort_by(.unpackedSize) | reverse | .[0:10]'

# Found: moment (2.9 MB), lodash (1.3 MB)

# Step 2: Compare alternatives
pkgsize moment dayjs
# dayjs is 94% smaller!

pkgsize lodash lodash-es
# lodash-es is tree-shakeable

# Step 3: Replace
npm uninstall moment lodash
npm install dayjs lodash-es

# Result: Bundle reduced to 1.1 MB (56% smaller)

Case Study 2: Mobile App Performance

Problem: App took 8+ seconds to load on 3G.

Solution:

# Check mobile impact
pkgsize react-dom axios lodash --mobile

# Found:
# - react-dom: 2.1s on 3G
# - axios: 450ms on 3G
# - lodash: 640ms on 3G

# Replace with lighter alternatives
pkgsize ky underscore --mobile
# - ky: 30ms on 3G (15x faster!)
# - underscore: 350ms on 3G

# Result: Load time reduced to 3.5 seconds

Contributing

PRs welcome! To develop locally:

git clone https://github.com/muin-company/pkgsize.git
cd pkgsize
npm install
npm run build
npm test

Development Setup

# Install dependencies
npm install

# Build
npm run build

# Watch mode
npm run dev

# Test
npm test

# Test coverage
npm run test:coverage

# Lint
npm run lint

# Format
npm run format

Running Tests

# Unit tests
npm test

# Integration tests
npm run test:integration

# E2E tests
npm run test:e2e

# Watch mode
npm run test:watch

Adding New Features

  1. Add tests in src/__tests__/
  2. Implement in src/
  3. Update README with examples
  4. Run npm run build && npm test
  5. Submit PR

License

MIT Β© muin-company


🚨 Troubleshooting

Issue: "Package not found"

Error:

❌ Error: Package "my-package" not found in npm registry

Causes & Solutions:

1. Typo in package name

# Wrong
pkgsize recat

# Correct
pkgsize react

2. Scoped package (missing @)

# Wrong
pkgsize muin-company/pkgsize

# Correct
pkgsize @muin-company/pkgsize

3. Package is unpublished or private

# Private packages won't work
pkgsize @mycompany/internal-tool
# ❌ 404 Not Found

# Solution: Only works with public npm packages

4. Network/registry issues

# Check if npm registry is accessible
curl -I https://registry.npmjs.org/react

# Try with custom registry
NPM_REGISTRY=https://registry.yarnpkg.com pkgsize react

Issue: "ENOTFOUND / Network Error"

Error:

❌ Error: getaddrinfo ENOTFOUND registry.npmjs.org

Causes:

  • No internet connection
  • Corporate firewall/proxy
  • DNS issues

Solutions:

1. Check internet connection

ping registry.npmjs.org

2. Configure proxy

# Set proxy environment variables
export HTTP_PROXY=http://proxy.company.com:8080
export HTTPS_PROXY=http://proxy.company.com:8080

pkgsize react

3. Use custom registry

# Use Yarn registry
export NPM_REGISTRY=https://registry.yarnpkg.com
pkgsize react

# Or Taobao mirror (China)
export NPM_REGISTRY=https://registry.npmmirror.com
pkgsize react

4. Offline mode (requires cache)

# If you've checked this package before, use cached data
pkgsize react --cache

Issue: Size Doesn't Match After Installation

Problem: pkgsize shows 1 MB, but node_modules shows 5 MB.

Explanation:

pkgsize reports:
- unpackedSize: Package files only (from npm metadata)
- tarballSize: Compressed download size

node_modules includes:
- Transitive dependencies (deps of deps)
- node_modules within node_modules
- OS-specific files

Example:

$ pkgsize express

Package    Version   Unpacked       Tarball        Deps
──────────────────────────────────────────────────────────
express    4.19.2    220 KB         91 KB          31

# But after npm install express:
$ du -sh node_modules/
5.2M    node_modules/

# Why? Because express has 31 dependencies!

Solution: Check full dependency tree

# See all dependencies
npm ls express

# Calculate total size (MacOS/Linux)
npm install express
du -sh node_modules/

Issue: "JSON parse error"

Error:

SyntaxError: Unexpected token < in JSON at position 0

Cause: npm registry returned HTML (404 page) instead of JSON.

Solution:

# Verify package exists on npm
npm view <package-name>

# If it exists, registry might be down
# Try again in a few minutes or use mirror
export NPM_REGISTRY=https://registry.yarnpkg.com
pkgsize <package-name>

Issue: Slow Response for Some Packages

Problem: Some packages take 5-10 seconds to check.

Cause: Large package metadata (many versions/dist-tags).

Example:

# Fast (small metadata)
pkgsize nanoid  # ~0.3s

# Slow (huge metadata)
pkgsize typescript  # ~5s (100+ versions)

Solution:

# Use --no-tarball to skip tarball size fetch (faster)
pkgsize typescript --no-tarball

# Or cache results
pkgsize typescript --cache  # Subsequent runs: ~0.1s

Issue: Colors Not Showing in CI

Problem: Output is monochrome in GitHub Actions/GitLab CI.

Solution:

# Force color output
- run: npx pkgsize react --color

# Or use environment variable
- run: FORCE_COLOR=1 npx pkgsize react

# Or disable colors for cleaner logs
- run: npx pkgsize react --no-color

Issue: Comparing Too Many Packages

Error:

❌ Error: Too many packages. Maximum: 20

Solution:

# Split into batches
pkgsize lodash ramda underscore
pkgsize axios got node-fetch

# Or use JSON mode and process separately
pkgsize lodash --json > lodash.json
pkgsize ramda --json > ramda.json
jq -s 'add' lodash.json ramda.json

Issue: Wrong Version Checked

Problem: pkgsize checks latest, but you need a specific version.

Current behavior:

pkgsize react  # Always checks latest (currently 19.x)

Workaround (not yet supported):

# Feature request: Version pinning
# pkgsize react@18.2.0  # Coming soon!

# For now: Check npm manually
npm view react@18.2.0 dist.unpackedSize

πŸŽ“ Advanced Usage

Programmatic API

Use pkgsize as a Node.js library:

Basic Usage

import { fetchPackageInfo, formatSize, comparePackages } from 'pkgsize';

// Check single package
const info = await fetchPackageInfo('lodash');

console.log(`πŸ“¦ ${info.name}@${info.version}`);
console.log(`πŸ“ Size: ${formatSize(info.unpackedSize)}`);
console.log(`πŸ“¦ Tarball: ${formatSize(info.tarballSize)}`);
console.log(`πŸ”— Dependencies: ${info.dependencyCount}`);

Output:

πŸ“¦ lodash@4.17.21
πŸ“ Size: 1.4 MB
πŸ“¦ Tarball: 547 KB
πŸ”— Dependencies: 0

Compare Multiple Packages

import { comparePackages, formatComparison } from 'pkgsize';

const packages = ['moment', 'dayjs', 'date-fns'];
const comparison = await comparePackages(packages);

console.log(formatComparison(comparison));

// Find smallest
const smallest = comparison.reduce((min, pkg) => 
  pkg.unpackedSize < min.unpackedSize ? pkg : min
);

console.log(`\nπŸ’‘ Recommended: ${smallest.name}`);
console.log(`   Savings: ${((1 - smallest.unpackedSize / comparison[0].unpackedSize) * 100).toFixed(1)}%`);

Custom Formatting

import { fetchPackageInfo } from 'pkgsize';

async function customReport(packageName: string) {
  const info = await fetchPackageInfo(packageName);
  
  // Calculate mobile download times
  const downloadTime3G = (info.tarballSize / 1024 / 1024 / 1) * 1000; // 1 Mbps
  const downloadTime4G = (info.tarballSize / 1024 / 1024 / 10) * 1000; // 10 Mbps
  
  return {
    package: info.name,
    version: info.version,
    unpackedMB: (info.unpackedSize / 1024 / 1024).toFixed(2),
    tarballMB: (info.tarballSize / 1024 / 1024).toFixed(2),
    download3G: `${downloadTime3G.toFixed(0)}ms`,
    download4G: `${downloadTime4G.toFixed(0)}ms`,
    dependencies: info.dependencyCount
  };
}

// Usage
const report = await customReport('react');
console.table([report]);

Output:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ package β”‚ version   β”‚ unpackedMB β”‚ tarballMB β”‚ download3G β”‚ download4G β”‚ dependencies β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    0    β”‚ 'react' β”‚ '19.2.4' β”‚   '0.17'   β”‚  '0.05'   β”‚   '447ms'  β”‚    '44ms'   β”‚      0       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Bulk Analysis

import { fetchPackageInfo } from 'pkgsize';
import * as fs from 'fs';

async function analyzeProject(packageJsonPath: string) {
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
  const deps = Object.keys(pkg.dependencies || {});
  
  const results = await Promise.all(
    deps.map(async name => {
      try {
        return await fetchPackageInfo(name);
      } catch (err) {
        return { name, error: err.message };
      }
    })
  );
  
  // Filter out errors
  const valid = results.filter(r => !r.error);
  
  // Calculate totals
  const totalSize = valid.reduce((sum, pkg) => sum + pkg.unpackedSize, 0);
  const totalDeps = valid.reduce((sum, pkg) => sum + pkg.dependencyCount, 0);
  
  console.log(`πŸ“Š Project Analysis`);
  console.log(`─────────────────────`);
  console.log(`Total packages: ${valid.length}`);
  console.log(`Total unpacked: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
  console.log(`Total dependencies: ${totalDeps}`);
  console.log(``);
  
  // Top 5 largest
  const sorted = valid.sort((a, b) => b.unpackedSize - a.unpackedSize);
  console.log(`πŸ”΄ Largest packages:`);
  sorted.slice(0, 5).forEach((pkg, i) => {
    const sizeMB = (pkg.unpackedSize / 1024 / 1024).toFixed(2);
    console.log(`${i + 1}. ${pkg.name}: ${sizeMB} MB`);
  });
}

// Usage
analyzeProject('./package.json');

CI Integration Helper

import { fetchPackageInfo } from 'pkgsize';

interface SizeLimits {
  [key: string]: number;  // Max size in bytes
}

async function enforceSize Limits(limits: SizeLimits): Promise<void> {
  const failures: string[] = [];
  
  for (const [packageName, maxSize] of Object.entries(limits)) {
    const info = await fetchPackageInfo(packageName);
    
    if (info.unpackedSize > maxSize) {
      const actual = (info.unpackedSize / 1024).toFixed(1);
      const limit = (maxSize / 1024).toFixed(1);
      failures.push(
        `${packageName}: ${actual} KB (limit: ${limit} KB)`
      );
    }
  }
  
  if (failures.length > 0) {
    console.error('❌ Size limit violations:');
    failures.forEach(f => console.error(`  - ${f}`));
    process.exit(1);
  }
  
  console.log('βœ… All packages within size limits');
}

// Usage
enforceSizeLimits({
  'lodash': 1024 * 1024,      // 1 MB max
  'moment': 2 * 1024 * 1024,  // 2 MB max
  'react': 500 * 1024         // 500 KB max
});

Integration with Build Tools

Webpack Plugin

// webpack-pkgsize-plugin.js
const { fetchPackageInfo } = require('pkgsize');

class PkgSizePlugin {
  apply(compiler) {
    compiler.hooks.beforeCompile.tapAsync('PkgSizePlugin', async (params, callback) => {
      console.log('πŸ” Checking dependency sizes...\n');
      
      const pkg = require('./package.json');
      const deps = Object.keys(pkg.dependencies || {});
      
      const large = [];
      
      for (const dep of deps) {
        const info = await fetchPackageInfo(dep);
        const sizeMB = info.unpackedSize / 1024 / 1024;
        
        if (sizeMB > 1) {
          large.push({ name: dep, size: sizeMB });
        }
      }
      
      if (large.length > 0) {
        console.warn('⚠️  Large dependencies detected:');
        large.forEach(({ name, size }) => {
          console.warn(`   - ${name}: ${size.toFixed(2)} MB`);
        });
        console.warn('   Consider code splitting or lazy loading.\n');
      }
      
      callback();
    });
  }
}

module.exports = PkgSizePlugin;
// webpack.config.js
const PkgSizePlugin = require('./webpack-pkgsize-plugin');

module.exports = {
  // ... other config
  plugins: [
    new PkgSizePlugin()
  ]
};

Vite Plugin

// vite-plugin-pkgsize.ts
import { Plugin } from 'vite';
import { fetchPackageInfo } from 'pkgsize';

export function pkgsizePlugin(): Plugin {
  return {
    name: 'vite-plugin-pkgsize',
    
    async buildStart() {
      const pkg = require('./package.json');
      const deps = Object.keys(pkg.dependencies || {});
      
      const results = await Promise.all(
        deps.map(name => fetchPackageInfo(name))
      );
      
      const total = results.reduce((sum, r) => sum + r.unpackedSize, 0);
      const totalMB = (total / 1024 / 1024).toFixed(2);
      
      console.log(`πŸ“¦ Total dependency size: ${totalMB} MB`);
      
      if (total > 10 * 1024 * 1024) {
        console.warn('⚠️  Dependencies exceed 10 MB. Consider optimization.');
      }
    }
  };
}
// vite.config.ts
import { defineConfig } from 'vite';
import { pkgsizePlugin } from './vite-plugin-pkgsize';

export default defineConfig({
  plugins: [pkgsizePlugin()]
});

Custom Reporters

Markdown Report Generator

import { fetchPackageInfo } from 'pkgsize';
import * as fs from 'fs';

async function generateMarkdownReport(packages: string[], outputPath: string) {
  const results = await Promise.all(
    packages.map(name => fetchPackageInfo(name))
  );
  
  let markdown = '# Package Size Report\n\n';
  markdown += `**Generated:** ${new Date().toISOString()}\n\n`;
  markdown += `| Package | Version | Unpacked | Tarball | Dependencies |\n`;
  markdown += `|---------|---------|----------|---------|-------------|\n`;
  
  results.forEach(pkg => {
    const unpacked = (pkg.unpackedSize / 1024).toFixed(1);
    const tarball = (pkg.tarballSize / 1024).toFixed(1);
    markdown += `| ${pkg.name} | ${pkg.version} | ${unpacked} KB | ${tarball} KB | ${pkg.dependencyCount} |\n`;
  });
  
  markdown += '\n## Summary\n\n';
  const total = results.reduce((sum, p) => sum + p.unpackedSize, 0);
  markdown += `**Total Size:** ${(total / 1024 / 1024).toFixed(2)} MB\n`;
  
  fs.writeFileSync(outputPath, markdown);
  console.log(`βœ… Report saved to ${outputPath}`);
}

// Usage
const pkg = require('./package.json');
const deps = Object.keys(pkg.dependencies || {});
generateMarkdownReport(deps, 'PACKAGE-SIZES.md');

HTML Dashboard

import { fetchPackageInfo } from 'pkgsize';
import * as fs from 'fs';

async function generateHTMLDashboard(packages: string[], outputPath: string) {
  const results = await Promise.all(
    packages.map(name => fetchPackageInfo(name))
  );
  
  const html = `
<!DOCTYPE html>
<html>
<head>
  <title>Package Size Dashboard</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 20px; }
    table { border-collapse: collapse; width: 100%; }
    th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
    th { background: #4CAF50; color: white; }
    .large { background: #ffcccc; }
    .medium { background: #ffffcc; }
    .small { background: #ccffcc; }
  </style>
</head>
<body>
  <h1>πŸ“¦ Package Size Dashboard</h1>
  <p>Generated: ${new Date().toLocaleString()}</p>
  
  <table>
    <tr>
      <th>Package</th>
      <th>Version</th>
      <th>Unpacked</th>
      <th>Tarball</th>
      <th>Dependencies</th>
    </tr>
    ${results.map(pkg => {
      const sizeMB = pkg.unpackedSize / 1024 / 1024;
      const rowClass = sizeMB > 1 ? 'large' : sizeMB > 0.5 ? 'medium' : 'small';
      return `
        <tr class="${rowClass}">
          <td>${pkg.name}</td>
          <td>${pkg.version}</td>
          <td>${(pkg.unpackedSize / 1024).toFixed(1)} KB</td>
          <td>${(pkg.tarballSize / 1024).toFixed(1)} KB</td>
          <td>${pkg.dependencyCount}</td>
        </tr>
      `;
    }).join('')}
  </table>
  
  <h2>Summary</h2>
  <p><strong>Total Size:</strong> ${(results.reduce((sum, p) => sum + p.unpackedSize, 0) / 1024 / 1024).toFixed(2)} MB</p>
</body>
</html>
  `;
  
  fs.writeFileSync(outputPath, html);
  console.log(`βœ… Dashboard saved to ${outputPath}`);
}

// Usage
const pkg = require('./package.json');
const deps = Object.keys(pkg.dependencies || {});
generateHTMLDashboard(deps, 'size-dashboard.html');

❓ FAQ

Q: Does this show the actual bundle size?

No. pkgsize shows:

  • βœ… Unpacked size - Raw files in node_modules
  • βœ… Tarball size - npm download size

Not shown:

  • ❌ Bundle size - After webpack/vite/rollup minification
  • ❌ Gzipped size - What users actually download
  • ❌ Tree-shaken size - After unused code removal

To check bundle size, use:

# Webpack Bundle Analyzer
npm install --save-dev webpack-bundle-analyzer

# Vite
npm run build
# Check dist/ folder size

# Or use bundlephobia.com
open https://bundlephobia.com/package/lodash

Q: Why is tarball smaller than unpacked?

Tarball is compressed (gzip). Example:

Unpacked: 1.4 MB (raw files)
Tarball:  547 KB (compressed for npm download)

When you npm install, npm downloads the tarball, then unpacks it to node_modules.

Q: Can I check multiple versions?

Not yet. Currently only checks latest version.

Workaround:

# Check specific version manually
npm view react@18.2.0 dist.unpackedSize
npm view react@19.0.0 dist.unpackedSize

Coming soon:

# Planned feature
pkgsize react@18.2.0 react@19.0.0

Q: Does this work for private packages?

No. Only works with public npm registry packages.

Alternative for private packages:

# After installing
npm install @mycompany/private-pkg

# Check size
du -sh node_modules/@mycompany/private-pkg

Q: Why are dependencies not included in size?

By design. pkgsize shows direct package size only.

Example:

express (220 KB) + 31 dependencies (~5 MB total)

pkgsize reports 220 KB, not 5 MB.

To see total:

npm install express
du -sh node_modules/

Q: Can I use this offline?

Not yet. Requires internet to query npm registry.

Coming soon:

# Cache results for offline use
pkgsize lodash --cache

Q: How accurate is this?

Very accurate for latest versions. Data comes directly from npm registry metadata.

Note: Sizes may vary slightly after installation due to:

  • OS-specific files
  • Optional dependencies
  • Post-install scripts

Q: Can I compare across registries?

Not currently. Uses npm registry only.

Workaround:

# Check yarn registry
curl https://registry.yarnpkg.com/react | jq .dist.unpackedSize

# Check custom registry
curl https://registry.company.com/my-pkg | jq .dist.unpackedSize

Q: Why is this better than bundlephobia?

Different use cases:

Feature pkgsize bundlephobia.com
CLI tool βœ… Yes ❌ No (web only)
Bundle size ❌ No βœ… Yes
Gzip size ❌ No βœ… Yes
Tree-shaking ❌ No βœ… Yes
Offline mode πŸ”œ Coming ❌ No
Compare packages βœ… Yes ❌ No
CI integration βœ… Easy ⚠️ API only

Use pkgsize for:

  • Quick CLI checks
  • CI/CD integration
  • Comparing alternatives
  • Pre-install decisions

Use bundlephobia for:

  • Actual bundle impact
  • Frontend optimization
  • Tree-shaking analysis

Q: How do I check peer dependencies?

pkgsize doesn't resolve peer deps. You need to check manually:

# Check peer dependencies
npm info react peerDependencies

# Then check each one
pkgsize react-dom

πŸ”„ Comparison with Alternatives

Tool Speed Accuracy Features Dependencies
pkgsize ⚑️ Fast βœ… Registry CLI, JSON, Compare 0
npm-view 🐒 Slow βœ… Registry Basic info npm
bundlephobia 🐒 Very Slow βœ…βœ… Build Bundle size, Gzip Web
package-size ⚑️ Fast ⚠️ Estimate CLI Many
cost-of-modules 🐒 Medium βœ…βœ… Installed Deep analysis Many

Why pkgsize?

  • βœ… Zero dependencies - Won't bloat your project
  • βœ… Registry-based - No installation required
  • βœ… Compare mode - Side-by-side comparison
  • βœ… JSON output - Easy CI integration
  • βœ… Fast - Direct API calls, no build step

πŸ›£οΈ Roadmap

  • Version comparison - pkgsize react@18 react@19
  • Offline cache - Save results for offline use
  • Dependency tree size - Include transitive deps
  • Historic tracking - Size changes over time
  • Badge generation - Size badges for README
  • GitHub Action - Automated PR size checks
  • VS Code extension - Check size on hover

πŸ™ Acknowledgments

Inspired by:


Made with ❀️ by muin-company
Because smaller is better.

About

Check npm package sizes before you install. Compare alternatives, stay lean

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors