[wp-trac] [WordPress Trac] #46370: A proposal for creating wp_enqueue_font()

WordPress Trac noreply at wordpress.org
Thu Feb 28 10:23:06 UTC 2019


#46370: A proposal for creating wp_enqueue_font()
-----------------------------+-----------------------------
 Reporter:  jonoaldersonwp   |      Owner:  (none)
     Type:  feature request  |     Status:  new
 Priority:  normal           |  Milestone:  Awaiting Review
Component:  General          |    Version:
 Severity:  normal           |   Keywords:
  Focuses:  performance      |
-----------------------------+-----------------------------
 = The problem
 Loading custom fonts in a sub-optimal manner can cause significant
 performance and privacy issues. Anecdotally, inefficient font loading is
 frequently one of the biggest performance bottlenecks in many WordPress
 themes/sites.

 This is, in part, because there hasn’t been a ‘best practice’ approach;
 there’s no ‘standard’ way of adding a font to a theme, and ensuring that
 it’s loaded in a performance-friendly manner.

 Loading resources from Google Fonts has been the generally-accepted
 workaround to this, but that’s come with privacy concerns, and, poor
 implementations (no DNS prefetching, multiple requests, loading unneeded
 localisations/weights, etc).

 Even our own sites and setups ([https://wordpress.org/ wordpress.org], the
 WordPress admin area, Gutenberg, and some of our [https://wp-
 themes.com/twentyseventeen/ core themes]) fall afoul of these issues.

 If we’re serious about WordPress becoming a fast platform, we can’t rely
 on theme developers to add and manage fonts without providing a framework
 to support them.

 == Why now?
 It’s only recently that CSS and performance technologies/methodologies
 have matured sufficiently to define a ‘right way’ to load fonts.

 In particular:
 - Best practices for defining and loading fonts via CSS are now well-
 established, stable, and see broad usage elsewhere.

 - The increasing adoption of of HTTP/2 means that localising assets can
 (in many cases) be faster than loading them from remote sources.

 - Specifically,
 [https://developers.google.com/web/fundamentals/performance/optimizing-
 content-efficiency/webfont-optimization Google's Web Fundamentals
 documentation] (which much of this spec is directly lifted and adapted
 from) describes a stable, compatible, and plug-and-play approach to
 loading fonts.

 == The vision
 Theme developers shouldn’t have to manage their own font loading and
 optimisation. The registering and loading of fonts should be managed in
 the same way that we manage CSS and JavaScript - via abstraction, through
 an enqueue system.

 Whilst fonts have more moving parts than script and style files, I believe
 that `wp_enqueue_font()` could become a robust, standardised way of adding
 custom fonts to themes without radical effort or change - with all of the
 advantages we’re used to from abstracting JS/CSS (conditional logic,
 dependency management, versioning/cache-busting, consolidation of
 duplicates, etc).

 A move to an enqueue-based approach may also provide plugin developers
 with hooks to intercept the default behaviours, and to modify the output.
 E.g., to utilise the
 [https://developers.google.com/web/fundamentals/performance/optimizing-
 content-efficiency/webfont-optimization#the_font_loading_api font loading
 API] rather than outputting CSS. This provides a huge opportunity for
 caching and performance-optimising plugins to speed up WordPress
 sites/themes.

 = The ‘Web Fundamentals’ approach
 The [https://developers.google.com/web/fundamentals/performance
 /optimizing-content-efficiency/webfont-optimization Web Fundamentals
 documentation] provides an example of an optimal CSS structure for loading
 fonts. E.g.,

 {{{
 @font-face {
   font-family: 'Awesome Font';
   font-style: normal;
   font-weight: 400;
   src: local('Awesome Font'),
        url('/fonts/awesome-l.woff2') format('woff2'),
        url('/fonts/awesome-l.woff') format('woff'),
        url('/fonts/awesome-l.ttf') format('truetype'),
        url('/fonts/awesome-l.eot') format('embedded-opentype');
   unicode-range: U+000-5FF; /* Latin glyphs */
 }

 @font-face {
   font-family: 'Awesome Font';
   font-style: normal;
   font-weight: 700;
   src: local('Awesome Font'),
        url('/fonts/awesome-l-700.woff2') format('woff2'),
        url('/fonts/awesome-l-700.woff') format('woff'),
        url('/fonts/awesome-l-700.ttf') format('truetype'),
        url('/fonts/awesome-l-700.eot') format('embedded-opentype');
   unicode-range: U+000-5FF; /* Latin glyphs */
 }
 }}}

 In this example, a single font (‘Awesome Font’) is being loaded, in two
 different weights (`400` and `700`). For each of those weights, multiple
 formats are provided, prioritised in such a way that considers and is
 optimized for browser support. The approach for different styles (e.g.,
 ''italic''), and all other variations, follow this pattern.

 In addition to using an optimal CSS approach, the documentation recommends
 using a `<link rel="preload">` directive (or equivalents JavaScript-based
 approaches). This prevents the browser from having to wait until the
 render tree is complete before downloading font resources.

 Our aim is to enable all themes/plugins to achieve this approach and to
 output this code (the CSS, paired with a preload directive) without
 requiring developers to explicitly craft and maintain this code.

 = A spec
 The process of enqueuing a font should allow a developer to specify the
 name of the font, and to provide an optional level of detail/control over
 specific aspects (versions, weights, file locations). Where
 details/specifics aren’t provided, sensible fallbacks and defaults should
 be assumed.

 I suggest the following structure:

 {{{
 wp_enqueue_font(
   string $handle,                    // The name of the font
   string|array|bool $src = false,    // The source(s) of the font files
   string $style          = 'normal', // The style of the font
   int $weight            = 400,      // The weight of the font
   string $display        = 'auto'    // Display/swap behaviour
   string $range          = false,    // A unicode range value
   string|bool $external  = false,    // Externalise the CSS file/code
   bool $preload          = true      // Should the resource be preloaded
   bool $in_footer        = false     // Output the CSS/file in the footer
 )
 }}}

 == Starting from simplicity
 A simple implementation of this, which omits much of the detail and
 utilises default behaviours, might look something like:

 {{{
 wp_enqueue_font(
   'Awesome Font',
   '/fonts/awesome-font-400.woff2',
   '', // $style
   '', // $weight
   '', // $display
   '', // $range
   '', // $external
   '', // $preload
   ''  // $in_footer
 )
 }}}

 In this example, we’ve only specified the bare minimum information
 required to enqueue a font - a name and a location. But there’s enough
 here that we can generate the following CSS:

 {{{
 @font-face {
   font-family: 'Awesome Font';
   font-style: normal;
   font-weight: 400;
   src: local('Awesome Font'),
        url('/fonts/awesome-font-400.woff2') format('woff2'),
 }
 }}}

 - ''NOTE: If no `$src` value is defined, only a local font should be
 referenced (via `$handle`).''

 == Supporting font registration
 As with scripts and styles, we should allow for registration and enqueuing
 as separate  processes. I.e., `wp_register_font` should accept the same
 arguments as `wp_enqueue_font`, and then, `wp_enqueue_font` can simply
 reference a registered `$handle`.

 == Definitions
 === $src
 Whilst our `$src` variable can accept a simple string, more advanced usage
 should specify multiple versions and their formats. That looks like this
 (maintaining the rest of our ‘simplest implementation’ approach):

 {{{
 wp_enqueue_font(
   'Awesome Font',
   array(
     '/fonts/awesome-font-400.woff2' => 'woff2',
     '/fonts/awesome-font-400.woff'  => 'woff',
     '/fonts/awesome-font-400.ttf'   => 'truetype',
     '/fonts/awesome-font-400.eot'   => 'embedded-opentype'
   ),
   '', // $style
   '', // $weight
   '', // $display
   '', // $range
   '', // $external
   '', // $preload
   ''  // $in_footer
 )
 }}}

 - ''NOTE: If a ''type'' is invalid, the declaration should be ignored.''

 - ''NOTE: Data URLs (e.g., `data:application/font-
 woff2;charset=utf-8;base64[...]`) may be provided instead of file paths.''

 === $external
 When the `$external` flag is false (which is the default behaviour), the
 generated CSS should be output in the <head>, via `wp_add_inline_style()`
 (unless `$in_footer` is set to `true`, in which case, the code should be
 output in a generated `<style>` tag hooked into `wp_footer`).

 When set to `true`, we should wrap `wp_enqueue_style` and call a
 procedurally generated CSS file containing the relevant CSS (with a
 filename of `font-$handle-$style-$weight.css`).

 Alternatively, the value may be a string which represents a filepath,
 where the CSS ‘file’ should be accessible from (via a rewrite rule).

 - ''NOTE: When `$external` is true, we should check that the composite
 handle for the font can be converted to a unique stylesheet handle. If
 such a stylesheet handle already exists, we should de-enqueue the existing
 stylesheet and enqueue the new one.''
 - ''NOTE: Default location of generated CSS file TBD.''
 - ''NOTE: Invalid string values should fall back to the default
 behaviour.''

 === $preload
 When the `$preload` flag is true (which is the default behaviour),
 `wp_resource_hints`* should be filtered to add a `<link rel="preload">`
 directive for the most modern format font file in the $src array (usually
 woff2).

 If `$external` is set to true, the associated CSS file should also be
 preloaded.

 - ''NOTE: The preload tag is currently unsupported by `wp_resource_hints`,
 and it's expected that another (similar) function may be introduced to
 support this. Discussion [https://core.trac.wordpress.org/ticket/42438
 here].''

 = Other considerations
 == Unique handles
 Unlike a style/script, the `$handle` is insufficient to represent a unique
 font (for the purposes of identification, registration, and conflict
 management). For fonts, we should consider the combination of the
 `$handle`, `$style` and `$weight` to represent a unique instance.

 - ''NOTE: If there are non-unique instances of a font, the last-enqueued
 version should take priority.''

 == Invalid values & minimum requirements
 Invalid or malformed values for parameters with constrained values (e.g.,
 `$style`, `$weight`, `$display`) should fall back to their defaults.

 Invalid or malformed values for parameters without constrained values
 (e.g., `$range`, `$src`) should be ignored.

 If this validation results in there being no values for `$handle` and
 `$src` (which represents the bare minimum requirements), no CSS should be
 generated/output.

 = Next steps
 There are lots of moving parts on this one, but I’m hoping that most of it
 is fairly straightforward. I’d love some feedback on (any gaps in / issues
 with) the spec.

 I’m anticipating that we'll need a bunch of discussion, iteration,
 exploration and definition before it makes sense to start authoring any
 code on this one, but that said, it’d be super to see some of this start
 to take shape.

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/46370>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list