Skip to content

[google_fonts] Add google_fonts_lite file to allow tree-shaking of the other huge files#11433

Open
TheCarpetMerchant wants to merge 4 commits intoflutter:mainfrom
TheCarpetMerchant:main
Open

[google_fonts] Add google_fonts_lite file to allow tree-shaking of the other huge files#11433
TheCarpetMerchant wants to merge 4 commits intoflutter:mainfrom
TheCarpetMerchant:main

Conversation

@TheCarpetMerchant
Copy link
Copy Markdown

Adds a google_fonts_lite.dart generated file, which contains a map of fontFamily to fonts variants and a getFont function that simply forwards the call to the internal googleFontsTextStyle function which does the downloading and loading of the font.

List which issues are fixed by this PR. You must list at least one issue.
flutter/flutter#184337

I have not done documentation, testing etc because this PR is for discussion purposes. I'd like approval of the concept before doing the paperwork.

The idea is to provide a lite version of the GoogleFonts class. This lite version does not depend on any of the other generated code, which is absolutely huge and takes up a massive amount of space (8MB on android for v8.0.0). By using this lite version only, the compiler is able to tree-shake all that additionnal code while you retain the ability to invoke any font from the package via getFont.

For me this brings the package's footprint from 8MB/target platform to less than a 1MB/target when building a release apk.

@flutter-dashboard
Copy link
Copy Markdown

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@github-actions github-actions Bot added p: google_fonts triage-framework Should be looked at in framework triage labels Apr 4, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a 'lite' version of the Google Fonts generator to support tree-shaking by creating a GoogleFontsLite class. Key changes include updating the generator to process a new template, making the GoogleFontsFile constructor const, and adding the google_fonts_lite.tmpl file. Review feedback recommends fixing a typo in the template comments, declaring the GoogleFontsLite class as abstract with a static const map, using display names for font keys, and implementing safer error handling in the getFont method to replace the current null-assertion operator.

// That is used to allow tree-shaking to remove all of the _parts files.
// If you only call GoogleFontsLite.fontsMap or GoogleFontsLite.loadFont(),
// the code in the GoogleFonts class and its parts classes is never called.
// WHat is called is googleFontsTextStyle, which attempts to load a font if not already loaded.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Typo: "WHat" should be "What".

// What is called is googleFontsTextStyle, which attempts to load a font if not already loaded.

