Skip to main content
Glama
bundleSizeAnalyzer.js13.7 kB
export async function analyzeBundleSize(args) { const { projectPath, platform = 'all', includeAssets = true, includeTreemap = true } = args; try { const analysis = { summary: {}, breakdown: {}, assets: {}, dependencies: {}, recommendations: [], optimizationPotential: {}, }; // Analyze bundle size by platform if (platform === 'all' || platform === 'android') { analysis.breakdown.android = await analyzeAndroidBundle(projectPath); } if (platform === 'all' || platform === 'ios') { analysis.breakdown.ios = await analyzeiOSBundle(projectPath); } if (platform === 'all' || platform === 'web') { analysis.breakdown.web = await analyzeWebBundle(projectPath); } // Analyze assets if (includeAssets) { analysis.assets = await analyzeAssets(projectPath); } // Analyze dependencies impact analysis.dependencies = await analyzeDependencies(projectPath); // Calculate summary analysis.summary = calculateSummary(analysis); // Generate recommendations analysis.recommendations = generateSizeRecommendations(analysis); // Calculate optimization potential analysis.optimizationPotential = calculateOptimizationPotential(analysis); // Generate treemap visualization const treemap = includeTreemap ? generateTreemap(analysis) : null; return { content: [ { type: 'text', text: JSON.stringify({ analysis, treemap, commands: getOptimizationCommands(), comparison: generateSizeComparison(analysis), }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error analyzing bundle size: ${error.message}`, }, ], }; } } async function analyzeAndroidBundle(projectPath) { // Simulate Android bundle analysis return { apkSize: { total: 15.2, // MB breakdown: { classes: 4.5, resources: 3.2, native: 2.8, assets: 3.1, other: 1.6, }, }, aabSize: { total: 12.8, // MB breakdown: { base: 10.2, dynamic: 2.6, }, }, methodCount: 45832, architecture: { arm64: 4.2, armeabi: 3.8, x86: 3.9, }, dexInfo: { mainDex: 3.2, multidex: true, dexCount: 2, }, }; } async function analyzeiOSBundle(projectPath) { // Simulate iOS bundle analysis return { ipaSize: { total: 18.5, // MB breakdown: { executable: 5.2, frameworks: 6.8, resources: 4.1, assets: 2.4, }, }, appThinning: { universal: 18.5, iPhone: 14.2, iPad: 15.8, }, bitcode: false, swiftLibraries: 2.1, architecture: { arm64: 8.5, armv7: 0, // deprecated }, }; } async function analyzeWebBundle(projectPath) { // Simulate web bundle analysis return { totalSize: { uncompressed: 8.5, // MB gzipped: 2.8, brotli: 2.2, }, breakdown: { mainDart: 3.2, flutterEngine: 2.8, canvasKit: 1.5, assets: 0.8, other: 0.2, }, chunks: [ { name: 'main.dart.js', size: 3.2, gzipped: 1.1 }, { name: 'flutter.js', size: 2.8, gzipped: 0.9 }, { name: 'canvaskit.wasm', size: 1.5, gzipped: 0.6 }, ], firstLoad: { size: 2.1, // MB criticalPath: ['flutter.js', 'main.dart.js'], }, }; } async function analyzeAssets(projectPath) { // Simulate asset analysis return { total: { count: 142, size: 4.8, // MB }, byType: { images: { count: 85, size: 3.2, largest: [ { name: 'background.png', size: 420, dimensions: '1920x1080' }, { name: 'logo.png', size: 380, dimensions: '512x512' }, { name: 'splash.png', size: 350, dimensions: '1024x1024' }, ], }, fonts: { count: 12, size: 1.2, list: [ { name: 'Roboto-Regular.ttf', size: 168 }, { name: 'Roboto-Bold.ttf', size: 172 }, { name: 'CustomIcons.ttf', size: 45 }, ], }, json: { count: 25, size: 0.3, }, other: { count: 20, size: 0.1, }, }, unused: { count: 8, size: 0.6, files: [ 'old_logo.png', 'unused_animation.json', 'test_data.json', ], }, optimization: { uncompressedImages: 12, potentialSaving: 1.2, // MB }, }; } async function analyzeDependencies(projectPath) { // Simulate dependency analysis return { total: { count: 28, size: 6.5, // MB }, largest: [ { name: 'flutter_map', size: 1.2, version: '^3.0.0' }, { name: 'video_player', size: 0.9, version: '^2.5.0' }, { name: 'firebase_core', size: 0.8, version: '^2.4.0' }, { name: 'cached_network_image', size: 0.6, version: '^3.2.0' }, { name: 'provider', size: 0.3, version: '^6.0.0' }, ], byCategory: { ui: { count: 8, size: 2.1 }, networking: { count: 5, size: 1.8 }, state: { count: 3, size: 0.8 }, utilities: { count: 12, size: 1.8 }, }, treeshaking: { eligible: 15, potentialReduction: 1.8, }, duplicates: [ { package: 'http', versions: ['0.13.5', '1.0.0'], impact: 0.2, }, ], }; } function calculateSummary(analysis) { const summary = { totalSize: {}, largestComponents: [], platforms: {}, }; // Calculate total sizes if (analysis.breakdown.android) { summary.platforms.android = { apk: analysis.breakdown.android.apkSize.total, aab: analysis.breakdown.android.aabSize.total, }; } if (analysis.breakdown.ios) { summary.platforms.ios = { ipa: analysis.breakdown.ios.ipaSize.total, thinned: analysis.breakdown.ios.appThinning.iPhone, }; } if (analysis.breakdown.web) { summary.platforms.web = { uncompressed: analysis.breakdown.web.totalSize.uncompressed, compressed: analysis.breakdown.web.totalSize.gzipped, }; } // Find largest components const components = []; if (analysis.assets.total.size > 2) { components.push({ name: 'Assets', size: analysis.assets.total.size, percentage: 25, }); } if (analysis.dependencies.total.size > 3) { components.push({ name: 'Dependencies', size: analysis.dependencies.total.size, percentage: 35, }); } summary.largestComponents = components.sort((a, b) => b.size - a.size); return summary; } function generateSizeRecommendations(analysis) { const recommendations = []; // Asset recommendations if (analysis.assets.optimization.uncompressedImages > 5) { recommendations.push({ category: 'assets', priority: 'high', title: 'Compress images', description: `${analysis.assets.optimization.uncompressedImages} uncompressed images found`, impact: `Could save ~${analysis.assets.optimization.potentialSaving}MB`, actions: [ 'Use WebP format for better compression', 'Implement image optimization in CI/CD', 'Consider using flutter_image_compress', ], }); } if (analysis.assets.unused.count > 0) { recommendations.push({ category: 'assets', priority: 'medium', title: 'Remove unused assets', description: `${analysis.assets.unused.count} unused assets detected`, impact: `Could save ${analysis.assets.unused.size}MB`, files: analysis.assets.unused.files, }); } // Dependency recommendations if (analysis.dependencies.duplicates.length > 0) { recommendations.push({ category: 'dependencies', priority: 'high', title: 'Resolve duplicate dependencies', description: 'Multiple versions of same packages detected', impact: `Could save ${analysis.dependencies.duplicates.reduce((sum, d) => sum + d.impact, 0)}MB`, duplicates: analysis.dependencies.duplicates, }); } if (analysis.dependencies.largest[0].size > 1) { recommendations.push({ category: 'dependencies', priority: 'medium', title: 'Review large dependencies', description: 'Some dependencies significantly impact bundle size', packages: analysis.dependencies.largest.slice(0, 3), suggestion: 'Consider lighter alternatives or lazy loading', }); } // Platform-specific recommendations if (analysis.breakdown.android?.methodCount > 64000) { recommendations.push({ category: 'android', priority: 'high', title: 'Approaching method count limit', description: 'Android DEX method limit approaching', current: analysis.breakdown.android.methodCount, limit: 65536, suggestion: 'Enable R8/ProGuard for better optimization', }); } if (analysis.breakdown.web?.totalSize.uncompressed > 10) { recommendations.push({ category: 'web', priority: 'high', title: 'Large web bundle size', description: 'Web bundle exceeds recommended size', suggestion: 'Enable deferred loading for better initial load', }); } return recommendations; } function calculateOptimizationPotential(analysis) { const potential = { totalPossibleReduction: 0, byCategory: {}, effort: 'medium', }; // Calculate asset optimization if (analysis.assets.optimization) { potential.byCategory.assets = analysis.assets.optimization.potentialSaving; potential.totalPossibleReduction += analysis.assets.optimization.potentialSaving; } // Calculate dependency optimization if (analysis.dependencies.treeshaking) { potential.byCategory.dependencies = analysis.dependencies.treeshaking.potentialReduction; potential.totalPossibleReduction += analysis.dependencies.treeshaking.potentialReduction; } // Calculate unused asset removal if (analysis.assets.unused) { potential.byCategory.unusedAssets = analysis.assets.unused.size; potential.totalPossibleReduction += analysis.assets.unused.size; } // Determine effort level if (potential.totalPossibleReduction > 5) { potential.effort = 'high'; } else if (potential.totalPossibleReduction < 2) { potential.effort = 'low'; } potential.percentageReduction = (potential.totalPossibleReduction / analysis.summary.platforms.android?.apk || 15) * 100; return potential; } function generateTreemap(analysis) { const treemap = { name: 'app', value: 0, children: [], }; // Add platform breakdowns if (analysis.breakdown.android) { const android = { name: 'Android', children: [], }; Object.entries(analysis.breakdown.android.apkSize.breakdown).forEach(([key, value]) => { android.children.push({ name: key, value: value * 1024, // Convert to KB }); }); treemap.children.push(android); } // Add assets if (analysis.assets.byType) { const assets = { name: 'Assets', children: [], }; Object.entries(analysis.assets.byType).forEach(([type, data]) => { assets.children.push({ name: type, value: data.size * 1024, }); }); treemap.children.push(assets); } // Add dependencies if (analysis.dependencies.largest) { const deps = { name: 'Dependencies', children: analysis.dependencies.largest.map(dep => ({ name: dep.name, value: dep.size * 1024, })), }; treemap.children.push(deps); } // Calculate total treemap.value = treemap.children.reduce((sum, child) => sum + (child.value || child.children.reduce((s, c) => s + c.value, 0)), 0 ); return treemap; } function getOptimizationCommands() { return { android: { buildOptimized: 'flutter build apk --release --shrink', buildAAB: 'flutter build appbundle --release', analyzeAPK: 'flutter build apk --analyze-size', enableR8: 'Add "android.enableR8=true" to gradle.properties', }, ios: { buildOptimized: 'flutter build ios --release', analyzeSize: 'flutter build ios --analyze-size', enableBitcode: 'Enable bitcode in Xcode build settings', }, web: { buildOptimized: 'flutter build web --release --web-renderer canvaskit --tree-shake-icons', analyzeSize: 'flutter build web --analyze-size', enablePWA: 'flutter build web --pwa-strategy offline-first', }, assets: { optimizeImages: 'flutter pub run flutter_image_compress:compress', removeUnused: 'flutter pub run dart_code_metrics:metrics check-unused-files lib', }, }; } function generateSizeComparison(analysis) { // Generate size comparison with industry standards return { android: { current: analysis.breakdown.android?.apkSize.total || 0, recommended: 10, status: analysis.breakdown.android?.apkSize.total > 10 ? 'above' : 'within', }, ios: { current: analysis.breakdown.ios?.ipaSize.total || 0, recommended: 15, status: analysis.breakdown.ios?.ipaSize.total > 15 ? 'above' : 'within', }, web: { current: analysis.breakdown.web?.totalSize.gzipped || 0, recommended: 3, status: analysis.breakdown.web?.totalSize.gzipped > 3 ? 'above' : 'within', }, benchmark: { source: 'Industry standards for production apps', lastUpdated: '2024', }, }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dvillegastech/flutter_mcp_2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server