Analyzes package.json and project structure for bundle optimization opportunities,
heavy dependencies, and best practice recommendations.
python bundle_analyzer.py <project_dir>
python bundle_analyzer.py . --json
python bundle_analyzer.py /path/to/project --verbose
from typing import Dict, List, Optional, Any, Tuple
# Known heavy packages and their lighter alternatives
"alternative": "date-fns (12KB) or dayjs (2KB)",
"reason": "Large locale files bundled by default"
"alternative": "lodash-es with tree-shaking or individual imports (lodash/get)",
"reason": "Full library often imported when only few functions needed"
"alternative": "Native DOM APIs or React/Vue patterns",
"reason": "Rarely needed in modern frameworks"
"alternative": "Native fetch API (0KB) or ky (3KB)",
"reason": "Fetch API covers most use cases"
"alternative": "Native ES6+ methods or lodash-es",
"reason": "Most utilities now in standard JavaScript"
"alternative": "recharts (bundled with React) or lightweight-charts",
"reason": "Consider if you need all chart types"
"alternative": "None - use dynamic import for 3D features",
"reason": "Very large, should be lazy-loaded"
"alternative": "Import specific modules (firebase/auth, firebase/firestore)",
"reason": "Modular imports significantly reduce size"
"alternative": "shadcn/ui (copy-paste components) or Tailwind",
"reason": "Heavy runtime, consider headless alternatives"
"alternative": "shadcn/ui or Radix UI + Tailwind",
"reason": "Heavy runtime, consider headless alternatives"
"alternative": "shadcn/ui or Radix UI + Tailwind",
"reason": "Heavy runtime, consider headless alternatives"
# Recommended optimizations by package
PACKAGE_OPTIMIZATIONS = {
"react-icons": "Import individual icons: import { FaHome } from 'react-icons/fa'",
"date-fns": "Use tree-shaking: import { format } from 'date-fns'",
"@heroicons/react": "Already tree-shakeable, good choice",
"lucide-react": "Already tree-shakeable, add to optimizePackageImports in next.config.js",
"framer-motion": "Use dynamic import for non-critical animations",
"recharts": "Consider lazy loading for dashboard charts",
# Development dependencies that should not be in dependencies
"typescript", "@types/", "eslint", "prettier", "jest", "vitest",
"@testing-library", "cypress", "playwright", "storybook", "@storybook",
"webpack", "vite", "rollup", "esbuild", "tailwindcss", "postcss",
"autoprefixer", "sass", "less", "husky", "lint-staged"
def load_package_json(project_dir: Path) -> Optional[Dict]:
"""Load and parse package.json."""
package_path = project_dir / "package.json"
if not package_path.exists():
with open(package_path) as f:
except json.JSONDecodeError:
def analyze_dependencies(package_json: Dict) -> Dict:
"""Analyze dependencies for issues."""
deps = package_json.get("dependencies", {})
dev_deps = package_json.get("devDependencies", {})
# Check for heavy packages
for pkg, info in HEAVY_PACKAGES.items():
"type": "heavy_dependency",
"alternative": info["alternative"],
# Check for dev dependencies in production
for dev_pattern in DEV_ONLY_PACKAGES:
"type": "dev_in_production",
"message": f"{pkg} should be in devDependencies, not dependencies"
# Check for optimization opportunities
for opt_pkg, opt_tip in PACKAGE_OPTIMIZATIONS.items():
# Check for outdated React patterns
if "prop-types" in deps and ("typescript" in dev_deps or "@types/react" in dev_deps):
"message": "prop-types is redundant when using TypeScript"
# Check for multiple state management libraries
state_libs = ["redux", "@reduxjs/toolkit", "mobx", "zustand", "jotai", "recoil", "valtio"]
found_state_libs = [lib for lib in state_libs if lib in deps]
if len(found_state_libs) > 1:
"packages": found_state_libs,
"type": "multiple_state_libs",
"message": f"Multiple state management libraries found: {', '.join(found_state_libs)}"
"total_dependencies": len(deps),
"total_dev_dependencies": len(dev_deps),
"optimizations": optimizations
def check_nextjs_config(project_dir: Path) -> Dict:
"""Check Next.js configuration for optimizations."""
project_dir / "next.config.js",
project_dir / "next.config.mjs",
project_dir / "next.config.ts"
for config_path in config_paths:
content = config_path.read_text()
# Check for image optimization
if "images" not in content:
suggestions.append("Configure images.remotePatterns for optimized image loading")
# Check for package optimization
if "optimizePackageImports" not in content:
suggestions.append("Add experimental.optimizePackageImports for lucide-react, @heroicons/react")
# Check for transpilePackages
if "transpilePackages" not in content and "swc" not in content:
suggestions.append("Consider transpilePackages for monorepo packages")
"path": str(config_path),
"suggestions": suggestions
"suggestions": ["Create next.config.js with image and bundle optimizations"]
def analyze_imports(project_dir: Path) -> Dict:
"""Analyze import patterns in source files."""
src_dirs = [project_dir / "src", project_dir / "app", project_dir / "pages"]
(r"import\s+\*\s+as\s+\w+\s+from\s+['\"]lodash['\"]", "Avoid import * from lodash, use individual imports"),
(r"import\s+moment\s+from\s+['\"]moment['\"]", "Consider replacing moment with date-fns or dayjs"),
(r"import\s+\{\s*\w+(?:,\s*\w+){5,}\s*\}\s+from\s+['\"]react-icons", "Import icons from specific icon sets (react-icons/fa)"),
for ext in ["*.ts", "*.tsx", "*.js", "*.jsx"]:
for file_path in src_dir.glob(f"**/{ext}"):
if "node_modules" in str(file_path):
content = file_path.read_text()
for pattern, message in patterns_to_check:
if re.search(pattern, content):
"file": str(file_path.relative_to(project_dir)),
"files_checked": files_checked,
def calculate_score(analysis: Dict) -> Tuple[int, str]:
"""Calculate bundle health score."""
# Deduct for heavy dependencies
score -= len(analysis["dependencies"]["issues"]) * 10
# Deduct for dev deps in production
score -= len([w for w in analysis["dependencies"]["warnings"]
if w.get("type") == "dev_in_production"]) * 5
# Deduct for import issues
score -= len(analysis.get("imports", {}).get("issues", [])) * 3
# Deduct for missing Next.js optimizations
if not analysis.get("nextjs", {}).get("found", True):
score = max(0, min(100, score))
def print_report(analysis: Dict) -> None:
"""Print human-readable report."""
score, grade = calculate_score(analysis)
print("FRONTEND BUNDLE ANALYSIS REPORT")
print(f"\nBundle Health Score: {score}/100 ({grade})")
deps = analysis["dependencies"]
print(f"\nDependencies: {deps['total_dependencies']} production, {deps['total_dev_dependencies']} dev")
print("\n--- HEAVY DEPENDENCIES ---")
for issue in deps["issues"]:
print(f"\n {issue['package']} ({issue['size']})")
print(f" Reason: {issue['reason']}")
print(f" Alternative: {issue['alternative']}")
print("\n--- WARNINGS ---")
for warning in deps["warnings"]:
print(f" - {warning['package']}: {warning['message']}")
print(f" - {warning['message']}")
if deps["optimizations"]:
print("\n--- OPTIMIZATION TIPS ---")
for opt in deps["optimizations"]:
print(f" - {opt['package']}: {opt['tip']}")
nextjs = analysis["nextjs"]
if nextjs.get("suggestions"):
print("\n--- NEXT.JS CONFIG ---")
for suggestion in nextjs["suggestions"]:
print(f" - {suggestion}")
if analysis.get("imports", {}).get("issues"):
print("\n--- IMPORT ISSUES ---")
for issue in analysis["imports"]["issues"][:10]: # Limit to 10
print(f" - {issue['file']}: {issue['issue']}")
print("\n--- RECOMMENDATIONS ---")
print(" Bundle is well-optimized!")
print(" 1. Replace heavy dependencies with lighter alternatives")
print(" 2. Move dev-only packages to devDependencies")
if deps["optimizations"]:
print(" 3. Apply import optimizations for tree-shaking")
parser = argparse.ArgumentParser(
description="Analyze frontend project for bundle optimization opportunities"
help="Project directory to analyze (default: current directory)"
help="Output in JSON format"
help="Include detailed import analysis"
args = parser.parse_args()
project_dir = Path(args.project_dir).resolve()
if not project_dir.exists():
print(f"Error: Directory not found: {project_dir}", file=sys.stderr)
package_json = load_package_json(project_dir)
print("Error: No valid package.json found", file=sys.stderr)
"project": str(project_dir),
"dependencies": analyze_dependencies(package_json),
"nextjs": check_nextjs_config(project_dir)
analysis["imports"] = analyze_imports(project_dir)
analysis["score"], analysis["grade"] = calculate_score(analysis)
print(json.dumps(analysis, indent=2))
if __name__ == "__main__":