JavaScript Bundle Analysis and Optimization: A Developer's Guide
A practical Fusebox guide to javascript bundle analysis and optimization.
JavaScript Bundle Analysis and Optimization: A Developer's Guide
JavaScript bundles are the cholesterol of web performance—they accumulate silently until they block everything. The average web page loads 450KB of JavaScript, but studies show users only interact with 30% of it. This guide provides tools to analyze bundle composition, identify bloat, and implement code splitting strategies that actually work.
Why Bundle Size Matters
In 2022, a major e-commerce platform discovered their 2MB JavaScript bundle was causing a 40% cart abandonment rate on mobile. After implementing aggressive code splitting and tree shaking, they reduced the initial bundle to 150KB and increased mobile conversions by 35%. Every kilobyte of JavaScript costs you users.
Bundle Analyzer Tool
Comprehensive bundle analysis in the browser:
// JavaScript Bundle Analyzer
class BundleAnalyzer {
constructor() {
this.scripts = [];
this.modules = new Map();
this.dependencies = new Map();
this.coverage = null;
}
// Analyze all loaded scripts
async analyzePageBundles() {
const analysis = {
scripts: await this.analyzeScripts(),
coverage: await this.analyzeCoverage(),
duplicates: this.findDuplicates(),
optimization: this.calculateOptimizationPotential(),
recommendations: []
};
analysis.recommendations = this.generateRecommendations(analysis);
return analysis;
}
// Analyze loaded scripts
async analyzeScripts() {
const scripts = Array.from(document.querySelectorAll('script'));
const scriptData = [];
for (const script of scripts) {
if (script.src) {
try {
const data = await this.analyzeScript(script.src);
scriptData.push(data);
} catch (error) {
console.error(`Failed to analyze ${script.src}:`, error);
}
} else if (script.textContent) {
// Inline scripts
scriptData.push({
type: 'inline',
content: script.textContent,
size: new Blob([script.textContent]).size,
location: 'inline',
async: script.async,
defer: script.defer
});
}
}
// Also check for dynamically loaded scripts
const resources = performance.getEntriesByType('resource');
for (const resource of resources) {
if (resource.initiatorType === 'script' ||
resource.name.endsWith('.js')) {
const existing = scriptData.find(s => s.url === resource.name);
if (!existing) {
scriptData.push({
url: resource.name,
size: resource.transferSize || resource.encodedBodySize,
loadTime: resource.duration,
type: 'dynamic',
startTime: resource.startTime
});
}
}
}
this.scripts = scriptData;
return scriptData;
}
// Analyze individual script
async analyzeScript(url) {
const response = await fetch(url);
const text = await response.text();
const analysis = {
url,
size: new Blob([text]).size,
gzipSize: response.headers.get('content-length') || 0,
type: this.detectBundleType(text),
modules: this.extractModules(text),
libraries: this.detectLibraries(text),
async: document.querySelector(`script[src="${url}"]`)?.async || false,
defer: document.querySelector(`script[src="${url}"]`)?.defer || false,
loadTime: this.getScriptLoadTime(url),
parseTime: this.estimateParseTime(text.length),
isMinified: this.isMinified(text),
sourceMaps: text.includes('//# sourceMappingURL=')
};
// Detect potential issues
analysis.issues = this.detectIssues(text, analysis);
return analysis;
}
// Detect bundle type
detectBundleType(code) {
if (code.includes('webpackJsonp') || code.includes('__webpack_require__')) {
return 'webpack';
} else if (code.includes('System.register') || code.includes('System.import')) {
return 'systemjs';
} else if (code.includes('define.amd') || code.includes('requirejs')) {
return 'amd';
} else if (code.includes('parcelRequire')) {
return 'parcel';
} else if (code.includes('__vite__')) {
return 'vite';
} else if (code.includes('_rollupPluginBabelHelpers')) {
return 'rollup';
}
return 'unknown';
}
// Extract modules from bundle
extractModules(code) {
const modules = [];
// Webpack module detection
const webpackModules = code.matchAll(/\/\*\*\*\/ "([^"]+)":/g);
for (const match of webpackModules) {
modules.push({
id: match[1],
type: 'webpack'
});
}
// ES6 imports
const imports = code.matchAll(/import\s+.*?\s+from\s+["']([^"']+)["']/g);
for (const match of imports) {
modules.push({
id: match[1],
type: 'es6'
});
}
// CommonJS requires
const requires = code.matchAll(/require\(["']([^"']+)["']\)/g);
for (const match of requires) {
modules.push({
id: match[1],
type: 'commonjs'
});
}
return modules;
}
// Detect common libraries
detectLibraries(code) {
const libraries = [];
const signatures = {
'react': /\bReact\.createElement\b|\buseState\b|\buseEffect\b/,
'react-dom': /\bReactDOM\.render\b|\bReactDOM\.createRoot\b/,
'vue': /\bnew Vue\b|\bVue\.component\b|\bVue\.use\b/,
'angular': /\bangular\.module\b|\b@angular\/core\b/,
'jquery': /\bjQuery\b|\b\$\(.+\)\./,
'lodash': /\b_\.\w+\b|\blodash\b/,
'moment': /\bmoment\(\b|\bmoment\.locale\b/,
'axios': /\baxios\.get\b|\baxios\.post\b/,
'd3': /\bd3\.select\b|\bd3\.scale\b/,
'three': /\bTHREE\.\w+\b|\bnew THREE\./,
'chart.js': /\bnew Chart\b|\bChart\.defaults\b/,
'rxjs': /\bObservable\b|\bfrom\b|\bof\b.*pipe\b/,
'redux': /\bcreateStore\b|\bcombineReducers\b/,
'express': /\bexpress\(\)\b|\bapp\.get\b/,
'webpack': /\b__webpack_require__\b/,
'babel': /\b_interopRequireDefault\b/,
'typescript': /\b__extends\b|\b__awaiter\b/,
'polyfill': /\bregeneratorRuntime\b|\bPromise\.prototype\.finally\b/
};
for (const [library, pattern] of Object.entries(signatures)) {
if (pattern.test(code)) {
// Try to find version
const versionPattern = new RegExp(`${library}@([\\d.]+)`, 'i');
const versionMatch = code.match(versionPattern);
libraries.push({
name: library,
version: versionMatch ? versionMatch[1] : 'unknown',
confidence: pattern.exec(code) ? 'high' : 'medium'
});
}
}
return libraries;
}
// Get script load time
getScriptLoadTime(url) {
const entry = performance.getEntriesByName(url)[0];
return entry ? entry.duration : 0;
}
// Estimate parse time
estimateParseTime(size) {
// Rough estimate: 1MB takes ~25ms to parse on modern desktop
// Mobile is ~4x slower
const baseParseTime = size / 1024 / 1024 * 25;
const isMobile = /mobile/i.test(navigator.userAgent);
return isMobile ? baseParseTime * 4 : baseParseTime;
}
// Check if code is minified
isMinified(code) {
const lines = code.split('\n');
const avgLineLength = code.length / lines.length;
const hasShortVars = /\b[a-z]\b\s*=/g.test(code);
const hasNoComments = !(/\/\*[\s\S]*?\*\/|\/\/.*/g.test(code));
return avgLineLength > 200 && hasShortVars && hasNoComments;
}
// Detect issues in bundle
detectIssues(code, analysis) {
const issues = [];
// Check for console logs in production
if (analysis.isMinified && code.includes('console.log')) {
issues.push({
type: 'console-logs',
severity: 'low',
message: 'Console logs found in production bundle'
});
}
// Check for source maps in production
if (analysis.isMinified && analysis.sourceMaps) {
issues.push({
type: 'source-maps',
severity: 'medium',
message: 'Source maps exposed in production'
});
}
// Check for development React
if (code.includes('development.js') ||
code.includes('This looks like a development build of React')) {
issues.push({
type: 'dev-build',
severity: 'high',
message: 'Development build detected in production'
});
}
// Check for duplicate code patterns
const codePatterns = code.match(/.{100,}/g) || [];
const duplicates = codePatterns.filter((pattern, index) =>
codePatterns.indexOf(pattern) !== index
);
if (duplicates.length > 10) {
issues.push({
type: 'duplicates',
severity: 'medium',
message: `${duplicates.length} duplicate code patterns found`
});
}
return issues;
}
// Analyze code coverage
async analyzeCoverage() {
if (!window.__coverage__ && typeof window.Coverage === 'undefined') {
// Try to use Chrome DevTools Protocol if available
try {
const coverage = await this.collectCoverage();
return coverage;
} catch (e) {
return { supported: false, message: 'Coverage API not available' };
}
}
return { supported: true, data: window.__coverage__ };
}
// Collect coverage data (simplified)
async collectCoverage() {
// This would require Chrome DevTools Protocol
// Simplified version for demonstration
const coverage = {
js: [],
css: []
};
// Estimate based on function calls
this.scripts.forEach(script => {
if (script.content || script.url) {
coverage.js.push({
url: script.url || 'inline',
usedBytes: 0,
totalBytes: script.size,
unusedPercentage: 100 // Would need real data
});
}
});
return coverage;
}
// Find duplicate dependencies
findDuplicates() {
const modules = new Map();
const duplicates = [];
this.scripts.forEach(script => {
if (script.modules) {
script.modules.forEach(module => {
if (!modules.has(module.id)) {
modules.set(module.id, []);
}
modules.get(module.id).push(script.url || 'inline');
});
}
});
modules.forEach((locations, moduleId) => {
if (locations.length > 1) {
duplicates.push({
module: moduleId,
locations,
count: locations.length
});
}
});
return duplicates.sort((a, b) => b.count - a.count);
}
// Calculate optimization potential
calculateOptimizationPotential() {
let savingsBytes = 0;
const opportunities = [];
// Check for unminified code
const unminified = this.scripts.filter(s => !s.isMinified);
if (unminified.length > 0) {
const savings = unminified.reduce((sum, s) => sum + (s.size * 0.7), 0);
savingsBytes += savings;
opportunities.push({
type: 'minification',
savings,
scripts: unminified.map(s => s.url || 'inline')
});
}
// Check for development builds
const devBuilds = this.scripts.filter(s =>
s.issues?.some(i => i.type === 'dev-build')
);
if (devBuilds.length > 0) {
const savings = devBuilds.reduce((sum, s) => sum + (s.size * 0.3), 0);
savingsBytes += savings;
opportunities.push({
type: 'production-builds',
savings,
scripts: devBuilds.map(s => s.url)
});
}
// Check for duplicate modules
const duplicateSavings = this.findDuplicates().reduce((sum, dup) =>
sum + ((dup.count - 1) * 50000), 0 // Estimate 50KB per duplicate
);
if (duplicateSavings > 0) {
savingsBytes += duplicateSavings;
opportunities.push({
type: 'deduplication',
savings: duplicateSavings
});
}
return {
totalSavings: savingsBytes,
percentage: (savingsBytes / this.getTotalSize()) * 100,
opportunities
};
}
// Get total bundle size
getTotalSize() {
return this.scripts.reduce((sum, script) => sum + (script.size || 0), 0);
}
// Generate recommendations
generateRecommendations(analysis) {
const recommendations = [];
const totalSize = this.getTotalSize();
// Size recommendations
if (totalSize > 500000) { // 500KB
recommendations.push({
priority: 'high',
type: 'bundle-size',
message: `Total JavaScript size is ${(totalSize / 1024 / 1024).toFixed(2)}MB`,
impact: 'Slow initial load, especially on mobile',
solutions: [
'Implement code splitting',
'Lazy load non-critical features',
'Remove unused dependencies'
]
});
}
// Check for missing async/defer
const blockingScripts = analysis.scripts.filter(s =>
s.url && !s.async && !s.defer && !s.type.includes('module')
);
if (blockingScripts.length > 0) {
recommendations.push({
priority: 'high',
type: 'render-blocking',
message: `${blockingScripts.length} render-blocking scripts detected`,
scripts: blockingScripts.map(s => s.url),
solution: 'Add async or defer attributes'
});
}
// Duplicate dependencies
if (analysis.duplicates.length > 0) {
recommendations.push({
priority: 'medium',
type: 'duplicates',
message: `${analysis.duplicates.length} duplicate modules detected`,
impact: `Could save ~${(analysis.optimization.opportunities
.find(o => o.type === 'deduplication')?.savings / 1024).toFixed(0)}KB`,
solution: 'Use webpack deduplication or module federation'
});
}
// Library-specific recommendations
const libraries = analysis.scripts.flatMap(s => s.libraries || []);
const hasReact = libraries.some(l => l.name === 'react');
const hasMoment = libraries.some(l => l.name === 'moment');
if (hasMoment) {
recommendations.push({
priority: 'medium',
type: 'large-library',
message: 'Moment.js detected (67KB gzipped)',
solution: 'Consider date-fns or day.js (2KB)'
});
}
if (hasReact && analysis.scripts.some(s =>
s.issues?.some(i => i.type === 'dev-build'))) {
recommendations.push({
priority: 'critical',
type: 'dev-react',
message: 'React development build in production',
impact: '4x larger bundle size',
solution: 'Set NODE_ENV=production'
});
}
return recommendations;
}
}
// Run bundle analysis
const analyzer = new BundleAnalyzer();
analyzer.analyzePageBundles().then(analysis => {
console.log('Bundle Analysis:', analysis);
});
Code Splitting Detector
Detect and analyze code splitting implementation:
// Code Splitting Detector
class CodeSplittingDetector {
constructor() {
this.chunks = new Map();
this.routes = new Map();
this.lazyModules = [];
}
// Detect code splitting patterns
detectCodeSplitting() {
const detection = {
hasCodeSplitting: false,
strategy: 'none',
chunks: [],
lazyComponents: [],
dynamicImports: [],
routeBasedSplitting: false,
recommendations: []
};
// Check for webpack chunks
if (window.webpackJsonp || window.webpackChunk) {
detection.hasCodeSplitting = true;
detection.strategy = 'webpack';
detection.chunks = this.analyzeWebpackChunks();
}
// Check for dynamic imports
detection.dynamicImports = this.detectDynamicImports();
if (detection.dynamicImports.length > 0) {
detection.hasCodeSplitting = true;
if (detection.strategy === 'none') {
detection.strategy = 'dynamic-import';
}
}
// Check for React lazy
detection.lazyComponents = this.detectReactLazy();
// Check for route-based splitting
detection.routeBasedSplitting = this.detectRouteBasedSplitting();
// Analyze effectiveness
detection.effectiveness = this.analyzeEffectiveness(detection);
// Generate recommendations
detection.recommendations = this.generateRecommendations(detection);
return detection;
}
// Analyze webpack chunks
analyzeWebpackChunks() {
const chunks = [];
const jsonpArray = window.webpackJsonp || window.webpackChunk || [];
if (Array.isArray(jsonpArray)) {
jsonpArray.forEach((chunk, index) => {
if (Array.isArray(chunk)) {
const [chunkIds, modules] = chunk;
chunks.push({
id: chunkIds,
moduleCount: Object.keys(modules || {}).length,
index
});
}
});
}
// Check for chunk loading functions
const chunkLoadingGlobal = window.__webpack_require__;
if (chunkLoadingGlobal && chunkLoadingGlobal.e) {
chunks.push({
type: 'lazy-chunks',
hasChunkLoader: true
});
}
return chunks;
}
// Detect dynamic imports
detectDynamicImports() {
const dynamicImports = [];
const scripts = Array.from(document.querySelectorAll('script'));
scripts.forEach(script => {
const content = script.textContent || '';
// Look for import() calls
const importMatches = content.matchAll(/import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g);
for (const match of importMatches) {
dynamicImports.push({
module: match[1],
type: 'dynamic-import'
});
}
// Look for webpack's require.ensure
const requireMatches = content.matchAll(/require\.ensure\s*\(\s*\[([^\]]*)\]/g);
for (const match of requireMatches) {
dynamicImports.push({
modules: match[1],
type: 'require-ensure'
});
}
// Look for webpack magic comments
const magicComments = content.matchAll(/\/\*\s*webpackChunkName:\s*"([^"]+)"\s*\*\//g);
for (const match of magicComments) {
dynamicImports.push({
chunkName: match[1],
type: 'webpack-magic-comment'
});
}
});
return dynamicImports;
}
// Detect React.lazy usage
detectReactLazy() {
const lazyComponents = [];
const scripts = document.querySelectorAll('script');
scripts.forEach(script => {
const content = script.textContent || '';
// React.lazy patterns
const lazyMatches = content.matchAll(/React\.lazy\s*\(\s*\(\s*\)\s*=>\s*import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g);
for (const match of lazyMatches) {
lazyComponents.push({
component: match[1],
type: 'react-lazy'
});
}
// Next.js dynamic imports
const nextDynamic = content.matchAll(/dynamic\s*\(\s*\(\s*\)\s*=>\s*import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g);
for (const match of nextDynamic) {
lazyComponents.push({
component: match[1],
type: 'nextjs-dynamic'
});
}
});
return lazyComponents;
}
// Detect route-based code splitting
detectRouteBasedSplitting() {
// Check for common routing patterns
const patterns = {
reactRouter: /Route.*component=\{.*lazy|import\(/,
vueRouter: /component:\s*\(\)\s*=>\s*import/,
angularRouter: /loadChildren:\s*\(\)\s*=>\s*import/
};
let hasRouteBasedSplitting = false;
const scripts = document.querySelectorAll('script');
scripts.forEach(script => {
const content = script.textContent || '';
for (const [framework, pattern] of Object.entries(patterns)) {
if (pattern.test(content)) {
hasRouteBasedSplitting = true;
this.routes.set(framework, true);
}
}
});
return hasRouteBasedSplitting;
}
// Analyze code splitting effectiveness
analyzeEffectiveness(detection) {
const effectiveness = {
score: 0,
issues: [],
metrics: {}
};
// Check if initial bundle is still too large
const initialBundle = performance.getEntriesByType('resource')
.filter(r => r.initiatorType === 'script')
.sort((a, b) => a.startTime - b.startTime)[0];
if (initialBundle && initialBundle.transferSize > 200000) { // 200KB
effectiveness.issues.push({
type: 'large-initial-bundle',
size: initialBundle.transferSize,
message: 'Initial bundle still too large despite code splitting'
});
}
// Check chunk loading performance
const chunkRequests = performance.getEntriesByType('resource')
.filter(r => r.name.includes('chunk') || r.name.includes('.lazy.'));
effectiveness.metrics.chunkCount = chunkRequests.length;
effectiveness.metrics.avgChunkSize = chunkRequests.length > 0 ?
chunkRequests.reduce((sum, r) => sum + r.transferSize, 0) / chunkRequests.length : 0;
// Calculate score
if (detection.hasCodeSplitting) {
effectiveness.score += 30;
}
if (detection.routeBasedSplitting) {
effectiveness.score += 30;
}
if (detection.lazyComponents.length > 0) {
effectiveness.score += 20;
}
if (effectiveness.metrics.avgChunkSize < 50000) { // 50KB average
effectiveness.score += 20;
}
return effectiveness;
}
// Generate recommendations
generateRecommendations(detection) {
const recommendations = [];
if (!detection.hasCodeSplitting) {
recommendations.push({
priority: 'critical',
type: 'no-code-splitting',
message: 'No code splitting detected',
impact: 'Users download all code upfront',
solution: 'Implement dynamic imports for routes and heavy components',
example: `// Route-based splitting
const HomePage = lazy(() => import('./pages/HomePage'));
const ProfilePage = lazy(() => import('./pages/ProfilePage'));
// Component-based splitting
const HeavyChart = lazy(() => import('./components/HeavyChart'));
// Conditional splitting
if (user.isAdmin) {
const AdminPanel = await import('./AdminPanel');
}`
});
}
if (detection.hasCodeSplitting && !detection.routeBasedSplitting) {
recommendations.push({
priority: 'high',
type: 'no-route-splitting',
message: 'Code splitting not applied to routes',
impact: 'All routes loaded upfront',
solution: 'Split code by route for better initial load'
});
}
if (detection.effectiveness.metrics.avgChunkSize > 100000) {
recommendations.push({
priority: 'medium',
type: 'large-chunks',
message: `Average chunk size is ${(detection.effectiveness.metrics.avgChunkSize / 1024).toFixed(0)}KB`,
impact: 'Slow lazy loading',
solution: 'Split chunks further or use granular imports'
});
}
if (detection.dynamicImports.length === 0 && detection.hasCodeSplitting) {
recommendations.push({
priority: 'low',
type: 'old-splitting-syntax',
message: 'Using older code splitting syntax',
solution: 'Migrate to dynamic import() for better browser support'
});
}
return recommendations;
}
}
// Detect code splitting
const splittingDetector = new CodeSplittingDetector();
console.log('Code Splitting Analysis:', splittingDetector.detectCodeSplitting());
Bundle Optimization Generator
Generate optimization strategies:
// Bundle Optimization Generator
class BundleOptimizer {
constructor(bundleAnalysis) {
this.analysis = bundleAnalysis;
this.optimizations = [];
}
// Generate comprehensive optimization plan
generateOptimizationPlan() {
const plan = {
immediate: [],
shortTerm: [],
longTerm: [],
config: this.generateConfigs(),
estimatedSavings: 0
};
// Immediate optimizations (can be done now)
plan.immediate = this.getImmediateOptimizations();
// Short-term optimizations (1-2 sprints)
plan.shortTerm = this.getShortTermOptimizations();
// Long-term optimizations (major refactoring)
plan.longTerm = this.getLongTermOptimizations();
// Calculate total savings
plan.estimatedSavings = this.calculateTotalSavings([
...plan.immediate,
...plan.shortTerm,
...plan.longTerm
]);
return plan;
}
// Get immediate optimizations
getImmediateOptimizations() {
const optimizations = [];
// Production builds
const devBuilds = this.analysis.scripts.filter(s =>
s.issues?.some(i => i.type === 'dev-build')
);
if (devBuilds.length > 0) {
optimizations.push({
id: 'production-builds',
title: 'Switch to Production Builds',
effort: 'low',
impact: 'high',
savings: devBuilds.reduce((sum, s) => sum + s.size * 0.7, 0),
implementation: `// package.json
"scripts": {
"build": "NODE_ENV=production webpack --mode production"
}
// webpack.config.js
module.exports = {
mode: process.env.NODE_ENV || 'production',
optimization: {
minimize: true,
nodeEnv: 'production'
}
}`
});
}
// Tree shaking
const hasUnusedExports = this.analysis.coverage?.js.some(c =>
c.unusedPercentage > 30
);
if (hasUnusedExports) {
optimizations.push({
id: 'tree-shaking',
title: 'Enable Tree Shaking',
effort: 'low',
impact: 'medium',
savings: this.analysis.scripts.reduce((sum, s) => sum + s.size * 0.2, 0),
implementation: `// webpack.config.js
module.exports = {
optimization: {
usedExports: true,
sideEffects: false,
providedExports: true
}
};
// package.json
{
"sideEffects": false, // or ["*.css", "*.scss"]
}`
});
}
// Compression
optimizations.push({
id: 'compression',
title: 'Enable Gzip/Brotli Compression',
effort: 'low',
impact: 'high',
savings: this.analysis.scripts.reduce((sum, s) => sum + s.size * 0.6, 0),
implementation: `// webpack.config.js
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8
}),
new CompressionPlugin({
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8,
filename: '[path][base].br'
})
]
}`
});
return optimizations;
}
// Get short-term optimizations
getShortTermOptimizations() {
const optimizations = [];
// Code splitting
if (!this.analysis.hasCodeSplitting) {
optimizations.push({
id: 'code-splitting',
title: 'Implement Code Splitting',
effort: 'medium',
impact: 'high',
savings: this.analysis.scripts.reduce((sum, s) => sum + s.size * 0.4, 0),
implementation: `// Routes
const routes = [
{
path: '/',
component: () => import('./pages/Home.vue')
},
{
path: '/profile',
component: () => import('./pages/Profile.vue')
}
];
// Components
const HeavyComponent = lazy(() =>
import(/* webpackChunkName: "heavy" */ './HeavyComponent')
);
// Conditional loading
button.addEventListener('click', async () => {
const { processData } = await import('./dataProcessor');
processData();
});`
});
}
// Replace large libraries
const largeLibraries = this.analysis.scripts
.flatMap(s => s.libraries || [])
.filter(l => ['moment', 'lodash'].includes(l.name));
if (largeLibraries.length > 0) {
optimizations.push({
id: 'replace-libraries',
title: 'Replace Large Libraries',
effort: 'medium',
impact: 'medium',
savings: 200000, // Estimate
libraries: largeLibraries,
implementation: `// Replace Moment.js with date-fns
// Before: import moment from 'moment'; // 67KB gzipped
// After:
import { format, parseISO } from 'date-fns'; // 2KB
// Replace Lodash with native methods
// Before: import _ from 'lodash'; // 24KB gzipped
// After:
import debounce from 'lodash-es/debounce'; // 1KB
// Or use native alternatives
const groupBy = (arr, key) =>
arr.reduce((acc, item) => {
(acc[item[key]] = acc[item[key]] || []).push(item);
return acc;
}, {});`
});
}
// Bundle analysis
optimizations.push({
id: 'bundle-analysis',
title: 'Regular Bundle Analysis',
effort: 'low',
impact: 'medium',
implementation: `// webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false
})
]
};
// package.json
"scripts": {
"analyze": "webpack --profile --json > stats.json && webpack-bundle-analyzer stats.json"
}`
});
return optimizations;
}
// Get long-term optimizations
getLongTermOptimizations() {
const optimizations = [];
// Micro frontends
if (this.getTotalSize() > 2000000) { // 2MB
optimizations.push({
id: 'micro-frontends',
title: 'Migrate to Micro Frontends',
effort: 'high',
impact: 'high',
description: 'Split application into independent deployable units',
implementation: `// Module Federation (Webpack 5)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
// Shell application
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
header: 'header@http://localhost:3001/remoteEntry.js',
catalog: 'catalog@http://localhost:3002/remoteEntry.js'
}
})
]
};
// Micro frontend
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'header',
filename: 'remoteEntry.js',
exposes: {
'./Header': './src/components/Header'
}
})
]
};`
});
}
// Modern build pipeline
optimizations.push({
id: 'modern-pipeline',
title: 'Modern Build Pipeline',
effort: 'high',
impact: 'high',
description: 'Serve modern JavaScript to modern browsers',
implementation: `// Differential serving
module.exports = [
// Modern browsers
{
entry: './src/index.js',
output: {
filename: '[name].[contenthash].modern.js'
},
target: ['web', 'es2020'],
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: { esmodules: true },
bugfixes: true
}]
]
}
}
}]
}
},
// Legacy browsers
{
entry: './src/index.js',
output: {
filename: '[name].[contenthash].legacy.js'
},
target: ['web', 'es5'],
module: {
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: '> 0.25%, not dead'
}]
]
}
}
}]
}
}
];
// HTML
<script type="module" src="app.modern.js"></script>
<script nomodule src="app.legacy.js"></script>`
});
// Build caching
optimizations.push({
id: 'build-caching',
title: 'Implement Build Caching',
effort: 'medium',
impact: 'medium',
description: 'Speed up builds and ensure consistent hashing',
implementation: `// webpack.config.js
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
reuseExistingChunk: true
}
}
}
}
};`
});
return optimizations;
}
// Generate configuration files
generateConfigs() {
return {
webpack: this.generateWebpackConfig(),
babel: this.generateBabelConfig(),
tsconfig: this.generateTsConfig(),
package: this.generatePackageScripts()
};
}
// Generate optimized webpack config
generateWebpackConfig() {
return `const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
clean: true
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
},
mangle: true,
format: {
comments: false
}
},
extractComments: false
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
reuseExistingChunk: true
},
common: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single',
moduleIds: 'deterministic',
usedExports: true,
sideEffects: false
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
}),
process.env.ANALYZE && new BundleAnalyzerPlugin()
].filter(Boolean),
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
}
]
}
};`;
}
// Generate Babel config
generateBabelConfig() {
return `module.exports = {
presets: [
['@babel/preset-env', {
targets: {
browsers: ['> 1%', 'last 2 versions', 'not dead']
},
useBuiltIns: 'usage',
corejs: 3,
modules: false
}],
'@babel/preset-react'
],
plugins: [
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-proposal-class-properties',
['@babel/plugin-transform-runtime', {
corejs: false,
helpers: true,
regenerator: true
}],
process.env.NODE_ENV === 'production' && [
'transform-remove-console',
{ exclude: ['error', 'warn'] }
]
].filter(Boolean)
};`;
}
// Generate TypeScript config
generateTsConfig() {
return `{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"jsx": "react",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": true,
"preserveConstEnums": false,
"sourceMap": true,
"importHelpers": true
},
"exclude": ["node_modules", "dist", "build"]
}`;
}
// Generate package.json scripts
generatePackageScripts() {
return `{
"scripts": {
"build": "NODE_ENV=production webpack --mode production",
"build:analyze": "ANALYZE=true npm run build",
"build:modern": "webpack --config webpack.modern.js",
"build:legacy": "webpack --config webpack.legacy.js",
"build:all": "npm run build:modern && npm run build:legacy",
"bundle:check": "bundlesize",
"bundle:analyze": "webpack-bundle-analyzer stats.json",
"bundle:visualize": "webpack --profile --json > stats.json && open-cli webpack-bundle-analyzer stats.json"
},
"bundlesize": [
{
"path": "./dist/main.*.js",
"maxSize": "100 kB"
},
{
"path": "./dist/vendors.*.js",
"maxSize": "200 kB"
}
]
}`;
}
// Calculate total savings
calculateTotalSavings(optimizations) {
return optimizations.reduce((sum, opt) => sum + (opt.savings || 0), 0);
}
// Get total size
getTotalSize() {
return this.analysis.scripts.reduce((sum, s) => sum + (s.size || 0), 0);
}
}
// Generate optimization plan
const bundleAnalysis = await new BundleAnalyzer().analyzePageBundles();
const optimizer = new BundleOptimizer(bundleAnalysis);
const plan = optimizer.generateOptimizationPlan();
console.log('Optimization Plan:', plan);
Best Practices for Bundle Optimization
- Code Split by Route: Each route in its own chunk
- Lazy Load Below Fold: Don't load what users don't see
- Tree Shake Aggressively: Remove all dead code
- Minimize Parse Time: Smaller bundles parse faster
- Use Production Builds: Development builds are 4x larger
- Implement Caching: Use content hashes for long-term caching
- Monitor Bundle Size: Set up size budgets in CI/CD
- Analyze Regularly: Bundle size creeps up over time
- Modern/Legacy Split: Serve modern JS to modern browsers
- Remove Unused Dependencies: Audit node_modules regularly
Common Bundle Optimization Mistakes
- No Code Splitting: Everything in one giant bundle
- Splitting Too Much: Hundreds of tiny chunks
- Development Builds: React dev mode in production
- Full Library Imports: Importing all of Lodash for one function
- No Tree Shaking: Shipping dead code
- Vendor Bundle Too Large: All dependencies in one chunk
- No Compression: Serving uncompressed JavaScript
- Poor Cache Strategy: Cache busting on every deploy
- Polyfill Bloat: Polyfilling for IE11 when not needed
- Source Maps in Production: Exposing source code
Remember: Every byte of JavaScript has a cost—download time, parse time, compile time, and execution time. Optimize not just for size but for the entire JavaScript processing pipeline.