Comment on lines +12 to +21
class GoogleFontsLite {
static Map<String, Map<GoogleFontsVariant, GoogleFontsFile>> fontsMap = {
{{#fontEntries}}
'{{fontFamily}}': {
{{#fontUrls}}
const GoogleFontsVariant(fontWeight: FontWeight.w{{variantWeight}}, fontStyle: FontStyle.{{variantStyle}},) : const GoogleFontsFile('{{hash}}', {{length}},),
{{/fontUrls}}
},
{{/fontEntries}}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider making GoogleFontsLite an abstract class to prevent instantiation, as it only contains static members. Additionally, the fontsMap should be declared as static const for better efficiency. Using fontFamilyDisplay (the actual font name with spaces) as the map key is also recommended for better compatibility with font fallback and consistency with the main GoogleFonts API.

abstract class GoogleFontsLite {
  static const Map<String, Map<GoogleFontsVariant, GoogleFontsFile>> fontsMap = {
    {{#fontEntries}}
      '{{fontFamilyDisplay}}': {
      {{#fontUrls}}
      const GoogleFontsVariant(fontWeight: FontWeight.w{{variantWeight}}, fontStyle: FontStyle.{{variantStyle}},) : const GoogleFontsFile('{{hash}}', {{length}},),
      {{/fontUrls}}
      },
    {{/fontEntries}}
  };

Comment on lines +23 to +25
static TextStyle getFont(String fontFamily) {
return googleFontsTextStyle(fontFamily: fontFamily, fonts: fontsMap[fontFamily]!);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getFont method uses the null-assertion operator !, which will cause a runtime crash if an unknown font family is requested. It is safer to check for the existence of the font and throw a more descriptive error.

  static TextStyle getFont(String fontFamily) {
    final fonts = fontsMap[fontFamily];
    if (fonts == null) {
      throw ArgumentError('No font family with name $fontFamily was found.');
    }
    return googleFontsTextStyle(fontFamily: fontFamily, fonts: fonts);
  }

@stuartmorgan-g stuartmorgan-g requested a review from Piinks April 7, 2026 18:16
Copy link
Copy Markdown
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@guidezpl, correct me if I am wrong, the generator script here would need to integrate with the internal dashboard where we pull these fonts from? Thanks in advance. :)

I appreciate the contribution @TheCarpetMerchant! We need to confirm if this will work with the existing workflows we have for this package.

@guidezpl
Copy link
Copy Markdown
Member

guidezpl commented Apr 8, 2026

@guidezpl, correct me if I am wrong, the generator script here would need to integrate with the internal dashboard where we pull these fonts from? Thanks in advance. :)

I appreciate the contribution @TheCarpetMerchant! We need to confirm if this will work with the existing workflows we have for this package.

Before this PR, the generator finds the font directory to generate the Dart code, and this PR seems to do nothing different in that regard, so from that point of view, this PR is okay

@justinmc justinmc requested a review from Piinks April 21, 2026 22:09
Copy link
Copy Markdown
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your patience while I reviewed this very carefully. This is an excellent addition. Bringing the package footprint down from 8MB to <1MB is a massive win for the ecosystem, especially for web and size-constrained mobile applications. 🎊

While the current results are impressive, I noticed an architectural 'leak' in the dependency graph that we should address to prevent future regressions.

Currently, google_fonts_lite.dart imports google_fonts_base.dart, which in turn imports ../google_fonts.dart. The main google_fonts.dart file exports google_fonts_all_parts.dart, where the 'heavy' GoogleFonts class and its asMap() method live.

The likely reason you are seeing size savings now is that the compiler is smart enough to see that GoogleFonts.asMap() is never called when using the Lite path. However, this is a fragile dependency. Because google_fonts_base.dart still has a direct import path to the heavy library (just to access the Config instance), any future change to the GoogleFonts class, such as a static initializer, a new field, or a change in how Config is accessed, could accidentally 'trap' the compiler into pulling all 8MB of font parts back into the binary without warning.

To make this optimization robust and permanent, we should decouple the configuration from the font metadata:

  1. Move the Config class and the GoogleFonts.config static instance into a new, standalone internal file (e.g., lib/src/google_fonts_config.dart).
  2. Update google_fonts_base.dart to import this new config file instead of the heavy google_fonts.dart entry point.
  3. Ensure the generator is updated to reflect this new structure.

Given that the Config refactor is a foundational change, it might be cleanest to handle the extraction of Config in a separate, small PR first. Once the configuration is decoupled, this PR can be rebased to provide a truly 'Lite' path that is cryptographically sealed off from the heavy parts files.

WDYT @TheCarpetMerchant? :)

@TheCarpetMerchant
Copy link
Copy Markdown
Author

TheCarpetMerchant commented Apr 29, 2026

The likely reason you are seeing size savings now is that the compiler is smart enough to see that GoogleFonts.asMap() is never called when using the Lite path. However, this is a fragile dependency. Because google_fonts_base.dart still has a direct import path to the heavy library (just to access the Config instance), any future change to the GoogleFonts class, such as a static initializer, a new field, or a change in how Config is accessed, could accidentally 'trap' the compiler into pulling all 8MB of font parts back into the binary without warning.

To make this optimization robust and permanent, we should decouple the configuration from the font metadata:

1. Move the `Config` class and the `GoogleFonts.config` static instance into a new, standalone internal file (e.g., `lib/src/google_fonts_config.dart`).

2. Update `google_fonts_base.dart` to import this new config file instead of the heavy `google_fonts.dart` entry point.

3. Ensure the generator is updated to reflect this new structure.

Sure, makes sense to make it more robust. Here's a PR where each part of the library (the full part and the lite part) has its own instance of the config stored as a static variable within their own file (although the lite version doesn't exist yet lol) : #11602

Edit : I didn't keep a single static instance because that would have meant breaking existing user code by moving the instance outside of the GoogleFonts class and into the GoogleFontsConfig class. This would be a huge annoyance for users for no real benefit as users of the lite version will only use that one, and users of the main one won't see the difference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p: google_fonts triage-framework Should be looked at in framework triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants