Changes to the .screen-reader-text class in WordPress 6.8

The screen-reader-text CSSCSS Cascading Style Sheets. class is a small bit of CSS used in WordPress to hide text visually but still make it available to assistive technologies, screen readers, and any other software reading a page.

Given poor browser support for the clip-path property, the class has supported the deprecated clip property longer than it probably needed to. WordPress 4.9 did finally add support for clip-path, which now has wide support without prefixes across browsers.

WordPress 6.8 takes two more steps to modernize the class: it removes the clip property and the prefixed -webkit-clip-path property. Worth noting this change applies to the CSS class used in the WordPress adminadmin (and super admin) pages and across all bundled themes.

Here’s the CSS class from WordPress 4.9:

.screen-reader-text {
	border: 0;
	clip: rect(1px, 1px, 1px, 1px);
	-webkit-clip-path: inset(50%);
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	word-wrap: normal !important;
}

And here’s the new CSS class for WordPress 6.8:

.screen-reader-text {
	border: 0;
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	word-wrap: normal !important;
}

The only changes are the removal of the clip property and -webkit-clip-path.

In most cases this small change shouldn’t require any update to plugins and themes. But be aware of one case: when the screen-reader-text CSS class is used to dynamically reveal text. In a few cases, WordPress itself reveals some visually hidden text. For example, when there’s no JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a user’s browser. https://www.javascript.com/. support or on small screens, screen-reader-text gets reset to make the visually hidden text visible again:

.no-js .some-element .screen-reader-text {
	position: static;
	clip-path: none;
	width: auto;
	height: auto;
	margin: 0;
}

If you make an update to a similar CSS technique in your pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party or theme admin pages, don’t forget to remove the clip property from the associated reset CSS.

For more details, see the related changeset and Trac ticket.

Thanks to @marybaum and @audrasjb for proofreading.

#6-8, #dev-notes, #dev-notes-6-8

More efficient block type registration in 6.8

WordPress 6.8 introduces a new function wp_register_block_types_from_metadata_collection(), which allows plugins to register multiple blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. types with a single function call.

This function expands on the foundational capabilities of the wp_register_block_metadata_collection() function, which was introduced in WordPress 6.7 to improve performance.

Context

To recap the relevant functionality added in WordPress 6.7:

Plugins can now optionally register a PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher “manifest” file, which includes all the metadata for their block types. For any block type that is being registered, WordPress CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. will now check whether such a manifest file is present covering the block type, and if so, it will use the data from the manifest file instead of reading and parsing the block type’s block.jsonJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML. file directly.

Since the blocks manifest file includes all the block type names, a logical next step after adding support for such a file is to make the requirement for individual block type registration calls obsolete. This is what the new  wp_register_block_types_from_metadata_collection() function implements.

Benefits

By using the new function, you no longer need to add individual register_block_type() calls for every block type that you include in your pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party. This improves developer experience, especially when using the latest block development best practices where the block.json file is used as the sole entrypoint for both PHP (server-side) and JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a user’s browser. https://www.javascript.com/. (client-side). Adding a new block type to an existing plugin is now possible by creating the block’s directory and working exclusively within that directory. You no longer need to remember to register the block type somewhere else in the PHP codebase of the surrounding plugin.

Example

Let’s say you have a plugin with 5 custom block types: “accordion”, “carousel”, “carousel-slide”, “dialog”, and “icon-button”. At the present, this means your plugin’s PHP code may look like this:

$block_types = array( 'accordion', 'carousel', 'carousel-slide', 'dialog', 'icon-button' );
foreach ( $block_types as $block_type ) {
	register_block_type( __DIR__ . "/build/{$block_type}" );
}

With WordPress 6.8, you can now use the wp_register_block_types_from_metadata_collection() function to eliminate the need for the list of block types in the PHP code so that all block types are recognized and registered automatically.

To do that, you need to generate a manifest file for your block types. You can use the build-blocks-manifest command from the @wordpress/scripts NPM package, which was also explained in the relevant WordPress 6.7 dev note. It can be easily integrated into your build process by changing the scripts in your package.json file as follows:

  • Change the “build” script from wp-scripts build to wp-scripts build && wp-scripts build-blocks-manifest.
  • Change the “start” script from wp-scripts start to wp-scripts start && wp-scripts build-blocks-manifest.

With the generated manifest in place, the PHP code above can be simplified to no longer require a hard-coded list of block types:

wp_register_block_types_from_metadata_collection(
	__DIR__ . '/build',
	__DIR__ . '/build/blocks-manifest.php'
);

Backward compatibility with older WordPress versions

As the wp_register_block_types_from_metadata_collection() function is only available in the latest WordPress 6.8 release, you may still want to support older WordPress versions. Fortunately, the function can be easily replaced by a few lines of codeLines of Code Lines of code. This is sometimes used as a poor metric for developer productivity, but can also have other uses., as long as you have a generated blocks manifest in place as described above.

Here is a code example that uses the respective best practices for WordPress 6.8, WordPress 6.7, and older versions:

if ( function_exists( 'wp_register_block_types_from_metadata_collection' ) ) {
	wp_register_block_types_from_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' );
} else {
	if ( function_exists( 'wp_register_block_metadata_collection' ) ) {
		wp_register_block_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' );
	}
	$manifest_data = require __DIR__ . '/build/blocks-manifest.php';
	foreach ( array_keys( $manifest_data ) as $block_type ) {
		register_block_type( __DIR__ . "/build/{$block_type}" );
	}
}

The @wordpress/create-block NPM package has been enhanced to use the new functions conditionally, using a similar code snippet as shown above.

Summary and further reading

The new wp_register_block_types_from_metadata_collection() function is a very simple but neat way to eliminate individual block type registration calls from your PHP code, allowing you to focus exclusively on working on the block types in your plugin without having to modify anything else in the plugin.

Please see the following links for further reading:

  • TracTrac An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress. ticketticket Created for both bug reports and feature development on the bug tracker. #62267
  • Relevant WordPress 6.7 dev note about the previous block type registration enhancements

Props to @gziolo, @stevenlinx for review and proofreading.

#6-8, #dev-notes, #dev-notes-6-8

Data: A helpful performance warning for developers in the ‘useSelect’ hook

useSelect is a ReactReact React is a JavaScript library that makes it easy to reason about, construct, and maintain stateless and stateful user interfaces. https://reactjs.org/. hook that lets you subscribe to WordPress data in the blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. editor. It checks if consumed data has changed and then rerenders your components accordingly.

Usually, things just work, and consumers don’t have to worry about unnecessary rerenders. However, sometimes data is directly manipulated in the mapSelect callback, which can mislead the useSelect hook into thinking that the data has changed when it has not.

Example:

export function ExampleWithWarning() {
	const { nameAndIds } = useSelect( function mapSelect( select ) {
		const authors = select( 'core' ).getUsers( {
			who: 'authors',
			context: 'view',
		} );

		return {
			// `Array.map` will return a new array for every call,
			// even if the data is the same.
			nameAndIds: authors?.map( ( { id, name } ) => ( { id, name } ) ),
		};
	}, [] );

	return <>{ /* Your rendering logic here */ }</>;
}

WordPress will now display a warning when SCRIPT_DEBUG is enabled to help consumers identify possible performance bottlenecks.

Example warning:

The useSelect hook returns different values when called with the same state and parameters. This can lead to unnecessary re-renders and performance issues if not fixed.

Non-equal value keys: nameAndIds

This warning can be fixed by requesting only the values needed to render a component or moving data manipulation outside the mapSelect callback. The actual solution can vary based on your code and logic.

Please refer to the fantastic article “How to work effectively with the useSelect hook” to learn more about best practices for using the useSelect hook.

Here’s how I would fix the example code from this post:

export function ExampleFixed() {
	const { nameAndIds } = useSelect( function mapSelect( select ) {
		const authors = select( 'core' ).getUsers( {
			who: 'authors',
			context: 'view',
			// Requests only fields that are needed.
			_fields: 'id,name',
		} );

		return {
			nameAndIds: authors,
		};
	}, [] );

	return <>{ /* Your rendering logic here */ }</>;
}

Props to @kirasong for the review.

#6-8, #dev-notes, #dev-notes-6-8, #editor

Roster of design tools per block (WordPress 6.8 edition)

Below you find a table that lists all coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. blocks available in the inserter marks in the grid the feature they support in the blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. editor. It’s a basic lookup table that helps developers to find the information quickly.

While this post is released as part of 6.8, the content summarizes changes between 6.1 and 6.8. This is an updated of the 6.7 edition and provides a cumulative list of design supports added with the last six WordPress releases. The icon ☑️ indicates new in 6.8.

