-
Notifications
You must be signed in to change notification settings - Fork 384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve page experience for fonts in core themes #6674
Improve page experience for fonts in core themes #6674
Conversation
Plugin builds for 96b748b are ready 🛎️!
|
This poses a challenge. The browser should only be preloading the font format that it supports. As I understand, a browser here will preload the woff font files and the woff2 font files even though it only will use the woff2 ones. It seems the best practice is to only preload the latest format which is the first in the list (e.g. woff2). Ref: https://stackoverflow.com/a/53376159/93579 |
In the latest commits I pushed up, I've addressed this by only preloading the first data URL which is converted to an external file. What I didn't follow up on, however, is going ahead and preloading the first font file for each other @font-face {
font-family: "Inter var";
font-weight: 100 900; /* stylelint-disable-line font-weight-notation */
font-style: normal;
font-display: swap;
src: url(./assets/fonts/inter/Inter-upright-var.woff2) format("woff2");
}
@font-face {
font-family: "Inter var";
font-weight: 100 900; /* stylelint-disable-line font-weight-notation */
font-style: italic;
font-display: swap;
src: url(./assets/fonts/inter/Inter-italic-var.woff2) format("woff2");
} So both of these still have And in the case of I think we need to identify: (1) what we can do generally for all themes to improve font performance, and (2) what we can target specifically for the core themes based on the specific fonts they're using. |
* If a data: URL has been replaced with an external file URL, then we add a font-display:swap to the @font-face | ||
* rule if one isn't already present. This prevents FO | ||
* If a data: URL has been replaced with an external file URL, then we add a font-display:optional to the @font-face | ||
* rule if one isn't already present. This prevents a flash of unstyled text (FOUT). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Er, I think I may have meant FOIT here.
Also, in the case of improving performance of fonts that are already external (e.g. Inter), we should consider contributing patches to WordPress core so that all sites will benefit, not just those running the AMP plugin. This would be part of the new Performance initiative. |
Just ran across this interesting anecdote as well: https://twitter.com/simonhearne/status/1456014210157191172 Granted, this is not relevant to AMP pages since the CSS is all inlined. But for contributing patches to WordPress core, this is indeed something to be wary of. |
Thank you @westonruter for your comments!
If a given theme uses one of Google Fonts, what if we'd add this functionality to AMP plugin:
That way we can save extra request(s) and load fonts locally, + use
Source: https://www.industrialempathy.com/posts/high-performance-web-font-loading/#fonts-and-cdns |
Yeah, that is an interesting area to explore. However, since these three core themes don't use Google Fonts I think that should be explored in another issue. There is also a problem currently with downloading font files from Google Fonts. It turns out that Google Fonts serves different versions of font files based on the user agent. So there can be fonts tuned specifically for macOS vs Windows vs iOS, for example. If the font files are downloaded, then platform-optimized versions won't be used.
In the general theme case, we'd also need to go a step further and determine if the font file being downloaded is suitable for being optional in the first place. Some fonts may be required for a theme to render as desired if a suitable fallback is not generally available. So I don't think we can automate the injection of |
To summarize: (1) in this PR we'd implement these general improvements for any themes:
Extra exception for core themes that uses (2) Additionally, we should propose below changes to core themes - Twenty Nineteen and Twenty Twenty:
|
I'm not sure. If something had
Taking a look again at the latest core themes and the fonts they use:
The The impact of this font can be seen on this page: https://wpzurich.ch/were-on-tv/ (via WordPress/twentynineteen#657)
Notice the wider spaces appearing after the links. These spaces are both Then for Twenty Twenty which uses Inter var, it currently has
with-and-without-inter-var.movIn my opinion, the text in the header is what uses Inter var. In my opinion, the text is so incredibly close to the system font stack that it doesn't warrant the extra layout shift. Therefore, I think So in summary, I think the best performance outcome would be for Twenty Nineteen, Twenty Twenty, and Twenty Twenty-One: make all the fonts Nevertheless, the logic here for preloading is actually added in vain because other themes actually do have fonts which must be loaded as soon as possible. In particular, any theme using the Genericons icon font needs to have that font preloaded since it necessarily has So here's what I suggest: add a new argument to --- a/includes/sanitizers/class-amp-style-sanitizer.php
+++ b/includes/sanitizers/class-amp-style-sanitizer.php
@@ -166,6 +166,11 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
'skip_tree_shaking' => false,
'allow_excessive_css' => false,
'transform_important_qualifiers' => true,
+ 'font_face_display_overrides' => [
+ 'NonBreakingSpaceOverride' => 'optional',
+ 'Inter var' => 'optional',
+ 'Genericons' => 'auto',
+ ],
];
/** Then when processing all the Note that this --- a/includes/sanitizers/class-amp-style-sanitizer.php
+++ b/includes/sanitizers/class-amp-style-sanitizer.php
@@ -1655,6 +1655,7 @@ private function get_parsed_stylesheet( $stylesheet, $options = [] ) {
'parsed_cache_variant',
'dynamic_element_selectors',
'transform_important_qualifiers',
+ 'font_face_display_overrides',
]
),
[ How does this sound? |
Implemented, ready for review @westonruter 👍 |
…improve-core-themes-fonts-experience * 'develop' of github.com:ampproject/amp-wp: (344 commits) Add missing translation for external link screen-reader-text Add vertical centering of theme screenshots Remove apparently obsolete .theme-browser .theme style rule Regenerate package-lock.json at lockfileVersion 1 Discontinue unhooking wp_post_preview_js() in favor of dev mode Mark script output by wp_comment_form_unfiltered_html_nonce() as being in dev mode Mark wp-polyfill as a dev mode script for paired browsing Update Gutenberg package dependencies Eliminate emoji handling altogether since most browsers now support natively Improve copy for Sandboxing Level drawer Set npm to be less than v7 in engines Downgrade to lockfileVersion 1 Refactor threaded comments handling to remove script if no comment form is on the page Update Gutenberg package dependencies Omit AMP runtime on sandbox levels 1 & 2 if not needed Ensure Sandboxing::finalize_document() is only called when experiment is enabled Move Sandboxing Level to Advanced Settings Revert package-lock.json lockfileVersion to 1 Update amphtml spec to 2110290545003 Add screen reader text in visit site button ...
…font file is a data: URL
&& | ||
1 <= count( $font_files ) | ||
) { | ||
$this->dom->links->addPreload( $font_files[0], RequestDestination::FONT ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good!
'font_face_display_overrides' => [ | ||
'NonBreakingSpaceOverride' => 'optional', | ||
'Inter var' => 'optional', | ||
'Genericons' => 'auto', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After considering this more, I think block
is the better value to explicitly say we don't want the font to render anything until it loads.
'Genericons' => 'auto', | |
'Genericons' => 'block', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I read, browsers treat auto
as “often similar” to block
, but there seems some uncertainty.
$properties = $ruleset->getRules( 'font-display' ); | ||
if ( isset( $properties[0] ) ) { | ||
$ruleset->removeRule( $properties[0] ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$properties = $ruleset->getRules( 'font-display' ); | |
if ( isset( $properties[0] ) ) { | |
$ruleset->removeRule( $properties[0] ); | |
} | |
foreach ( $ruleset->getRules( 'font-display' ) as $rule ) { | |
$ruleset->removeRule( $rule ); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or rather just:
$properties = $ruleset->getRules( 'font-display' ); | |
if ( isset( $properties[0] ) ) { | |
$ruleset->removeRule( $properties[0] ); | |
} | |
$ruleset->removeRule( 'font-display' ); |
$properties = $ruleset->getRules( 'font-display' ); | ||
if ( | ||
( | ||
( isset( $properties[0] ) && in_array( $properties[0]->getValue(), [ 'auto', 'block', 'swap' ], true ) ) | ||
|| | ||
( ! isset( $properties[0] ) ) // Defaults to 'auto', hence should be preloaded as well. | ||
) | ||
&& | ||
1 <= count( $font_files ) | ||
) { | ||
$this->dom->links->addPreload( $font_files[0], RequestDestination::FONT ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just read Prevent layout shifting and flashes of invisible text (FOIT) by preloading optional fonts which recommends adding preload links to optional fonts as well, although it says:
Although it is not necessary to preload an optional font, it greatly improves the chance for it to load before the first render cycle, especially if it is not yet stored in the browser's cache.
Nevertheless, adding a preload will introduce network congestion as resources compete for bandwidth. So if the font truly is optional, IMO it seems preferable to not preload it.
*/ | ||
if ( $converted_count && 0 === count( $ruleset->getRules( 'font-display' ) ) ) { | ||
if ( isset( $font_family ) && in_array( $font_family, array_keys( $this->args['font_face_display_overrides'] ), true ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more condition is missing here it seems, and that is if the first font URL in the list is a data:
URL, then we must not add a preload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Failing test case added in 7c6afe7.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed with e02b96a
$font_file = plugin_dir_url( AMP__FILE__ ) . "assets/fonts/$font_filename"; | ||
$data_url->getURL()->setString( $font_file ); | ||
if ( 'inline' === $first_src_type ) { | ||
$first_src_type = 'file'; | ||
$font_file = $font_file; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Humm, $font_file = $font_file
?
I think this can be just:
$font_file = plugin_dir_url( AMP__FILE__ ) . "assets/fonts/$font_filename"; | |
$data_url->getURL()->setString( $font_file ); | |
if ( 'inline' === $first_src_type ) { | |
$first_src_type = 'file'; | |
$font_file = $font_file; | |
} | |
$font_file = plugin_dir_url( AMP__FILE__ ) . "assets/fonts/$font_filename"; | |
$data_url->getURL()->setString( $font_file ); | |
$first_src_type = 'file'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 6c16113
&& | ||
! empty( $font_file ) | ||
) { | ||
$this->dom->links->addPreload( $font_file, RequestDestination::FONT ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized this can't be done bere because when a parsed stylesheet is cached, this logic won't run. This opens a can of worms, as it means we have to collect the font URLs to preload and then pass them back all the way up to where we store the cached stylesheet, and then we need to add the preloading when we add the processed stylesheet to the document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bartoszgadomski Please review the additional changes. @milindmore22 This is ready to review as well. |
@bartoszgadomski When I tried this PR, I didn't see font being preloaded |
Hi @milindmore22! Fonts used in Twenty Nineteen, Twenty Twenty and Twenty Twenty One themes are not preloaded, as we decided to preload fonts that uses The only change we do for core themes is to override the To see preloading in action, you can use any theme that load custom fonts from local filesystem, i.e. Maxwell: https://wordpress.org/themes/maxwell/; this theme initially request fonts from Google Fonts, then save font files in local filesystem and create a CSS file with fonts definitions where all fonts have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bartoszgadomski looks good to me 👍🏼
Summary
Fixes #6036
I checked a test website against the Page Experience tool with three default WordPress themes (Twenty Twenty-One, Twenty Twenty and Twenty Nineteen) and I can't see any suggestions under "take action", nor any issues reported related to fonts (eg. this: https://user-images.githubusercontent.com/1132541/113437622-92497600-9404-11eb-922f-88e7ab72a890.png).
Fonts in default WordPress themes
Since we focus on three recent themes (as per #6036 (comment)):
Twenty Twenty-One theme uses system font stack, custom font files are not loaded:
Twenty Twenty uses "Inter var" font which is loaded from the local source file + "NonBreakingSpaceOverride" loaded inline in css file through "data:application/font-woff2":
Twenty Nineteen uses "Hoefler Text" with "NonBreakingSpaceOverride" font for body (loaded from the local source file):
Changes in this PR
@font-face
block to have thefont-display:optional
if thefont-display
rule is missing and a data: URL has been replaced with an external file URL (use case: force theNonBreakingSpaceOverride
font to havefont-display:optional
in "Twenty Twenty" and "Twenty Nineteen" themes)Checklist