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',
},
};
}