The features covered are:

  • Align
  • Typography,
  • Color,
  • Dimension,
  • Border,
  • Layout,
  • Gradient,
  • Duotone,
  • Shadow,
  • Background image
  • Pattern overrides / Block Bindings (PO/BB)

Work in progress

The issue Tracking: Addressing Design Tooling Consistency lists tracking issues for individual block supports.

Props to @audrasjb for review.

#6-8, #dev-notes, #dev-notes-6-8, #editor

Internationalization improvements in 6.8

Various internationalization (i18n) improvements are in WordPress 6.8, and this developers note focuses on these.

Localized PHPMailer messages

Over 12 years after #23311 was reported, WordPress 6.8 now properly localizes any user-visible PHPMailer error messages. To achieve this, a new WP_PHPMailer class extending PHPMailer was introduced to leverage the WordPress i18ni18n Internationalization, or the act of writing and preparing code to be fully translatable into other languages. Also see localization. Often written with a lowercase i so it is not confused with a lowercase L or the numeral 1. Often an acquired skill. system with PHPMailer. Note that developers don’t typically interact with this class directly outside of wp_mail() or the phpmailer_init action.

See [59592] for more context.

PluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party update emails in the adminadmin (and super admin)’s localeLocale A locale is a combination of language and regional dialect. Usually locales correspond to countries, as is the case with Portuguese (Portugal) and Portuguese (Brazil). Other examples of locales include Canadian English and U.S. English.

This was reported and fixed in #62496. It’s a follow-up to the email localization change introduced in WordPress 6.7, where this instance was missed. Now, plugin update emails are correctly sent in the admin locale (if the admin email matches a user on the site).

See [59460] and [59478] for details.

Just-in-time translationtranslation The process (or result) of changing text, words, and display formatting to support another language. Also see localization, internationalization. loading for plugins/themes not in the WordPress.orgWordPress.org The community site where WordPress code is created and shared by the users. This is where you can download the source code for WordPress core, plugins and themes as well as the central location for community conversations and organization. https://wordpress.org/ directory

Back in version 4.6, WordPress introduced just-in-time translation loading for any plugin or theme that is hosted on WordPress.org. That meant plugins no longer had to call load_plugin_textdomain() or load_theme_textdomain().

With WordPress 6.8, this is now expanded to all other plugins and themes by looking at the text domain information provided by the plugin/theme. Extensions with a custom Text Domain and Domain Path headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. no longer need to call load_plugin_textdomain() or load_theme_textdomain(). This reduces the risk of calling them too late, after some translation calls already happened, and generally makes it easier to properly internationalize a plugin or theme.

In short:

  • If your plugin/theme is hosted on WordPress.org and requires WordPress 4.6 or higher: you don’t need to use load_*_textdomain()
  • Else, if your plugin/theme provides the Text Domain and Domain Path headers and requires WordPress 6.8 or higher: you don’t need to use load_*_textdomain()

See #62244 for more.


Props to @audrasjb, @stevenlinx for review.

#6-8, #dev-notes, #dev-notes-6-8, #i18n

Speculative Loading in 6.8

WordPress 6.8 introduces speculative loading, which can lead to near-instant page load times by loading URLs before the user navigates to them. The feature relies on the Speculation Rules API, a web platform feature that allows defining rules for which kinds of URLs to prefetch or prerender, and how early such speculative loading should occur.

Please refer to the speculative loading announcement post for additional information about the Speculation Rules APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways. and related prior art in WordPress CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress..

Context

Prior to being implemented in WordPress Core, the feature has been successfully used on over 50,000 WordPress sites via the Speculative Loading feature plugin, which has been ported over to Core now, with a few modifications. Based on data queried from the HTTP Archive and Chrome User Experience Report (CrUX) datasets over all the time since the pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party launched, the sites that enabled speculative loading improved their Largest Contentful Paint (LCP) passing rate by ~1.9% at the median which, while it may seem a small number, is a huge boost for a single feature, considering that a lot of sites with various performance implications contribute to the data.

The Speculation Rules API was first introduced in early 2023 and has seen ever increasing usage since then. Today, over 8% of Chrome navigations rely on Speculation Rules. A related significant launch happened a few months ago when Cloudflare enabled speculative loading at large scale via its Speed Brain feature.

