Icon tree shaking ships unreferenced package icon fonts at full size
- Dominant language
- Dart
- Stars
- 179k
- Forks
- 31.1k
- PR merge metrics
- PR metrics pending
Description
If you have multiple icon font weights and one goes unused, no `const IconData(...)` for that weight survives compilation. With zero references, Flutter cannot infer that it is an icon font and it remains un-tree-shaken.
Related to https://github.com/flutter/flutter/issues/190902.
## Steps to reproduce
1. Create a Flutter app that depends on an icon package bundling multiple weights as separate font families, e.g. [`forui_phosphor 0.26.0`](https://pub.dev/packages/forui_phosphor) (regular, thin, light, bold, fill, each its own ttf and `@staticIconProvider` class).
2. Reference icons from one family only:
```dart
const Icon(FPhosphorIcons.acorn)
```
3. `flutter build ios --release --analyze-size`.
4. Inspect the bundled fonts in the size analysis JSON or the built bundle.
### Expected results
Unreferenced icon font families are tree-shaken.
### Actual results
Only families with at least one discovered `const IconData` are tree-shaken. Families with zero references are copied through at full size.
With reference to light icon:
[ios-code-size-analysis_light_reference.json](https://github.com/user-attachments/files/31426121/ios-code-size-analysis_light_reference.json)
With no reference to light icon:
[ios-code-size-analysis_no_reference.json](https://github.com/user-attachments/files/31426180/ios-code-size-analysis_no_reference.json)
Please inspect attached size_analysis.jsons using `dart devtools --appSizeBase=`.
### Code sample
Code sample
```dart
import 'package:flutter/widgets.dart';
import 'package:forui_phosphor/forui_phosphor.dart';
// MVE for icon fonts with zero IconData references shipping at full size.
//
// forui_phosphor bundles 5 weights as separate font families. Only regular and light are referenced below; thin, bold
// and fill ship at full size (~450-540 KB each) while the referenced families are subsetted to ~1-5 KB.
void main() => runApp(const Application());
class Application extends StatelessWidget {
const new({super.key});
@override
Widget build(BuildContext context) => const Directionality(
textDirection: TextDirection.ltr,
child: ColoredBox(
color: Color(0xFF000000),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(FPhosphorIcons.acorn, size: 48, color: Color(0xFFFFFFFF)),
// Toggle this single reference to observe light.ttf shipping at ~1 KB (subsetted) vs ~537 KB (whole).
Icon(FPhosphorLightIcons.acorn, size: 48, color: Color(0xFFFFFFFF)),
],
),
),
);
}
```
### Flutter Doctor output
Doctor output
```console
Flutter 3.47.1 • channel stable
Framework • revision 6655482ec0 • 2026-08-19
Engine • hash 11d79658c444477b06513d32b52c8c4ccb7276b0
```
Contributor guide
Assessment
This issue has not been assessed yet.