Make WordPress Core

Opened 2 months ago

Closed 5 weeks ago

Last modified 5 weeks ago

#64361 closed enhancement (fixed)

Leverage HTML API to implement block template skip link

Reported by: slieptsov's profile Slieptsov Owned by: westonruter's profile westonruter
Milestone: 7.0 Priority: normal
Severity: normal Version: 5.8
Component: Themes Keywords: has-patch has-unit-tests
Focuses: accessibility, javascript, performance Cc:

Description (last modified by westonruter)

When the_block_template_skip_link() (now wp_enqueue_block_template_skip_link()) was introduced in #53176 there was no HTML API which necessitated JavaScript to add the skip link. This can now be revisited to leverage WP_HTML_Tag_Processor to add the skip link.

The inline JavaScript can then be eliminated.

The CSS should also be minified.


Original description:

Can skip link CSS/JS be minified?

https://github.com/WordPress/wordpress-develop/blob/0b50baa5d164bedd17d3ae9e2eafc6286d6255b6/src/wp-includes/theme-templates.php#L172

Change History (25)

#1 @westonruter
2 months ago

  • Component changed from General to Themes
  • Description modified (diff)
  • Focuses javascript performance added
  • Milestone changed from Awaiting Review to 7.0
  • Summary changed from Skip Link Minification to Leverage HTML API to implement block template skip link
  • Version set to 5.8

What's more is that I don't think this needs to use JavaScript at all anymore. The HTML API can be used to make the markup modifications. This could be done in get_the_block_template_html().