The Speculation Rules API is supported by Chrome, Edge, and Opera so far, which means the vast majority of end users browsing the web can benefit from its capabilities. For users of browsers without support for the API, there are no adverse effects, since the Speculation Rules API is a progressive enhancementenhancement Enhancements are simple improvements to WordPress, such as the addition of a hook, a new feature, or an improvement to an existing feature.. Browsers without support simply ignore its presence, i.e. sites maintain the same behavior as before.

Default behavior and customization

The WordPress Core implementation enables speculative loading by default in the frontend of all sites, except when a user is logged in or when a site has pretty permalinks disabled. URLs are prefetched with conservative eagerness: this means that prefetching is triggered when a user starts to click on a link. While this is typically only a fraction of a second before the actual navigation occurs, it is still enough to lead to a notable performance improvement.

This default of prefetch with conservative eagerness is used as a reasonable starting point to enable speculative loading at the scale of WordPress. It is in line with the configuration that Cloudflare uses in its speculative loading feature, and it minimizes the chance of any speculative loads without a subsequent navigation to the URLURL A specific web address of a website or web page on the Internet, such as a website’s URL www.wordpress.org. The Speculative Loading plugin uses a default of prerender with moderate eagerness, which leads to a larger performance improvement due to the speculative load being triggered earlier as well as prerendering the URL, but also has tradeoffs related to certain client-side behavior inadvertently being triggered even in case the user never ends up navigating to the URL.

Customization via actions and filters

Excluding URL patterns from speculative loading

When a URL is prefetched, the server response is loaded before the user navigates to it. In most cases, this is not an issue since server responses for frontend URLs do not typically change the state of the site in any way. However, there may be plugins that use the pattern of so-called “action URLs”, where simply navigating to a specific URL (with a GET request) a state change occurs—for example on an e-commerce WordPress site, it could be adding a product to the shopping cart or marking an item as a favorite. It is worth noting that this is an antipattern, since state changes should typically be triggered only by POST requests, e.g. via form submissions, and GET requests are supposed to be “idempotent”. Despite that, plugins that use this pattern should ensure that such URLs are excluded from prefetching and prerendering. In the case of conservative eagerness, this should not be an issue since it’s almost guaranteed the user will also navigate to the URL. But for sites that use a more eager configuration there is a chance the navigation will never occur, which is why excluding such URLs is important.

By default, any URLs that include query parameters are excluded from prefetching and prerendering automatically, which should cater for the majority of such action URLs. However, in case a plugin is implementing its own rewrite rules for these URLs instead of using custom query parameters, they can use the wp_speculation_rules_href_exclude_paths filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. to provide URL patterns to exclude.

This example ensures that any URLs with a path starting in “/cart/” will be excluded from speculative loading, regardless of whether it’s prefetch or prerender:

add_filter(
	'wp_speculation_rules_href_exclude_paths',
	function ( $href_exclude_paths ) {
		$href_exclude_paths[] = '/cart/*';
		return $href_exclude_paths;
	}
);

All URL patterns provided should follow the URL Pattern web specification, and they will all be considered relative to the frontend of the site. For sites where the home URL is in a subdirectory, WordPress will automatically prefix the corresponding path segment so that plugin developers do not need to worry about that.

While WordPress Core’s default behavior is to prefetch URLs, sites may opt in to prerendering URLs. This leads to a significant performance boost, but also has additional implications on the speculatively loaded URLs, since even their client-side code will be loaded. If a site contains any client-side logic that should only run once the user actually navigates to the URL, it needs to check for whether the site is being prerendered first and only trigger such logic once the navigation has occurred (see “Detect prerender in JavaScript” documentation). A common use-case for that is analytics tooling (see “Impact on analytics” documentation). Many popular providers already support prerendering, so no change is necessary. But if your site or your plugin includes such functionality on certain URLs and you haven’t updated the JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a user’s browser. https://www.javascript.com/. logic to support prerendering yet, you can temporarily exclude the relevant URLs from prerendering specifically.

This example ensures that any URLs with a path starting in “/personalized-area/” will be excluded from prerender speculative loading only:

add_filter(
	'wp_speculation_rules_href_exclude_paths',
	function ( $href_exclude_paths, $mode ) {
		if ( 'prerender' === $mode ) {
			$href_exclude_paths[] = '/personalized-area/*';
		}
		return $href_exclude_paths;
	},
	10,
	2
);

Modifying the default speculative loading configuration

As mentioned before, WordPress sites are able to modify the default speculative loading configuration. For further improved performance, you may want the configuration to be more eager or to leverage prerendering. This can be achieved via the wp_speculation_rules_configuration filter, which receives either an associative array with mode and eagerness keys to control the configuration, or null to disable speculative loading for the current request.

The default value for the filter is array( 'mode' => 'auto', 'eagerness' => 'auto' ), unless a user is logged-in or the site has pretty permalinks disabled, in which case the default value is null. For both configuration parameters, the value auto signifies that WordPress Core will decide on the configuration, which as of today effectively leads to a mode of prefetch and an eagerness of conservative. Depending on various criteria such as the state of the Speculation Rules API and ecosystem support, the behavior may change in a future WordPress release.

Here is an example that uses the filter to increase the eagerness to moderate. This will improve the performance benefits, while increasing the tradeoff for a speculative load without subsequent navigation:

add_filter(
	'wp_speculation_rules_configuration',
	function ( $config ) {
		if ( is_array( $config ) ) {
			$config['eagerness'] = 'moderate';
		}
		return $config;
	}
);

The mode value can be either auto, prefetch, or prerender, and the eagerness value can be either auto, conservative, moderate, or eager. The Speculation Rules API also defines another eagerness value of immediate, however that value is strongly discouraged for document-level rules that speculatively load any URLs, so the WordPress Core API does not allow using it for its overall configuration.

If you wanted to opt for an even greater performance boost, here is an example that uses the filter to opt for prerender with moderate eagerness. This is similar to what the Speculative Loading feature plugin implements, and it can lead to a significant performance boost. Please keep in mind the effects of prerendering on client-side JavaScript logic explained in the previous section before enabling prerender.

add_filter(
	'wp_speculation_rules_configuration',
	function ( $config ) {
		if ( is_array( $config ) ) {
			$config['mode']      = 'prerender';
			$config['eagerness'] = 'moderate';
		}
		return $config;
	}
);

As mentioned before, speculative loading is disabled by default for sites that do not use pretty permalinks. This is because the aforementioned exclusion of URLs with query parameters would not reliably apply anymore if even WordPress Core’s arguments used query parameters. There may however be cases where, as a site owner of a site without pretty permalinks, you are confident that your site is not using any of the problematic patterns that are the reason for this exclusion in the first place, or you already identified them and explicitly excluded URLs with the specific query parameters from being speculatively loaded. In that case, you could use the filter to enable speculative loading, as seen here:

add_filter(
	'wp_speculation_rules_configuration',
	function ( $config ) {
		if ( ! $config && ! get_option( 'permalink_structure' ) ) {
			$config = array(
				'mode'      => 'auto',
				'eagerness' => 'auto',
			);
		}
		return $config;
	}
);

Please use caution when opting into speculative loading like this. WordPress Core’s defaults were carefully considered to cater for the majority of sites in a safe way, so only use this code snippet if you are confident it will not have adverse effects on your site.

Including additional speculation rules

The Speculation Rules API allows defining multiple rules to configure how the browser should speculatively load URLs. By default, WordPress Core only includes a single rule that handles all the aforementioned behavior. More advanced customization is possible by providing entirely new speculation rules in addition to Core’s main rule, which can be accomplished by using the wp_load_speculation_rules action. The action receives an instance of the new WP_Speculation_Rules class, which has validation mechanisms built in and can be amended as needed. By adding new rules, you can implement entirely custom configurations that will be applied on top of WordPress Core’s main rule.

Here is an example, which directly relates to the previous example that changes the default configuration to prerender with moderate eagerness. You may prefer not to change the default, but there may be specific URLs where you would like to enable prerender with moderate eagerness. You could add a custom URL-level speculation rule for that:

add_action(
	'wp_load_speculation_rules',
	function ( WP_Speculation_Rules $speculation_rules ) {
		$speculation_rules->add_rule(
			'prerender',
			'my-moderate-prerender-url-rule',
			array(
				'source'    => 'list',
				'urls'      => array(
					'/some-url/',
					'/another-url/',
					'/yet-another-url/',
				),
				'eagerness' => 'moderate',
			)
		);
	}
);

Taking this example further, maybe there is no easy way to provide a list of URLs, e.g. in case they change often or more URLs need to be added regularly. In that case, you consider using a document-level speculation rule that applies for all links that are marked with a specific class, or where a parent element has a specific class:

add_action(
	'wp_load_speculation_rules',
	function ( WP_Speculation_Rules $speculation_rules ) {
		$speculation_rules->add_rule(
			'prerender',
			'my-moderate-prerender-optin-rule',
			array(
				'source'    => 'document',
				'where'     => array(
					'selector_matches' => '.moderate-prerender, .moderate-prerender a',
				),
				'eagerness' => 'moderate',
			)
		);
	}
);

With this rule in place, users would be able to add a moderate-prerender class to any blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. that supports the advanced UIUI User interface for adding classes, and that way they could manually opt in any link on demand.

Please refer to the Speculation Rules API specification for details on what a rule definition can look like.

Customization via UI

The previous example already hints at how speculative loading can be customized via the UI. While a dedicated UI for the feature like it exists in the Speculative Loading feature plugin is out of scope for WordPress Core, many block types provide an “Additional CSSCSS Cascading Style Sheets. class(es)” field in the “Advanced” panel in the block sidebarSidebar A sidebar in WordPress is referred to a widget-ready area used by WordPress themes to display information that is not a part of the main content. It is not always a vertical column on the side. It can be a horizontal rectangle below or above the content area, footer, header, or any where in the theme..

WordPress Core has built-in support for the CSS classes no-prefetch and no-prerender. You can add these to any block so that links within that block are opted out of prefetching or prerendering respectively. Note that no-prefetch opts out of both, i.e. opts out of speculative loading entirely, since prefetching is part of prerendering. Please refer to the section about excluding URL patterns for guidance on when it may be useful to exclude URLs from prefetching or prerendering.

This mechanism makes it easy for advanced users to take granular control over speculative loading for specific blocks.

Summary and further reading

The speculative loading feature in WordPress 6.8 is a great win for performance of WordPress and the web overall, by starting the page load process even before the user navigation occurs. By fine-tuning it further based on your site’s needs, you can achieve even greater performance boosts than with the default configuration.

Please see the following links for further reading:

Props to @westonruter, @tunetheweb, @adamsilverstein, @joemcgill for review and proofreading.

#6-8, #dev-notes, #dev-notes-6-8, #feature-projects, #performance, #speculative-loading

WordPress 6.8 will use bcrypt for password hashing

Note: This article has been updated with additional technical information about the structure of password hashes that may be relevant to developers of plugins that directly handle them.

The underlying algorithm that’s used to hash and store user passwords in the database will be changed in WordPress 6.8 from phpass portable hashing to bcrypt. The adoption of bcrypt hardens password security in WordPress by significantly increasing the computational cost of cracking a password hash.

In addition, application passwords, user password reset keys, personal data request keys, and the recovery mode key will switch from using phpass to the cryptographically secure but fast BLAKE2b hashing algorithm via Sodium.

No action needs to be taken by site owners or users as a result of these changes. Passwords and security keys that were saved in prior versions of WordPress will continue to work after updating to 6.8. Users don’t need to change or reset their passwords, logged in users will remain logged in, and their sessions will remain valid.

When a user first subsequently logs in after the update – or when they next change their password – their password will automatically get rehashed with bcrypt and resaved in the database. Application passwords and security keys will not get automatically rehashed, but an existing hash will remain valid if it was generated prior to WordPress 6.8 and used before it expires.

Note that post passwords will continue to use phpass portable hashing for now. This may change in the future after further investigation has been done on how best to improve the hashing and checking mechanism of post passwords.

Portability

Hashes that are generated by the phpass portable hashing algorithm are portable between different sites, environments, and servers. This portability doesn’t change with this switch to bcrypt and BLAKE2b, so you can move your database from one server to another and update to newer versions of PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher and WordPress and the password hashes will continue to function as expected.

Updates to password handling functions

The wp_hash_password() and wp_check_password() functions have been updated to use the PHP native password_hash() and password_verify() functions with the bcrypt algorithm and SHA-384 pre-hashing. Both functions retain support for the $wp_hasher global object in case that’s being used to implement an alternative hashing mechanism.

The wp_check_password() function retains support for passwords that were hashed using phpass, which means existing password hashes won’t be invalidated.

A new wp_password_needs_rehash() function has been introduced as a wrapper for password_needs_rehash(). If a pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party needs to adjust its logic then the password_needs_rehash filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. can be used. The function is also pluggable, so it can be overridden if absolutely necessary.

Pre-hashing with SHA-384 is implemented in order to avoid the 72 byte length limit imposed on passwords by bcrypt. Password hashes are therefore stored with a $wp prefix to distinguish them from vanilla bcrypt hashes which may be in use via a plugin. By default this means the full prefix will be $wp$2y$.

