Bundle Analyzer Setup - Complete Guide
Published: September 25, 2024 | Reading time: 23 minutes
Bundle Analyzer Overview
Bundle analysis is essential for optimizing frontend application performance:
Bundle Analysis Benefits
# Bundle Analysis Benefits
- Identify large dependencies
- Optimize bundle size
- Find duplicate code
- Analyze code splitting
- Monitor bundle growth
- Performance insights
- Cost optimization
Webpack Bundle Analyzer
Webpack Bundle Analysis Setup
Webpack Bundle Analyzer
# Webpack Bundle Analyzer Setup
# 1. Installation
npm install --save-dev webpack-bundle-analyzer
# 2. Basic Configuration
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'server',
openAnalyzer: true,
generateStatsFile: true,
statsFilename: 'bundle-stats.json',
reportFilename: 'bundle-report.html'
})
]
};
# 3. Conditional Analysis
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
const shouldAnalyze = process.env.ANALYZE === 'true';
const plugins = [];
if (shouldAnalyze) {
plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html',
generateStatsFile: true,
statsFilename: 'bundle-stats.json'
})
);
}
return {
plugins
};
};
# 4. Package.json Scripts
{
"scripts": {
"build": "webpack --mode production",
"build:analyze": "ANALYZE=true webpack --mode production",
"analyze": "webpack-bundle-analyzer dist/bundle-stats.json"
}
}
# 5. Advanced Configuration
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html',
generateStatsFile: true,
statsFilename: 'bundle-stats.json',
analyzerPort: 'auto',
defaultSizes: 'parsed',
excludeAssets: /\.map$/,
logLevel: 'info'
})
]
};
# 6. Custom Analysis Script
// scripts/analyze-bundle.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
function analyzeBundle() {
console.log('Building bundle for analysis...');
try {
execSync('npm run build:analyze', { stdio: 'inherit' });
const statsPath = path.join(__dirname, '../dist/bundle-stats.json');
const reportPath = path.join(__dirname, '../dist/bundle-report.html');
if (fs.existsSync(statsPath)) {
console.log('Bundle analysis completed!');
console.log(`Stats file: ${statsPath}`);
console.log(`Report file: ${reportPath}`);
// Parse and display key metrics
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
const assets = stats.assets || [];
console.log('\nBundle Size Summary:');
assets.forEach(asset => {
const sizeKB = (asset.size / 1024).toFixed(2);
console.log(`${asset.name}: ${sizeKB} KB`);
});
}
} catch (error) {
console.error('Bundle analysis failed:', error.message);
process.exit(1);
}
}
analyzeBundle();
# 7. Bundle Size Monitoring
// scripts/bundle-monitor.js
const fs = require('fs');
const path = require('path');
class BundleMonitor {
constructor() {
this.thresholds = {
main: 500 * 1024, // 500KB
vendor: 1000 * 1024, // 1MB
total: 2000 * 1024 // 2MB
};
}
analyzeBundle(statsPath) {
if (!fs.existsSync(statsPath)) {
throw new Error('Stats file not found');
}
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
const assets = stats.assets || [];
const analysis = {
totalSize: 0,
chunks: {},
warnings: [],
recommendations: []
};
assets.forEach(asset => {
analysis.totalSize += asset.size;
const chunkName = this.getChunkName(asset.name);
if (!analysis.chunks[chunkName]) {
analysis.chunks[chunkName] = 0;
}
analysis.chunks[chunkName] += asset.size;
});
// Check thresholds
Object.entries(analysis.chunks).forEach(([chunk, size]) => {
const threshold = this.thresholds[chunk] || this.thresholds.main;
if (size > threshold) {
analysis.warnings.push(
`${chunk} chunk (${this.formatSize(size)}) exceeds threshold (${this.formatSize(threshold)})`
);
}
});
if (analysis.totalSize > this.thresholds.total) {
analysis.warnings.push(
`Total bundle size (${this.formatSize(analysis.totalSize)}) exceeds threshold (${this.formatSize(this.thresholds.total)})`
);
}
// Generate recommendations
this.generateRecommendations(analysis, stats);
return analysis;
}
getChunkName(filename) {
if (filename.includes('vendor')) return 'vendor';
if (filename.includes('main')) return 'main';
if (filename.includes('runtime')) return 'runtime';
return 'other';
}
formatSize(bytes) {
const sizes = ['B', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 B';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}
generateRecommendations(analysis, stats) {
const modules = stats.modules || [];
// Find large modules
const largeModules = modules
.filter(module => module.size > 100 * 1024) // > 100KB
.sort((a, b) => b.size - a.size)
.slice(0, 5);
if (largeModules.length > 0) {
analysis.recommendations.push('Large modules detected:');
largeModules.forEach(module => {
analysis.recommendations.push(`- ${module.name}: ${this.formatSize(module.size)}`);
});
}
// Check for duplicate modules
const moduleNames = modules.map(m => m.name);
const duplicates = moduleNames.filter((name, index) => moduleNames.indexOf(name) !== index);
if (duplicates.length > 0) {
analysis.recommendations.push('Duplicate modules detected - consider code splitting');
}
}
generateReport(analysis) {
const report = {
timestamp: new Date().toISOString(),
summary: {
totalSize: this.formatSize(analysis.totalSize),
chunkCount: Object.keys(analysis.chunks).length,
warningCount: analysis.warnings.length
},
chunks: Object.entries(analysis.chunks).map(([name, size]) => ({
name,
size: this.formatSize(size)
})),
warnings: analysis.warnings,
recommendations: analysis.recommendations
};
return report;
}
}
const monitor = new BundleMonitor();
const analysis = monitor.analyzeBundle('./dist/bundle-stats.json');
const report = monitor.generateReport(analysis);
console.log('Bundle Analysis Report:');
console.log(JSON.stringify(report, null, 2));
# 8. Vite Bundle Analysis
// vite.config.js
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({
filename: 'dist/bundle-analysis.html',
open: true,
gzipSize: true,
brotliSize: true
})
],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'moment']
}
}
}
}
});
# 9. Rollup Bundle Analysis
import { visualizer } from 'rollup-plugin-visualizer';
export default {
plugins: [
visualizer({
filename: 'dist/bundle-analysis.html',
open: true,
gzipSize: true,
brotliSize: true,
template: 'treemap'
})
]
};
# 10. Bundle Comparison Tool
// scripts/compare-bundles.js
const fs = require('fs');
const path = require('path');
class BundleComparator {
constructor() {
this.baselinePath = './baseline/bundle-stats.json';
this.currentPath = './dist/bundle-stats.json';
}
compareBundles() {
const baseline = this.loadStats(this.baselinePath);
const current = this.loadStats(this.currentPath);
if (!baseline || !current) {
console.log('Missing baseline or current stats');
return;
}
const comparison = {
timestamp: new Date().toISOString(),
totalSizeChange: this.calculateSizeChange(baseline.totalSize, current.totalSize),
chunkChanges: this.compareChunks(baseline.chunks, current.chunks),
newChunks: this.findNewChunks(baseline.chunks, current.chunks),
removedChunks: this.findRemovedChunks(baseline.chunks, current.chunks)
};
this.generateComparisonReport(comparison);
}
loadStats(statsPath) {
if (!fs.existsSync(statsPath)) return null;
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
const assets = stats.assets || [];
return {
totalSize: assets.reduce((sum, asset) => sum + asset.size, 0),
chunks: this.groupByChunk(assets)
};
}
groupByChunk(assets) {
const chunks = {};
assets.forEach(asset => {
const chunkName = this.getChunkName(asset.name);
if (!chunks[chunkName]) {
chunks[chunkName] = 0;
}
chunks[chunkName] += asset.size;
});
return chunks;
}
getChunkName(filename) {
if (filename.includes('vendor')) return 'vendor';
if (filename.includes('main')) return 'main';
if (filename.includes('runtime')) return 'runtime';
return 'other';
}
calculateSizeChange(baseline, current) {
const change = current - baseline;
const percentChange = (change / baseline) * 100;
return {
absolute: change,
percent: percentChange,
formatted: this.formatSizeChange(change, percentChange)
};
}
formatSizeChange(change, percent) {
const size = Math.abs(change);
const direction = change > 0 ? '+' : '-';
const formattedSize = this.formatSize(size);
return `${direction}${formattedSize} (${percent.toFixed(1)}%)`;
}
formatSize(bytes) {
const sizes = ['B', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 B';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}
compareChunks(baseline, current) {
const changes = {};
const allChunks = new Set([...Object.keys(baseline), ...Object.keys(current)]);
allChunks.forEach(chunk => {
const baselineSize = baseline[chunk] || 0;
const currentSize = current[chunk] || 0;
if (baselineSize !== currentSize) {
changes[chunk] = this.calculateSizeChange(baselineSize, currentSize);
}
});
return changes;
}
findNewChunks(baseline, current) {
return Object.keys(current).filter(chunk => !baseline[chunk]);
}
findRemovedChunks(baseline, current) {
return Object.keys(baseline).filter(chunk => !current[chunk]);
}
generateComparisonReport(comparison) {
console.log('Bundle Comparison Report:');
console.log(`Total size change: ${comparison.totalSizeChange.formatted}`);
if (Object.keys(comparison.chunkChanges).length > 0) {
console.log('\nChunk changes:');
Object.entries(comparison.chunkChanges).forEach(([chunk, change]) => {
console.log(` ${chunk}: ${change.formatted}`);
});
}
if (comparison.newChunks.length > 0) {
console.log('\nNew chunks:', comparison.newChunks.join(', '));
}
if (comparison.removedChunks.length > 0) {
console.log('\nRemoved chunks:', comparison.removedChunks.join(', '));
}
}
}
const comparator = new BundleComparator();
comparator.compareBundles();
Bundle Analysis Tools
Analysis Tools and Techniques
Analysis Tools
- webpack-bundle-analyzer
- rollup-plugin-visualizer
- Vite bundle analysis
- Bundle size monitoring
- Bundle comparison
- Custom analysis scripts
- CI/CD integration
Analysis Features
- Bundle size visualization
- Dependency analysis
- Code splitting insights
- Duplicate detection
- Size thresholds
- Historical comparison
- Performance metrics
Summary
Bundle analyzer setup involves several key components:
- Webpack Bundle Analyzer: Comprehensive analysis with visualization
- Vite Analysis: Fast analysis with rollup-plugin-visualizer
- Bundle Monitoring: Automated size tracking and alerts
- Comparison Tools: Historical analysis and trend monitoring
Need More Help?
Struggling with bundle analysis or need help optimizing your bundle size? Our frontend experts can help you set up comprehensive bundle analysis and optimization strategies.
Get Bundle Analysis Help