Repository navigation
Tree shake unused assets #64106
Description
Activity
- addedc: new featureNothing broken; request for a new capabilityNothing broken; request for a new capabilitytoolAffects the "flutter" command-line tool. See also t: labels.Affects the "flutter" command-line tool. See also t: labels.a: sizeReducing IPA/APK/JS sizesReducing IPA/APK/JS sizesa: buildBuilding flutter applications with the toolBuilding flutter applications with the toolP3Issues that are less important to the Flutter projectIssues that are less important to the Flutter projecta: assetsPackaging, accessing, or using assetsPackaging, accessing, or using assets
on Aug 18, 2020 - addedteam-toolOwned by Flutter Tool teamOwned by Flutter Tool teamtriaged-toolTriaged by Flutter Tool teamTriaged by Flutter Tool team
on Jul 8, 2023 We should aim for a solution that would also work for Dart standalone. High level idea:
(Once we have a solution that works for any Dart package, we could possibly also revisit the existing built-in shakers and const finders in the flutter framework and dart2js, which could then possibly use the same mechanism.)
An use case I have personally:
- library a contains a set of assets "A"
- library b has a subset of assets from library a it wants to use.
More concrete:
- library A is a flag library containing loads of flags
- library B only uses flags from current countries.
but assets in library a are loaded via string interpolation:
"assets/svg/${isoCode.toLowerCase()}.svg'but assets in library a are loaded via string interpolation:
"assets/svg/${isoCode.toLowerCase()}.svg'As @dnfield mentioned, currently we only analyze consts on immediate call sites.
@mosuem is working on a
@mustBeConstannotation that will make such logic available to a more general assets system dart-lang/sdk#54003.Let me sketch what I understand of your use case
// package:a Uint8List flag(@mustBeConst String assetId){ // ... } // package:b void country(String isoCode){ // ... flag("assets/svg/${isoCode.toLowerCase()}.svg"); // ... } void main(){ country("NL"); country("DE"); country("BE"); }
This won't work out of the box because the invocation of
flagincountryis not a const argument.If we start propagating consts, then it could work if the could would be changed to mention the assetId verbatim:
// package:a Uint8List flag(@mustBeConst String assetId){ // ... } // package:b void country(@mustBeConst String assetId){ final String isoCode = // ... assetId ... // ... flag(assetId); // ... } void main() { country("package:a/assets/svg/NL.svg"); country("package:a/assets/svg/DE.svg"); country("package:a/assets/svg/BE.svg"); }
If we would then add some kind of support for evaluating expressions during this constant propagation as well, then we could possibly support what you are asking for out of the box.
Some more reading on this idea of constant propagation:
A workaround for the lack of constant propagation + evaluating expressions is to use
package:buildto generate something along the lines ofCountry { static Country get nl => country("package:a/assets/svg/NL.svg"); static Country get de => country("package:a/assets/svg/DE.svg"); static Country get be => country("package:a/assets/svg/BE.svg"); }
I believe this is the approach that
package:messagesis taking. Maybe @mosuem can eleborate.@mosuem, I believe wrapping an existing API is a rather common use case. I'm not sure we should go and tell everyone to use code gen to solve this.
@davidmorgan Maybe macros could do the same trick?
Just to be clear, my personal use case would be more akin to this for your first snippet:
// package:a class Flag { const Flag(this.isoCode); _loadSvg("assets/svg/${isoCode.toLowerCase()}.svg") } // package:b enum IsoCode { fr, us } void main(){ for (final isoCode in IsoCode.values) { final flag = Flag(isoCode.name); } }
Differences :
- The package A library would not require a mustBeConst
- The client library (package B) could use mustBeConst to hopefully trigger treeshaking
- String interpolation is done in package A.
More theorically:
- Package A with a set of assets X
- Package B which uses only a subset of X.
- We could imagine another user makes his own package B' with his own subset of X
Basically A, in this instance would be a library where user can pick and choose the assets they need, and might want to use all assets dynamically.
Ah, in that case just the constant propagation without string interpolation would work. 👌
The
package:ahook/link.dartwould look at theisoCodewhen filtering what assets to bundle.// package:a Uint8List flag(@mustBeConst String isoCode){ loadFlag("assets/svg/${isoCode.toLowerCase()}.svg"); } // package:b void country(@mustBeConst String isoCode){ // ... flag(isoCode); // ... } void main(){ country("NL"); }
The
package:alink.dartcan do the string-interpolation from the const isoCode argument to the full assetId.Tracking issue for
link.dart:The use case works without const propagation, but the author of
package:awill need to add alink.dartand placeResourceIdentifierannotations.// package:a @ResourceIdentifier() class Flag { const Flag(this.isoCode); _loadSvg("assets/svg/${isoCode.toLowerCase()}.svg") } // package:b enum IsoCode { fr, us } const flags = [Flag('fr'), Flag('us')]; // Could also be generated through codegen void main(){ for (final flag in flags) { print(flag); } }
The
ResourceIdentifierannotation will record calls toFlag, and their arguments, so in this case onlyNL. Thelink.dartofpackage:acan then remove allassets/svg/*.svgassets exceptassets/svg/fr.svgandassets/svg/us.svg.
Having const propagation would allow you original example using
void main(){ for (final isoCode in IsoCode.values) { final flag = Flag(isoCode.name); } }
to work, as the logic would be smart enough to see that all calls to
Flagstill have const arguments, which can be recorded. I imagine this to be quite complex though, asvalueswould have to be marked as const to know thatisoCodein the for loop is const, thennamewould have to be marked as const, and finally theResourceIdentifierwould have to infer that it gets all values fromIsoCode. I don't know if this is feasible to achieve.Re: what macros will be able to do, sure, you will be able to do some computation then emit consts or instantiations of const things; you can emit either just the implementation:
@Country('NL') static Country get nl;or declare+implement:
@CountryGetters(['NL', 'FR', ...]) library;It would be very helpful if tree shaking could be done for static strings only. This would significantly improve asset management for many developers.
For non-static strings, providing lint warnings to alert developers about potential unused assets would be an excellent solution.
This way, developers can manually review and manage those assets. Implementing these features would greatly enhance the efficiency of Flutter projects and help in keeping the final build lean and optimized.
Reacted by Moritz, Werner Kok, Kane Wickman, clotodex, Xiuxuan Wang, Niclas, vladislavglushko, Ad and Shiromi Tsukasa- addedtool-still-validIssues that have been deemed still valid as part of the Flutter Tools issue cleanup initiative.Issues that have been deemed still valid as part of the Flutter Tools issue cleanup initiative.
on Jul 9, 2025 - added⏳Bot is counting down the days until it unassigns the issueBot is counting down the days until it unassigns the issue
on Jul 30, 2026 This issue is assigned to @dcharkes but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!
Using
@RecordUse()and tree shaking in link hooks will go stable in the next Flutter release. This enables tree-shaking dynamic libraries "code assets" from build hooks in link hooks.Data assets (images, sounds, etc.) from build hooks are still gated behind an experiment flag. So in order to tree-shake these the experiment still needs to be enabled. (No timeline yet on making data assets stable.)
- removed⏳Bot is counting down the days until it unassigns the issueBot is counting down the days until it unassigns the issue
on Jul 30, 2026
In theory, we could probably shake out assets (e.g. fonts, images) from packages that a user isn't using.
In practice, this is actually pretty hard because of the way strings are used in this and level of detection we can use when assets are used dynamically (e.g. get the name of an asset to show from your service, then show it).
This is difficult even for icon fonts, because if you don't have a const reference to the font using IconData, we may not know if you're using it as a regular font or not.
Today, the work around is to just not use packages that have assets you do not need - or, to re-package them to your liking before shipping thea pp.
See also: b/146889328