Skip to content

Tree shake unused assets #64106

Description

@dnfield

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

Activity

  1. added
    c: new featureNothing broken; request for a new capability
    toolAffects the "flutter" command-line tool. See also t: labels.
    a: sizeReducing IPA/APK/JS sizes
    a: buildBuilding flutter applications with the tool
    P3Issues that are less important to the Flutter project
    a: assetsPackaging, accessing, or using assets
    on Aug 18, 2020
  2. dcharkes commented on Nov 13, 2023

    @dcharkes
    Contributor

    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.)

  3. cedvdb commented on Mar 26, 2024

    @cedvdb
    Contributor

    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'

  4. dcharkes commented on Mar 27, 2024

    @dcharkes
    Contributor

    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 @mustBeConst annotation 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 flag in country is 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:build to generate something along the lines of

    Country {
      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:messages is 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?

  5. cedvdb commented on Mar 27, 2024

    @cedvdb
    Contributor

    @dcharkes

    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.

  6. dcharkes commented on Mar 27, 2024

    @dcharkes
    Contributor

    Ah, in that case just the constant propagation without string interpolation would work. 👌

    The package:a hook/link.dart would look at the isoCode when 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:a link.dart can do the string-interpolation from the const isoCode argument to the full assetId.

    Tracking issue for link.dart:

  7. mosuem commented on Mar 27, 2024

    @mosuem
    Contributor

    The use case works without const propagation, but the author of package:a will need to add a link.dart and place ResourceIdentifier annotations.

    // 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 ResourceIdentifier annotation will record calls to Flag, and their arguments, so in this case only NL. The link.dart of package:a can then remove all assets/svg/*.svg assets except assets/svg/fr.svg and assets/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 Flag still have const arguments, which can be recorded. I imagine this to be quite complex though, as values would have to be marked as const to know that isoCode in the for loop is const, then name would have to be marked as const, and finally the ResourceIdentifier would have to infer that it gets all values from IsoCode. I don't know if this is feasible to achieve.

  8. davidmorgan commented on Mar 27, 2024

    @davidmorgan
    Contributor

    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;
    
  9. normidar commented on Jul 10, 2024

    @normidar

    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.

  10. added
    tool-still-validIssues that have been deemed still valid as part of the Flutter Tools issue cleanup initiative.
    on Jul 9, 2025
  11. self-assigned this
    on Mar 26, 2026
  12. added
    ⏳Bot is counting down the days until it unassigns the issue
    on Jul 30, 2026
  13. flutter-triage-bot commented on Jul 30, 2026

    @flutter-triage-bot

    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!

  14. dcharkes commented on Jul 30, 2026

    @dcharkes
    Contributor

    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.)

  15. removed
    ⏳Bot is counting down the days until it unassigns the issue
    on Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P3Issues that are less important to the Flutter projecta: assetsPackaging, accessing, or using assetsa: buildBuilding flutter applications with the toola: sizeReducing IPA/APK/JS sizesc: new featureNothing broken; request for a new capabilityteam-toolOwned by Flutter Tool teamtoolAffects the "flutter" command-line tool. See also t: labels.tool-still-validIssues that have been deemed still valid as part of the Flutter Tools issue cleanup initiative.triaged-toolTriaged by Flutter Tool team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions