A build-time tool that detects and removes unused assets from Flutter projects. Uses AST (Abstract Syntax Tree) analysis to accurately identify asset references in Dart code.
- π Smart Detection - AST-based scanning (not just regex) for accurate results
- π Multiple Report Formats - Generate Markdown, JSON, or HTML reports
- π‘οΈ Safe Deletion - Automatic backup before removing assets
- β‘ CI/CD Ready - Strict mode for build pipelines
- π― Dynamic Asset Support - Handles interpolated paths with whitelist patterns
- π Annotation Support -
@KeepAssetto preserve specific assets - π§Ή One-Command Cleanup - Analyze, report, and clean in a single command
Add to your pubspec.yaml:
dev_dependencies:
asset_tree_shaker: ^1.1.2Then run:
dart pub getdart pub global activate asset_tree_shakerdart run asset_tree_shaker checkThis single command:
- π Shows all assets with β USED / β UNUSED status
- π Generates
asset_report.mdautomatically - π§Ή Prompts to delete unused assets interactively
# Analyze without prompting for cleanup
dart run asset_tree_shaker analyze
# Generate detailed report
dart run asset_tree_shaker report --format=markdown
# Remove unused assets (with confirmation)
dart run asset_tree_shaker clean
# CI/CD mode: fail if unused assets found
dart run asset_tree_shaker analyze --strictAll-in-one analysis, reporting, and cleanup with interactive prompt.
dart run asset_tree_shaker check [options]Options:
-p, --project Path to Flutter project root (default: .)
-o, --output Output path for the report file (default: asset_report.md)
-c, --config Path to configuration file
-v, --verbose Enable verbose output
--no-report Skip generating report file
--clean Prompt to clean unused assets (default: on)
Examples:
# Full analysis with interactive cleanup
dart run asset_tree_shaker check
# Skip report generation
dart run asset_tree_shaker check --no-report
# Custom report filename
dart run asset_tree_shaker check -o my_report.md
# Custom project path
dart run asset_tree_shaker check -p ./my_flutter_projectAnalyze assets and report unused ones.
dart run asset_tree_shaker analyze [options]Options:
-p, --project Path to Flutter project root (default: .)
-s, --strict Fail with exit code 1 if unused assets found (for CI/CD)
-c, --config Path to configuration file
-v, --verbose Enable verbose output
Examples:
# Analyze current project
dart run asset_tree_shaker analyze
# Strict mode (for CI/CD pipelines)
dart run asset_tree_shaker analyze --strict
# Verbose output
dart run asset_tree_shaker analyze --verboseGenerate a detailed asset usage report.
dart run asset_tree_shaker report [options]Options:
-p, --project Path to Flutter project root (default: .)
-f, --format Report format: markdown, json, html (default: markdown)
-o, --output Output file path
-c, --config Path to configuration file
Examples:
# Generate markdown report
dart run asset_tree_shaker report --format=markdown -o report.md
# Generate JSON report
dart run asset_tree_shaker report --format=json -o report.json
# Generate HTML report
dart run asset_tree_shaker report --format=html -o report.htmlRemove unused assets from the project (with backup).
dart run asset_tree_shaker clean [options]Options:
-p, --project Path to Flutter project root (default: .)
-f, --force Skip confirmation prompt
-d, --dry-run Show what would be deleted without deleting
--no-backup Skip creating backup file
--update-pubspec Remove from pubspec.yaml too
-c, --config Path to configuration file
Examples:
# Clean with confirmation prompt
dart run asset_tree_shaker clean
# Force delete without prompt
dart run asset_tree_shaker clean --force
# Preview deletions without actually deleting
dart run asset_tree_shaker clean --dry-run
# Delete without creating backup
dart run asset_tree_shaker clean --force --no-backupCreate a default configuration file (asset_tree_shaker.yaml).
dart run asset_tree_shaker init [options]Options:
--force Overwrite existing configuration
Example:
# Create default config
dart run asset_tree_shaker init
# Overwrite existing config
dart run asset_tree_shaker init --forceRestore assets from a backup file.
dart run asset_tree_shaker restore [options]Options:
--from Path to backup file (required)
Example:
# Restore from backup
dart run asset_tree_shaker restore --from=.asset_backup_2024-12-30.jsonCreate asset_tree_shaker.yaml in your project root to customize behavior:
# Directories to scan for Dart files (relative to project root)
scan_paths:
- lib/
- test/
# Patterns to exclude from analysis (glob patterns)
exclude_patterns:
- "assets/generated/**"
- "assets/placeholders/**"
- "assets/temp/**"
# Known dynamic patterns (runtime-loaded assets that can't be statically detected)
# Use glob patterns to match files at runtime
dynamic_patterns:
- "assets/avatars/*.png"
- "assets/locales/*.json"
- "assets/themes/**/*.yaml"
# Strict mode: fail with exit code 1 if unused assets found
# Useful for CI/CD pipelines
strict_mode: false
# Report generation settings
generate_report: true
report_format: markdown # markdown, json, or html
report_output: asset_report.mdGenerate default config:
dart run asset_tree_shaker initThis creates a complete asset_tree_shaker.yaml template with all available options.
Dynamic asset paths like 'assets/images/$userId.png' can't be statically analyzed. Asset Tree Shaker handles this through:
// Detected: pattern "assets/images/*"
Image.asset('assets/images/${user.id}.png');dynamic_patterns:
- "assets/avatars/*.png"import 'package:asset_tree_shaker/asset_tree_shaker.dart';
@KeepAsset('assets/images/splash.png')
class SplashScreen extends StatelessWidget { }
@KeepAssets(['assets/sounds/click.mp3', 'assets/sounds/success.mp3'])
class SoundManager { }name: Asset Check
on: [push, pull_request]
jobs:
asset-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dart-lang/setup-dart@v1
with:
sdk: 3.0.0
- name: Install dependencies
run: dart pub get
- name: Check unused assets
run: dart run asset_tree_shaker analyze --strictasset-check:
image: google/dart:latest
script:
- dart pub get
- dart run asset_tree_shaker analyze --strictCreate .git/hooks/pre-commit:
#!/bin/bash
dart run asset_tree_shaker analyze --strict
if [ $? -ne 0 ]; then
echo "Commit aborted: Unused assets found"
exit 1
fiMake it executable:
chmod +x .git/hooks/pre-commitUse Asset Tree Shaker in your own Dart code:
import 'package:asset_tree_shaker/asset_tree_shaker.dart';
Future<void> analyzeAssets() async {
const projectRoot = '.';
// 1. Discover all declared assets
final discovery = AssetDiscovery(projectRoot: projectRoot);
final declaredAssets = await discovery.discoverAssets();
// 2. Scan for asset usage
final config = await loadConfig(); // Load from asset_tree_shaker.yaml
final scanner = UsageScanner(projectRoot: projectRoot, config: config);
final scanResult = await scanner.scan();
// 3. Analyze and compare
final analyzer = GraphAnalyzer(config: config);
final result = analyzer.analyze(
declaredAssets: declaredAssets,
scanResult: scanResult,
projectRoot: projectRoot,
);
// 4. Use results
print('Total assets: ${result.assets.length}');
print('Unused assets: ${result.unusedAssets.length}');
print('Potential savings: ${result.unusedSizeFormatted}');
// 5. Generate reports
final generator = ReportGenerator(result: result);
final mdReport = generator.generate(ReportFormat.markdown);
final jsonReport = generator.generate(ReportFormat.json);
// 6. Clean up
final cleaner = AssetCleaner(projectRoot: projectRoot);
final cleanResult = await cleaner.clean(
analysisResult: result,
dryRun: false,
createBackup: true,
);
print('Deleted: ${cleanResult.deletedAssets.length} assets');
}| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Unused assets found (strict mode) or operation failed |
| 64 | Usage error (invalid arguments) |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π³ Asset Tree Shaker β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Project: /Users/vansh/my_app
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Asset Status β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β UNUSED assets/images/old_logo.png 12.5 KB
β β USED assets/images/logo.png 8.2 KB
β β USED assets/fonts/Roboto.ttf 156.0 KB
β π KEPT assets/backgrounds/placeholder.png 45.3 KB
β β DYNAMIC assets/avatars/default.png 102.1 KB
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Summary:
β
Used: 3
β Unused: 1
βοΈ Skipped: 2
πΎ Total size: 323.1 KB
ποΈ Unused size: 12.5 KB (3.9% savings)
- Check that your
pubspec.yamlhas anassets:section - Verify asset paths are correct in
pubspec.yaml
Add to asset_tree_shaker.yaml:
dynamic_patterns:
- "assets/pattern/**"Or use annotations in your code:
import 'package:asset_tree_shaker/asset_tree_shaker.dart';
@KeepAsset('assets/images/runtime_loaded.png')
class MyWidget {}- Run with
--verboseto see detailed information - Check if assets were moved or renamed
- Verify dynamic asset patterns in config
Contributions are welcome! To get started:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
For detailed guidelines, see CONTRIBUTING.md.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with the Dart analyzer package
- Inspired by similar tools in other ecosystems (webpack, etc.)
- Thanks to the Flutter community for feedback and contributions
Made with β€οΈ by Vansh Panchal