This was first introduced in r51003 (#53176) and was revisited in r56932 (#59505).

#2 @westonruter
2 months ago

  • Description modified (diff)

#3 @westonruter
2 months ago

  • Keywords needs-patch added

#4 @anton7249
2 months ago

Ok, got it, thanks.

#5 @whiteshadow01
2 months ago

The HTML API doesn't allow inserting elements into the DOM, only modifying the existing ones. How do we plan on inserting a new anchor tag as we are doing it in the javascript right now using the HTML API?

#6 @westonruter
2 months ago

@whiteshadow01 It does, but not via the public interface. You can see an example of this in wp_hoist_late_printed_styles(). See these lines.

#7 @whiteshadow01
2 months ago

Thanks @westonruter

This ticket was mentioned in PR #10676 on WordPress/wordpress-develop by @rutviksavsani.


7 weeks ago
#8

  • Keywords has-patch has-unit-tests added; needs-patch removed

Move the block template skip link from client-side injection to server-side HTML processing using the HTML API, while keeping the existing accessibility behaviour and minifying the CSS.

## What this changes

  • Adds _block_template_skip_link_markup() to process the block template HTML:
    • Ensures the first <main> has an id (adds wp--skip-link--target if missing).
    • Inserts a single <a id="wp-skip-link" class="skip-link screen-reader-text"> before .wp-site-blocks.
    • Skips insertion when there is no <main> or when a skip link already exists.


  • Updates get_the_block_template_html() to run the rendered template through the new helper.
  • Refactors wp_enqueue_block_template_skip_link() to:
    • Remove the old JS-based DOM injection.
    • Minify the skip-link CSS as an inline style.
  • Preserves backward compatibility gating via the_block_template_skip_link and block-templates theme support.
  • Add tests for the new function as well.

## Ticket
Trac ticket: https://core.trac.wordpress.org/ticket/64361

#9 @westonruter
6 weeks ago

  • Owner set to westonruter
  • Status changed from new to reviewing

@westonruter commented on PR #10676:


6 weeks ago
#10

@rutviksavsani Could you address that great feedback from Dennis?

@rutviksavsani commented on PR #10676:


6 weeks ago
#11

@westonruter @dmsnell I have implemented the suggestions with best of my understandings, Let me know if I missed anything.

@rutviksavsani commented on PR #10676:


6 weeks ago
#12

@dmsnell I have added the changes and looks good to me with what we wanted to achieve. Let me know if it looks good to you.

@westonruter commented on PR #10676:


6 weeks ago
#13

I'm confused why the Tests_Template::test_wp_hoist_late_printed_styles are failing. They pass for my locally.

I'm also seeing a problem on the frontend with how the sourceURL comment is being constructed for the new style. I see:

/*# sourceURL=http://wp-includes/css/wp-block-template-skip-link.css */

@rutviksavsani commented on PR #10676:


6 weeks ago
#14

I'm confused why the Tests_Template::test_wp_hoist_late_printed_styles are failing. They pass for my locally.

I'm also seeing a problem on the frontend with how the sourceURL comment is being constructed for the new style. I see:

/*# sourceURL=http://wp-includes/css/wp-block-template-skip-link.css */

Look's okay to me on local as well for the sourceURL. Tests failure I am not completely sure of on what it can be but it's failing on other PRs on the repo as well.

https://github.com/user-attachments/assets/166d93df-1667-4edd-aa2d-03ae19c4a7a0

@westonruter commented on PR #10676:


6 weeks ago
#15

Tests failure I am not completely sure of on what it can be but it's failing on other PRs on the repo as well.

I'm not seeing test failures for the latest commit in trunk: https://github.com/WordPress/wordpress-develop/actions/runs/20841211620/job/59875830100

No test failures in this PR either: https://github.com/WordPress/wordpress-develop/pull/10694

Something is wrong with that Tests_Template::test_wp_hoist_late_printed_styles test. I'll have to debug it tomorrow.

@westonruter commented on PR #10676:


6 weeks ago
#16

Look's okay to me on local as well for the sourceURL.

I fixed this at least in 350b57c.

This now matches the existing code better:

https://github.com/WordPress/wordpress-develop/blob/76ca7726a951f35e3693b54f65c376ecd399df37/src/wp-includes/script-loader.php#L1639-L1645

I now see:

/*# sourceURL=/wp-includes/css/wp-block-template-skip-link.css */

@westonruter commented on PR #10676:


5 weeks ago
#17

Something is wrong with that Tests_Template::test_wp_hoist_late_printed_styles test. I'll have to debug it tomorrow.

Fixed in 61711e2716742d44a86c5b26477cad4d74edac83. The problem is that some other test in the test suite is not cleaning up after itself. When the test runs in isolation, it's not a block template. But some other test is leaving the block template status in place. Previously the code was already ignoring wp-block-template-skip-link-inline-css for this reason. It was failing for wp-block-template-skip-link-css because now since the stylesheet is actually in the filesystem, it can be conditionally _not_ inlined, as is being done in the tests.

#18 @sabernhardt
5 weeks ago

  • Focuses accessibility added

@westonruter commented on PR #10676:


5 weeks ago
#19

I cURL'ed the Sample Page before and after the change and here's the diff:

  • .html

    old new  
    24782478        width: auto;
    24792479        z-index: 100000;
    24802480      }
    2481       /*# sourceURL=wp-block-template-skip-link-inline-css */
     2481
     2482      /*# sourceURL=/wp-includes/css/wp-block-template-skip-link.css */
    24822483    </style>
    24832484    <style id="twentytwentyfive-style-inline-css">
    24842485      /*
     
    26282629  <body
    26292630    class="wp-singular page-template-default page page-id-2 wp-embed-responsive wp-theme-twentytwentyfive"
    26302631  >
     2632    <a
     2633      class="skip-link screen-reader-text"
     2634      id="wp-skip-link"
     2635      href="#wp--skip-link--target"
     2636      >Skip to content</a
     2637    >
    26312638    <div class="wp-site-blocks">
    26322639      <header class="wp-block-template-part">
    26332640        <div
     
    27652772      </header>
    27662773
    27672774      <main
     2775        id="wp--skip-link--target"
    27682776        class="wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained"
    27692777        style="margin-top: var(--wp--preset--spacing--60)"
    27702778      >
     
    30133021      fetchpriority="low"
    30143022      data-wp-router-options='{"loadOnClientNavigation":true}'
    30153023    ></script>
    3016     <script id="wp-block-template-skip-link-js-after">
    3017       (function () {
    3018         var skipLinkTarget = document.querySelector("main"),
    3019           sibling,
    3020           skipLinkTargetID,
    3021           skipLink;
    3022 
    3023         // Early exit if a skip-link target can't be located.
    3024         if (!skipLinkTarget) {
    3025           return;
    3026         }
    3027 
    3028         /*
    3029          * Get the site wrapper.
    3030          * The skip-link will be injected in the beginning of it.
    3031          */
    3032         sibling = document.querySelector(".wp-site-blocks");
    3033 
    3034         // Early exit if the root element was not found.
    3035         if (!sibling) {
    3036           return;
    3037         }
    3038 
    3039         // Get the skip-link target's ID, and generate one if it doesn't exist.
    3040         skipLinkTargetID = skipLinkTarget.id;
    3041         if (!skipLinkTargetID) {
    3042           skipLinkTargetID = "wp--skip-link--target";
    3043           skipLinkTarget.id = skipLinkTargetID;
    3044         }
    3045 
    3046         // Create the skip link.
    3047         skipLink = document.createElement("a");
    3048         skipLink.classList.add("skip-link", "screen-reader-text");
    3049         skipLink.id = "wp-skip-link";
    3050         skipLink.href = "#" + skipLinkTargetID;
    3051         skipLink.innerText = "Skip to content";
    3052 
    3053         // Inject the skip link.
    3054         sibling.parentElement.insertBefore(skipLink, sibling);
    3055       })();
    3056 
    3057       //# sourceURL=wp-block-template-skip-link-js-after
    3058     </script>
    30593024    <script id="wp-emoji-settings" type="application/json">
    30603025      {
    30613026        "baseUrl": "https://s.w.org/images/core/emoji/17.0.2/72x72/",

I just realized another benefit to doing this: the skip link works even when JavaScript is turned off.

@westonruter commented on PR #10676:


5 weeks ago
#20

I just noticed that RTL versions of the CSS files are being generated. Here are all the versions now after a build:

  • src/wp-includes/css/wp-block-template-skip-link-rtl.css
  • src/wp-includes/css/wp-block-template-skip-link-rtl.min.css
  • src/wp-includes/css/wp-block-template-skip-link.css
  • src/wp-includes/css/wp-block-template-skip-link.min.css

Previously there was no RTL variation in the CSS. So maybe this was a bug. But it doesn't seem the RTL version is currently getting registered so it is being unused. We need to double check that.

@westonruter commented on PR #10676:


5 weeks ago
#21

Indeed, this is an issue. The skip link is not getting positioned on the right as expected in Arabic:

English | Arabic

--

https://github.com/user-attachments/assets/465f52a3-f4b2-449d-8b52-31b6d8c06a89 | https://github.com/user-attachments/assets/94f8d776-aa89-43fe-9043-397ea27c82e1

@westonruter commented on PR #10676:


5 weeks ago
#22

OK, with 357ea8ac2177aa34c4b436b2d47ac7132303975a I now get the skip link positioned on the right as expected, but only when styles_inline_size_limit is zero. I then get this stylesheet in the HTML:

<link rel='stylesheet' id='wp-block-template-skip-link-rtl-css' href='http://localhost:8000/wp-includes/css/wp-block-template-skip-link-rtl.min.css?ver=7.0-alpha-61215-src' media='all' />

But when SCRIPT_DEBUG is off and styles_inline_size_limit is at the default of 40K, I see:

<style id="wp-block-template-skip-link-inline-css">
.skip-link.screen-reader-text {
        border: 0;
        clip-path: inset(50%);
        height: 1px;
        margin: -1px;
        overflow: hidden;
        padding: 0;
        position: absolute !important;
        width: 1px;
        word-wrap: normal !important;
}

.skip-link.screen-reader-text:focus {
        background-color: #eee;
        clip-path: none;
        color: #444;
        display: block;
        font-size: 1em;
        height: auto;
        left: 5px;
        line-height: normal;
        padding: 15px 23px 14px;
        text-decoration: none;
        top: 5px;
        width: auto;
        z-index: 100000;
}

/*# sourceURL=/wp-includes/css/wp-block-template-skip-link.css */
</style>

Note the erroneous left: 5px. It should have right: 5px; which I can see in wp-block-template-skip-link-rtl.css.

It turns out this is a known issue which doesn't have to be fixed in this PR: Core-61625. It's already something on my radar, so I'll follow up on that now that I've seen another example of how to reproduce it. At least it's fixed when stylesheet inlining is disabled.

@westonruter commented on PR #10676:


5 weeks ago
#23

The diff above in https://github.com/WordPress/wordpress-develop/pull/10676#issuecomment-3733991892 was with prettier applied. Here's the diff with SCRIPT_DEBUG off and no prettier formatting. Notice how the CSS is now minified:

  • .html

    old new  
    9797/*# sourceURL=core-block-supports-inline-css */
    9898</style>
    9999<style id="wp-block-template-skip-link-inline-css">
    100 
    101                 .skip-link.screen-reader-text {
    102                         border: 0;
    103                         clip-path: inset(50%);
    104                         height: 1px;
    105                         margin: -1px;
    106                         overflow: hidden;
    107                         padding: 0;
    108                         position: absolute !important;
    109                         width: 1px;
    110                         word-wrap: normal !important;
    111                 }
    112 
    113                 .skip-link.screen-reader-text:focus {
    114                         background-color: #eee;
    115                         clip-path: none;
    116                         color: #444;
    117                         display: block;
    118                         font-size: 1em;
    119                         height: auto;
    120                         left: 5px;
    121                         line-height: normal;
    122                         padding: 15px 23px 14px;
    123                         text-decoration: none;
    124                         top: 5px;
    125                         width: auto;
    126                         z-index: 100000;
    127                 }
    128 /*# sourceURL=wp-block-template-skip-link-inline-css */
     100/*! This file is auto-generated */
     101.skip-link.screen-reader-text{border:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute!important;width:1px;word-wrap:normal!important}.skip-link.screen-reader-text:focus{background-color:#eee;clip-path:none;color:#444;display:block;font-size:1em;height:auto;left:5px;line-height:normal;padding:15px 23px 14px;text-decoration:none;top:5px;width:auto;z-index:100000}
     102/*# sourceURL=/wp-includes/css/wp-block-template-skip-link.min.css */
    129103</style>
    130104<style id="twentytwentyfive-style-inline-css">
    131105a{text-decoration-thickness:1px!important;text-underline-offset:.1em}:where(.wp-site-blocks :focus){outline-width:2px;outline-style:solid}.wp-block-navigation .wp-block-navigation-submenu .wp-block-navigation-item:not(:last-child){margin-bottom:3px}.wp-block-navigation .wp-block-navigation-item .wp-block-navigation-item__content{outline-offset:4px}.wp-block-navigation .wp-block-navigation-item ul.wp-block-navigation__submenu-container .wp-block-navigation-item__content{outline-offset:0}blockquote,caption,figcaption,h1,h2,h3,h4,h5,h6,p{text-wrap:pretty}.more-link{display:block}:where(pre){overflow-x:auto}
     
    147121
    148122<body class="wp-singular page-template-default page page-id-2 wp-embed-responsive wp-theme-twentytwentyfive">
    149123
    150 <div class="wp-site-blocks"><header class="wp-block-template-part">
     124<a class="skip-link screen-reader-text" id="wp-skip-link" href="#wp--skip-link--target">Skip to content</a><div class="wp-site-blocks"><header class="wp-block-template-part">
    151125<div class="wp-block-group alignfull is-layout-flow wp-block-group-is-layout-flow">
    152126       
    153127        <div class="wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained">
     
    198172</header>
    199173
    200174
    201 <main class="wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="margin-top:var(--wp--preset--spacing--60)">
     175<main id="wp--skip-link--target" class="wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="margin-top:var(--wp--preset--spacing--60)">
    202176       
    203177        <div class="wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    204178               
     
    298272{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/twentytwentyfive/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]}
    299273</script>
    300274<script type="module" src="http://localhost:8000/wp-includes/js/dist/script-modules/block-library/navigation/view.min.js?ver=7437ed5c45ee57daf02c" id="@wordpress/block-library/navigation/view-js-module" fetchpriority="low" data-wp-router-options="{&quot;loadOnClientNavigation&quot;:true}"></script>
    301 <script id="wp-block-template-skip-link-js-after">
    302         ( function() {
    303                 var skipLinkTarget = document.querySelector( 'main' ),
    304                         sibling,
    305                         skipLinkTargetID,
    306                         skipLink;
    307 
    308                 // Early exit if a skip-link target can't be located.
    309                 if ( ! skipLinkTarget ) {
    310                         return;
    311                 }
    312 
    313                 /*
    314                  * Get the site wrapper.
    315                  * The skip-link will be injected in the beginning of it.
    316                  */
    317                 sibling = document.querySelector( '.wp-site-blocks' );
    318 
    319                 // Early exit if the root element was not found.
    320                 if ( ! sibling ) {
    321                         return;
    322                 }
    323 
    324                 // Get the skip-link target's ID, and generate one if it doesn't exist.
    325                 skipLinkTargetID = skipLinkTarget.id;
    326                 if ( ! skipLinkTargetID ) {
    327                         skipLinkTargetID = 'wp--skip-link--target';
    328                         skipLinkTarget.id = skipLinkTargetID;
    329                 }
    330 
    331                 // Create the skip link.
    332                 skipLink = document.createElement( 'a' );
    333                 skipLink.classList.add( 'skip-link', 'screen-reader-text' );
    334                 skipLink.id = 'wp-skip-link';
    335                 skipLink.href = '#' + skipLinkTargetID;
    336                 skipLink.innerText = 'Skip to content';
    337 
    338                 // Inject the skip link.
    339                 sibling.parentElement.insertBefore( skipLink, sibling );
    340         }() );
    341        
    342 //# sourceURL=wp-block-template-skip-link-js-after
    343 </script>
    344275<script id="wp-emoji-settings" type="application/json">
    345276{"baseUrl":"https://s.w.org/images/core/emoji/17.0.2/72x72/","ext":".png","svgUrl":"https://s.w.org/images/core/emoji/17.0.2/svg/","svgExt":".svg","source":{"concatemoji":"http://localhost:8000/wp-includes/js/wp-emoji-release.min.js?ver=7.0-alpha-61215-src"}}
    346277</script>

#24 @westonruter
5 weeks ago

  • Resolution set to fixed
  • Status changed from reviewing to closed

In 61469:

Themes: Use WP_HTML_Tag_Processor to insert the block template skip link instead of JavaScript.

  • The skip link now works when JavaScript is turned off.
  • By removing the script, the amount of JavaScript sent to the client is reduced for a very marginal performance improvement.
  • A new wp-block-template-skip-link stylesheet is registered, with minification and path data for inlining.
  • The CSS for the skip link now has an RTL version generated, although it is not yet served when the styles are inlined. See #61625.
  • The wp_enqueue_block_template_skip_link() function now exclusively enqueues the stylesheet since the script is removed.
  • For backwards-compatibility, the skip link will continue to be omitted if the_block_template_skip_link() is unhooked from the wp_footer action or wp_enqueue_block_template_skip_link() is unhooked from wp_enqueue_scripts.

Developed in https://github.com/WordPress/wordpress-develop/pull/10676

Follow-up to [56932], [51003].

Props rutviksavsani, westonruter, dmsnell, whiteshadow01, Slieptsov.
See #59505, #53176.
Fixes #64361.

@rutviksavsani commented on PR #10676:


5 weeks ago
#25

Thanks @dmsnell for the final review and Thanks @westonruter for taking it over the line with all the necessary checks.

Note: See TracTickets for help on using tickets.