crapifyme
Version:
Ultra-fast developer productivity CLI tools - remove comments, logs, and more
335 lines • 13.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BundleAnalyzer = void 0;
const https_1 = __importDefault(require("https"));
class BundleAnalyzer {
constructor(cacheTimeout = 3600000, requestTimeout = 10000) {
this.cache = new Map();
this.cacheTimeout = cacheTimeout;
this.requestTimeout = requestTimeout;
}
async analyzeBundleSize(dependencies) {
const packageSizes = new Map();
const heavyPackageThreshold = 100 * 1024;
const packages = Array.from(dependencies.entries())
.filter(([, dep]) => !dep.isDev)
.map(([name, dep]) => ({ name, version: this.parseVersion(dep.currentVersion) }));
if (packages.length > 0) {
process.stdout.write(`Analyzing bundle sizes for ${packages.length} packages`);
const animateTimer = setInterval(() => {
process.stdout.write('.');
}, 1000);
for (let i = 0; i < packages.length; i++) {
const pkg = packages[i];
try {
const sizeInfo = await this.getPackageSize(pkg.name, pkg.version);
packageSizes.set(pkg.name, sizeInfo);
await this.delay(100);
}
catch (error) {
if (i < 3) {
// Only warn for first few failures
console.warn(`\nWarning: Could not get size for ${pkg.name}: ${error.message}`);
}
}
}
clearInterval(animateTimer);
process.stdout.write('\n');
}
const totalRawSize = Array.from(packageSizes.values()).reduce((sum, pkg) => sum + pkg.size.raw, 0);
const totalGzipSize = Array.from(packageSizes.values()).reduce((sum, pkg) => sum + pkg.size.gzip, 0);
const largestPackages = Array.from(packageSizes.values())
.sort((a, b) => b.size.raw - a.size.raw)
.slice(0, 20)
.map(pkg => ({
name: pkg.name,
size: {
raw: pkg.size.raw,
gzip: pkg.size.gzip,
percentage: totalRawSize > 0 ? (pkg.size.raw / totalRawSize) * 100 : 0
}
}));
const treeshakeable = Array.from(packageSizes.values())
.filter(pkg => pkg.treeshakeable)
.map(pkg => pkg.name);
const nonTreeshakeable = Array.from(packageSizes.values())
.filter(pkg => !pkg.treeshakeable && pkg.size.raw > heavyPackageThreshold)
.map(pkg => pkg.name);
const sideEffects = Array.from(packageSizes.values())
.filter(pkg => pkg.sideEffects)
.map(pkg => pkg.name);
return {
totalSize: {
raw: totalRawSize,
gzip: totalGzipSize,
formatted: {
raw: this.formatSize(totalRawSize),
gzip: this.formatSize(totalGzipSize)
}
},
largestPackages,
treeshakeable,
nonTreeshakeable,
sideEffects
};
}
async getPackageSize(packageName, version = 'latest') {
const { realPackageName, realVersion } = this.parsePackageAlias(packageName, version);
const cacheKey = `${realPackageName}@${realVersion}`;
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.cachedAt < this.cacheTimeout) {
return this.formatPackageSize(cached);
}
}
const data = await this.fetchFromNpmRegistry(realPackageName, realVersion);
data.cachedAt = Date.now();
this.cache.set(cacheKey, data);
return this.formatPackageSize(data);
}
async fetchFromNpmRegistry(packageName, version) {
const cleanVersion = version.replace(/^[\^~]/, '').split(' ')[0];
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
return new Promise((resolve, reject) => {
const req = https_1.default.get(url, { timeout: this.requestTimeout }, res => {
if (res.statusCode !== 200) {
reject(new Error(`Package not found: ${res.statusCode}`));
return;
}
let data = '';
res.on('data', chunk => (data += chunk));
res.on('end', () => {
try {
const packageData = JSON.parse(data);
let versionData;
if (cleanVersion === 'latest' || !cleanVersion) {
const latestVersion = packageData['dist-tags']?.latest;
versionData = packageData.versions?.[latestVersion];
}
else {
versionData =
packageData.versions?.[cleanVersion] ||
packageData.versions?.[packageData['dist-tags']?.latest];
}
if (!versionData) {
reject(new Error('Version not found'));
return;
}
const bundleSize = this.estimateBundleSize(versionData, packageName);
const estimatedGzipSize = Math.floor(bundleSize * 0.3);
const hasESM = !!(versionData.module ||
versionData.exports ||
versionData.type === 'module');
const hasSideEffects = versionData.sideEffects !== false;
resolve({
name: packageName,
version: versionData.version,
size: bundleSize,
gzip: estimatedGzipSize,
description: versionData.description,
dependencyCount: Object.keys(versionData.dependencies || {}).length,
hasJSNext: hasESM,
hasJSModule: hasESM,
hasSideEffects,
isModuleType: versionData.type === 'module'
});
}
catch (error) {
reject(new Error('Failed to parse npm registry response'));
}
});
});
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.on('error', error => {
reject(new Error(`Network error: ${error.message}`));
});
});
}
formatPackageSize(data) {
return {
name: data.name,
size: {
raw: data.size,
gzip: data.gzip,
formatted: {
raw: this.formatSize(data.size),
gzip: this.formatSize(data.gzip)
}
},
treeshakeable: data.hasJSNext || data.hasJSModule || data.isModuleType || false,
sideEffects: data.hasSideEffects || false
};
}
async estimateProjectBundleSize(dependencies) {
const analysis = await this.analyzeBundleSize(dependencies);
const breakdown = analysis.largestPackages.map(pkg => ({
name: pkg.name,
size: {
raw: pkg.size.raw,
gzip: pkg.size.gzip
},
percentage: pkg.size.percentage
}));
return {
estimated: {
raw: analysis.totalSize.raw,
gzip: analysis.totalSize.gzip
},
breakdown
};
}
async comparePackageSizes(packages) {
const results = [];
for (const pkg of packages) {
try {
const sizeInfo = await this.getPackageSize(pkg);
results.push({
name: pkg,
size: sizeInfo.size,
formatted: sizeInfo.size.formatted,
treeshakeable: sizeInfo.treeshakeable,
sideEffects: sizeInfo.sideEffects
});
await this.delay(300);
}
catch (error) {
console.warn(`Warning: Failed to get size for ${pkg}: ${error.message}`);
}
}
return results.sort((a, b) => b.size.raw - a.size.raw);
}
getSizeDifference(currentSize, newSize) {
const absolute = newSize - currentSize;
const percentage = currentSize > 0 ? (absolute / currentSize) * 100 : 0;
const sign = absolute > 0 ? '+' : '';
const formattedAbsolute = this.formatSize(Math.abs(absolute));
const formattedPercentage = percentage.toFixed(1);
return {
absolute,
percentage,
formatted: `${sign}${formattedAbsolute} (${sign}${formattedPercentage}%)`
};
}
identifyHeavyPackages(dependencies, threshold = 100 * 1024) {
return this.analyzeBundleSize(dependencies).then(analysis => analysis.largestPackages.filter(pkg => pkg.size.raw > threshold).map(pkg => pkg.name));
}
generateSizeReport(analysis) {
const lines = [
'📊 BUNDLE SIZE ANALYSIS',
'─'.repeat(50),
`Total Bundle Size: ${analysis.totalSize.formatted.raw} (${analysis.totalSize.formatted.gzip} gzipped)`,
'',
'🔝 LARGEST PACKAGES:'
];
analysis.largestPackages.slice(0, 10).forEach((pkg, index) => {
lines.push(`${(index + 1).toString().padStart(2)}. ${pkg.name.padEnd(25)} ${pkg.size.raw.toString().padStart(8)} bytes (${pkg.size.percentage.toFixed(1)}%)`);
});
if (analysis.treeshakeable.length > 0) {
lines.push('', '🌳 TREE-SHAKEABLE:', analysis.treeshakeable.slice(0, 5).join(', '));
}
if (analysis.nonTreeshakeable.length > 0) {
lines.push('', '⚠️ NON-TREE-SHAKEABLE:', analysis.nonTreeshakeable.slice(0, 5).join(', '));
}
if (analysis.sideEffects.length > 0) {
lines.push('', '⚡ HAS SIDE EFFECTS:', analysis.sideEffects.slice(0, 5).join(', '));
}
return lines.join('\n');
}
parseVersion(version) {
return version.replace(/^[\^~>=<]/, '').split(' ')[0];
}
formatSize(bytes) {
if (bytes === 0)
return '0B';
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(unitIndex === 0 ? 0 : 1)}${units[unitIndex]}`;
}
estimateBundleSize(versionData, packageName) {
const unpackedSize = versionData.dist?.unpackedSize || 0;
const sizeMultipliers = {
react: 0.015,
vue: 0.02,
angular: 0.01,
svelte: 0.05,
lodash: 0.04,
moment: 0.02,
dayjs: 0.08,
'date-fns': 0.06,
'pixi.js': 0.03,
three: 0.02,
'@rive-app/canvas': 0.04,
leaflet: 0.05,
'mapbox-gl': 0.02,
'@lucide/svelte': 0.15,
'@heroicons/react': 0.2,
msgpackr: 0.1,
'@thumbmarkjs/thumbmarkjs': 0.3,
'@inlang/paraglide-js': 0.2,
'@inlang/paraglide-sveltekit': 0.15
};
let multiplier = 0.08;
if (sizeMultipliers[packageName]) {
multiplier = sizeMultipliers[packageName];
}
else {
if (packageName.includes('types/')) {
return 0;
}
else if (packageName.includes('icon') || packageName.includes('lucide')) {
multiplier = 0.15;
}
else if (packageName.includes('babel') || packageName.includes('eslint')) {
return 0;
}
else if (packageName.includes('util') || packageName.includes('helper')) {
multiplier = 0.12;
}
else if (packageName.startsWith('@types/')) {
return 0;
}
}
const estimatedSize = Math.max(unpackedSize * multiplier, 1024);
return Math.min(estimatedSize, 2 * 1024 * 1024);
}
parsePackageAlias(packageName, version) {
if (version.startsWith('npm:')) {
const match = version.match(/^npm:(.+?)@(.+)$/);
if (match) {
return {
realPackageName: match[1],
realVersion: match[2]
};
}
}
return {
realPackageName: packageName,
realVersion: version
};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
clearCache() {
this.cache.clear();
}
getCacheStats() {
return {
size: this.cache.size,
entries: Array.from(this.cache.keys())
};
}
}
exports.BundleAnalyzer = BundleAnalyzer;
//# sourceMappingURL=bundle-analyzer.js.map