New fast hashing functions

The following functions have been introduced as wrappers for the cryptographically secure but fast BLAKE2b algorithm via Sodium:

  • wp_fast_hash()
    Used to hash a string that is randomly generated using sufficiently high entropy, preferably over 128 bits for values that don’t have a corresponding expiry mechanism.
  • wp_verify_fast_hash()
    Used to verify a hash generated via wp_fast_hash(), with fallback support for phpass portable hashes.

Do developers need to do anything?

Code that calls wp_hash_password() and wp_check_password() will continue to work as expected and does not need to change.

Code that directly handles phpass hashes may need to be updated, for example:

  • Code that assumes the existence of the $P$ prefix on hashes. The code will need to be updated so it either doesn’t need to inspect the prefix of the hash at all, or updated to handle both the new prefixes and hashing algorithms and legacy hashes:
    • The $wp$2y$ prefix will be the new default for user passwords in WordPress and represents a SHA-384 pre-hashed bcrypt hash.
    • The plain $2y$ prefix may be used by existing plugins that use bcrypt hashes without pre-hashing.
    • The $generic$ prefix for a BLAKE2b hash used for application passwords and security keys.
    • A hash starting with $argon2 if a site opts in to using an Argon2 algorithm (see below). Note that any non-bcrypt algorithm won’t use pre-hashing and therefore won’t include $wp at the start of the prefix.
    • The old $P$ prefix for a phpass portable hash which will remain in wide use.
    • A legacy plain MD5 hash, which is a 32 character hexadecimal string.
  • Code that otherwise directly interacts with the hashed value of a user password. If such hashes are validated directly, this should be done via wp_check_password().
  • Code that otherwise directly interacts with the hashed value of an application password, password reset key, personal data request key, or the recovery mode key. If such hashes are validated directly, this should be done via the new wp_verify_fast_hash() function.
  • Any plugin that overwrites the pluggable wp_hash_password() and wp_check_password() functions. Unless these functions specifically implement another hashing algorithm, they can be removed in order to allow the bcrypt implementation in 6.8 to take effect.

Alternative authentication mechanisms such as single sign-on (SSO), social login, or one-time login are unlikely to be affected by this change, however you should still verify whether your specific implementation includes any handling of password hashes or security keys. Multi-factor (MFA and 2FA) implementations are also unlikely to be affected by this change.

What about Argon2?

Servers that support Argon2 can enable its usage with this single line of code in WordPress 6.8 and later:

add_filter( 'wp_hash_password_algorithm', fn() => PASSWORD_ARGON2ID );

If necessary, the password_algos() function should be used to first check for argon2id support. Unfortunately it’s not possible to rely on Argon2 being available on all servers because it requires both libargon2 to be available on the server and for PHP to be built with Argon2 support enabled. The sodium_compat library does not provide an implementation of Argon2.

Acknowledgements

We can’t pretend that switching to bcrypt for user-generated passwords is a recent proposal. Ideally the switch would have been made back when the increase to the minimum supported version of PHP facilitated this change. However, this change has now been made and it helps future-proof further improvements to password hashing, including increases to the bcrypt cost in newer versions of PHP.

Many thanks go to the Roots team for maintaining their bcrypt password hashing package for WordPress as well as the many contributors on the TracTrac An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress. tickets and GitHubGitHub GitHub is a website that offers online implementation of git repositories that can easily be shared, copied and modified by other developers. Public repositories are free to host, private repositories require a paid subscription. GitHub introduced the concept of the ‘pull request’ where code changes done in branches by contributors can be reviewed and discussed before being merged be the repository owner. https://github.com/ pull requests.

Further technical information

Further technical information, technical FAQs, and implementation details can be seen on the GitHub pull request for this change and in the discussion on the Trac ticket.

In case you need to know:

  • User passwords are stored as a hash in the wp_users.user_pass field in the database.
  • Application passwords are stored as a hash in a JSONJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML. serialized object in the wp_usermeta table using the _application_passwords key.
  • User password reset keys are stored as a hash in the wp_users.user_activation_key field in the database.
  • Personal data request keys are stored as a hash in the wp_posts.post_password field in the database against the post that represents the personal data request.
  • The recovery mode key is stored as a hash in the recovery_keys option in the wp_options database table.

Thanks to @desrosj and @joehoyle for helping review this post.

#6-8, #dev-notes