Block metadata viewScriptModule field in 6.5

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. 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. now recognizes the viewScriptModule field in block.json metadata. This provides an experience for script modules that’s analogous to what scripts and the viewScript field currently provide. The script module declared in viewScriptModule will be enqueued when a block is rendered for viewing on the frontend.

This new field is important so that developers can use script modules on the frontend. Scripts cannot depend on script modules, so script modules are necessary to use script modules like the Interactivity API (via the @wordpress/interactivity script module).

Read more: Block metadata viewScriptModule field in 6.5

This post assumes a basic understanding of the Script Modules API, read more about it here.

Getting started

You can generate a simple block 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 using the @wordpress/create-block-interactive-template package:

npx @wordpress/create-block --template @wordpress/create-block-interactive-template

This will generate an interactive block from a template that uses viewScriptModule.

Usage

Add a viewScriptModule field to the metadata in block.json that points to the script module.

Here’s what a simple plugin might look like:

myplugin/
├── build/
│ ├── block.json
│ ├── …snip…
│ ├── view.asset.php
│ └── view.js
└── plugin.php

The only code in the plugin.php file calls register_block_type for the block:

add_action( 'init', function() {
register_block_type( __DIR__ . '/build/block.json' );
} );

The relevant metadata in build/block.json includes viewScriptModule:

{
"…snip…": "…",
"viewScriptModule": "file:./view.js"
}

build/view.js is the script module that depends on @wordpress/interactivity:

import { getContext, store } from '@wordpress/interactivity';

store( 'myplugin/demo', {
increment() {
getContext().val += 1;
},
decrement() {
getContext().val -= 1;
},
} );

The module asset file build/view.asset.php declares the view script module’s dependency on @wordpress/interactivity:

<?php return array(
'dependencies' => array('@wordpress/interactivity'),
);

REST APIREST API The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think “phone app” or “website”) can communicate with the data store (think “database” or “file system”) https://developer.wordpress.org/rest-api/.

A new field view_script_module_ids has been added to the Block Types REST API. The field is a list of the view script module IDs associated with a block type.

Tooling

The @wordpress/scripts package is the standard way of compiling 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/. assets for use in WordPress. Experimental functionality has been added to the build and start scripts to support compiling modules. It can be enabled using --experimental-modules on the command line, for example:

 wp-scripts build --experimental-modules

Script module compilation with @wordpress/scripts does not support using your own webpack file at this time.

The example plugin above was compiled using the experimental module build with @wordpress/scripts.

@wordpress/dependency-extraction-webpack-plugin has been updated to produce script module asset files. No special configuration is required. See the package documentation for details.

Relevant links

Props

#6-5, #dev-notes, #dev-notes-6-5

Updates to Block Hooks in 6.5

First introduced in WordPress 6.4, 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. HooksHooks In WordPress theme and development, hooks are functions that can be applied to an action or a Filter in WordPress. Actions are functions performed when a certain event occurs in WordPress. Filters allow you to modify certain functions. Arguments used to hook both filters and actions look the same. offer an extensibility mechanism for Block Themes that allows extenders to inject a dynamic block in a location specified by an “anchor” block and a relative position. (For example, before or after the Post Content block.)

WordPress 6.5 updates Block Hooks to make them more widely useful and includes some frequently requested new features.

New features

Modified layouts

Previously restricted to layouts – templates, template parts, and patterns – that didn’t have any user modifications, Block Hooks now also work with modified layouts. This works out of the box; extenders do not need to update their code to benefit from this. Block Hooks will continue to respect user customizations. If a user moves or deletes a hooked block in the Site Editor, the Block Hooks mechanism will not re-insert it, thus preserving the user’s express intentions.

Navigation block

Additionally, it is now possible to inject hooked blocks into the Navigation block. Previously, a hooked block could only be added before or after the Navigation block, but now it can also be added as its first or last child.

As a consequence, the hooked_block_types 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.’s $context argument can now also be a WP_Post object with its post type set to wp_navigation.

This means that a negative condition, such as if ( ! $context instanceof WP_Block_Template ), is no longer sufficient to conclude that $context is an array (i.e., a pattern). Instead, use an affirmative check, such as if ( is_array( $context ) ).

The following example adds a (hypothetical) Shopping Cart block as the Navigation block’s last child, after ensuring that the block isn’t already present elsewhere in the Navigation menuNavigation Menu A theme feature introduced with Version 3.0. WordPress includes an easy to use mechanism for giving various control options to get users to click from one place to another on a site.:

function add_shopping_cart_block_to_navigation_block( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {
	// Is $context a Navigation menu?
	if ( ! $context instanceof WP_Post || 'wp_navigation' !== $context->post_type ) {
		return $hooked_block_types;
	}

	// Does the Navigation menu already contain the block?
	if ( str_contains( $context->post_content, '<!-- wp:my-ecommerce/shopping-cart' ) ) {
		return $hooked_block_types;
	}

	if ( 'last_child' === $relative_position && 'core/navigation' === $anchor_block_type ) {
		$hooked_block_types[] = 'my-ecommerce/shopping-cart';
	}
	return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_shopping_cart_block_to_navigation_block', 10, 4 );

New filters

In order to offer more fine-grained control over hooked blocks, two new filters have been introduced: hooked_block and hooked_block_{$hooked_block_type}. Both share the following signature:

  • $parsed_hooked_block (array|null) – The hooked block, in parsed block array format, or null, indicating that a previous filter has suppressed the injection of the given block.
  • $hooked_block_type (string) – The hooked block type name.
  • $relative_position (string) – The relative position of the hooked block (can be one of before, after, first_child, or last_child).
  • $parsed_anchor_block (array) – The anchor block, in parsed block array format.
  • $context (WP_Block_Template|WP_Post|array) – The block template, template part, wp_navigation post type, or pattern that the anchor block belongs to.

One of the most frequently requested features for Block Hooks was the ability to set a hooked block’s attributes. While it is generally advised to choose default values for block attributes that work well when the block is used as a hooked block, it is now possible to set custom values for its attributes via these new filters.

In addition, the filters allow setting a hooked block’s inner blocks (and, by extension, wrapping it inside of another block), or suppressing the hooked block altogether (by returning null).

Finally, the filters are also given the anchor block instance as an argument, so it’s possible to, for example, set the hooked block’s attributes based on the anchor block’s:

function set_block_layout_attribute_based_on_adjacent_block( $hooked_block, $hooked_block_type, $relative_position, $anchor_block ) {
	// Has the hooked block been suppressed by a previous filter?
	if ( is_null( $hooked_block ) ) {
		return $hooked_block;
	}

	// Is the hooked block adjacent to the anchor block?
	if ( 'before' !== $relative_position && 'after' !== $relative_position ) {
		return $hooked_block;
	}

	// Does the anchor block have a layout attribute?
	if ( isset( $anchor_block['attrs']['layout'] ) ) {
		// Copy the anchor block's layout attribute to the hooked block.
		$hooked_block['attrs']['layout'] = $anchor_block['attrs']['layout'];
	}

	return $hooked_block;
}
add_filter( 'hooked_block_my/like-button', 'set_block_layout_attribute_based_on_adjacent_block', 10, 4 );

For more examples, refer to the snippets found in the description of the PR that introduced the new filters.

Plugins block inspector panel toggles

The toggles for each hooked block in the block inspector’s “Plugins” panel have been updated to fix a confusing behavior that would remove the toggle in certain cases after it was deactivated. Furthermore, toggles are now shown for blocks added by the hooked_block_types filter; note that due to technical limitations, this currently only works for layouts that have user modifications.

A screenshot showing a Navigation block selected in the Site Editor. The block contains 'News', 'About', and 'Logout' links, and a shopping cart icon.

The Block Inspector sidebar is open. It is showing the 'Plugins' panel with toggles for the hooked blocks: one for the Shopping Cart, and one for the Login/out link.

Under the hood

The major technical challenge to making Block Hooks work with modified layouts was to ensure they would continue respecting user customizations to a given layout. That is, when a block was persisted, moved, or deleted by the user in the Site Editor, it wouldn’t end up being re-inserted by the Block Hooks mechanism.

It turned out that this problem could be solved by essentially a “finer-grained” version of the same principle (i.e., modified vs. unmodified). This is achieved by storing information about hooked block types in an ignoredHookedBlocks array inside the global metadata attribute on the anchor block. Specifically, it works via the two following operations:

On write (i.e., persisting a modified layout to the database): All hooked block types that were present during the edit are persisted to their respective anchor block’s ignoredHookedBlocks arrays. (Note that this is regardless of the nature of the edit: It works the same both for when the hooked block is persisted and when it is removed.)

On read (i.e., rendering on the front end or loading into the Site Editor): The Block Hooks mechanism will now look for the presence of a given hooked block type inside its anchor block’s ignoredHookedBlocks array. If a hooked block’s type name is found in that array, it will be ignored (i.e., not injected).

See this Trac ticket for more information on how this solution was conceived.

For controlled inner blocks: For inner blocks that aren’t present inside the containing block but loaded separately at runtime, the same ignoredHookedBlocks array is stored, but in a new post metaMeta Meta is a term that refers to the inside workings of a group. For us, this is the team that works on internal WordPress sites like WordCamp Central and Make WordPress. field _wp_ignored_hooked_blocks instead of in the containing block’s attributes. This is how hooked blocks work in the Navigation block with its external wp_navigation post.


With these new features in place and several important limitations lifted, Block Hooks can now be used across a large number of layouts, without paying heed to whether the user has modified a layout or not. Specialized filters allow you to fine-tune hooked blocks by setting their attributes and inner blocks while also taking information about the anchor block instance into account when doing so. This makes Block Hooks a more intuitive and powerful tool for extending Block Themes.

We’re looking forward to seeing what extenders will create with this 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.!

Props to @dmsnell, @gziolo, @ndiego, and @leonnugraha for peer review.

#6-5, #dev-notes, #dev-notes-6-5

Performance improvements for registering block variations with callbacks

Background

In WordPress 5.8, the 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. for registering 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. variations was introduced, bringing new capabilities to developers. However, it soon became evident that generating these variations had a non-trivial performance cost, particularly when used for template-part and navigation-link in WordPress 5.9 and post-terms in WordPress 6.1.

The coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. of this issue was that these variations were being generated on every page load due to their registration using the init hook. This was inefficient, as the variations were only needed in the editor or for returning block data in the REST APIREST API The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think “phone app” or “website”) can communicate with the data store (think “database” or “file system”) https://developer.wordpress.org/rest-api/.. This redundancy in loading led to unnecessary performance overhead, underscoring the need for a more streamlined approach.

What is changed

In WordPress 6.5, we’ve introduced $variation_callback as a new attribute in WP_Block_Type. This feature allows developers to register variation-building functions as callbacks rather than building variations before registration. With this approach, variations are constructed from this callback only when they are accessed for the first time. 

Consequently, the WP_Block_Type::get_variations() method was introduced as the primary means to access variations. This method skillfully checks for a callback in variation_callback and triggers the callback, but only during the first access of the variations. This efficient approach ensures that the callback is executed just once, optimizing performance by avoiding redundant processing on subsequent accesses. Additionally, this strategy aids in lazy loading, as variations no longer need to be loaded during registration.

Moreover, it integrates the get_block_type_variations 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., which provides a versatile mechanism for developers to modify the resulting variations. These enhancements reflect WordPress’s ongoing commitment to improving its developer-centric features and maintaining compatibility with existing functionalities.
In WordPress 6.5, a significant alteration is made to WP_Block_Type::$variations. The notable change involved transitioning the visibility of variations from public to private. This modification was specifically designed to necessitate the use of the PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 5.6.20 or higher magic __get method for accessing variations only via WP_Block_Type::get_variations().

Please note that the modifications to WP_Block_Type::$variations may introduce certain limitations that require developer attention. These are detailed in the “Developer Action” section below.

Lazy loading variations using variation_callback

To facilitate the lazy loading of variations, we can replace the traditional method of loading variations with a callback. This approach is exemplified in the case of GutenbergGutenberg The Gutenberg project is the new Editor Interface for WordPress. The editor improves the process and experience of creating new content, making writing rich content much simpler. It uses ‘blocks’ to add richness rather than shortcodes, custom HTML etc. https://wordpress.org/gutenberg/ PR #56952, which is aimed at enhancing performance.

Previously, template-part variations were loaded as follows:

register_block_type_from_metadata(
    __DIR__ . '/template-part',
    array(
        'render_callback'    => 'render_block_core_template_part',
        'variations'         => build_template_part_block_variations(),
    )
);

To optimize this, we have transitioned to using a variation_callback:

register_block_type_from_metadata(
    __DIR__ . '/template-part',
    array(
        'render_callback'    => 'render_block_core_template_part',
        'variation_callback' => 'build_template_part_block_variations',
    )
);

It’s important to note that if both variations and variation_callback are defined, like in the following example:

register_block_type_from_metadata(
'block-name',
array(
'variations' => array( 'test-variation' => array( 'test-variations' ) ),
'variation_callback' => 'get_test_variations',
)
);

In this scenario, the variations array takes precedence, and the variation_callback will not be called.

Developer action required

With the addition of variations to the existing magic function framework in WP_Block_Type, a significant limitation emerges: the inability to modify variations by reference directly. This change impacts how variations were traditionally updated.

Consider these examples of updating variations by reference, which will no longer function as expected in WordPress 6.5:

$block_type->variations[] = array( 'test' => array( 'test' ) );
// or
$block_type->variations['example1'] = array( 'test' );

To overcome this limitation in WordPress 6.5, a workaround involving a temporary variable can be used. Here’s how you can modify the temporary variable and then reassign it back to $block_type->variations:

$variations = $block_type->variations;$variations[] = array( 'test' => array( 'test' ) );
$block_type->variations = $variations;
// Similarly$variations = $block_type->variations;
$variations['example1'] = array( 'test' );$block_type->variations = $variations;

Alternatively, to align with the latest updates and best practices in WordPress, you might consider using the get_block_type_variations filter (reference link).

Utilizing the get_block_type_variations filter for enhanced variation management

The introduction of the get_block_type_variations filter in WordPress 6.5 allows for the manipulation and customization of the variations array, providing greater flexibility in handling block variations.

Here’s an implementation example of the get_block_type_variations filter:

function modify_block_type_variations( $variations, $block_type ) {
// Example: Add a new variation
if ( 'block-name' === $block_type->name ) {
$variations[] = array(
'name' => 'new-variation',
'title' => __( 'New Variation', 'textdomain' ),
// Other variation properties...
);
}

return $variations;
}
add_filter( 'get_block_type_variations', 'modify_block_type_variations', 10, 2 );

In this example, the modify_block_type_variations function is hooked to the get_block_type_variations filter. It checks if the block type is ‘block-name‘ and then adds a new variation to the variations array. This function then returns the modified variations array.

By leveraging this filter, developers can dynamically adjust the variations according to specific requirements or conditions.

Please refer to #59969 for additional details on these changes.

Props to @adamsilverstein, @westonruter, @joemcgill, and @leonnugraha for reviewing this post.

#6-5, #block-api, #dev-notes, #dev-notes-6-5

I18N Improvements in 6.5 (Performant Translations)

Various internationalization (i18n) improvements are in WordPress 6.5, and this developers note will focus on these.

New localization system with improved performance

Over the past year, WordPress contributors have meticulously analyzed performance of the existing 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 in WordPress and ultimately created a new Performant Translations feature pluginFeature Plugin A plugin that was created with the intention of eventually being proposed for inclusion in WordPress Core. See Features as Plugins. that provided a completely overhauled system with significantly better performance. After thousands of betaBeta A pre-release of software that is given out to a large group of users to trial under real conditions. Beta versions have gone through alpha testing in-house and are generally fairly close in look, feel and function to the final product; however, design changes often occur as part of the process. testers and a merge announcement late last year, this new library is now included in WordPress 6.5! See #59656 for all the details.

The Performant Translations 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 is still useful and will continue to be maintained to build on top of the coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. solution with a distinct additional feature. As is already the case today, the plugin will automatically convert any .mo files to PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 5.6.20 or higher files if a PHP file does not currently exist. This is useful for sites where translations are not coming from translate.wordpress.org or only exist locally on that server.

This new library is faster at loading binary .mo files and uses less memory. It even supports loading multiple locales at the same time, which makes locale switching faster. In addition to that, it supports translations contained in PHP files, avoiding a binary file format and leveraging OPCache if available.

The new library is so fast, in fact, that it paves the way for the Preferred Languages feature plugin to merge translations of multiple locales by default starting with WordPress 6.5.

While this is in large part a silent and backward-compatible under-the-hood change, there are still a few things to be aware of:

New .l10n.php translationtranslation The process (or result) of changing text, words, and display formatting to support another language. Also see localization, internationalization. file format

When downloading language packs from 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/, there will be a new .l10n.php file in addition to the .mo and .po files you are already familiar with. If an .mo translation file has a corresponding .l10n.php file, the latter will be loaded instead, making things even faster and use even less memory.

This 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., so if there’s only an .mo file but no PHP file, translations will still be loaded as expected. However, the opposite is also true! So you can theoretically use only .l10n.php translation files in your project and features such as the just-in-time translation loading continue to work. Right now, WordPress still expects corresponding .po and .mo files for things like update checks. However, this limitation will be addressed in the future, see #60554 for more information.

Note: if you don’t see any .l10n.php translation files in wp-content/languages yet, it might be that the language pack hasn’t been updated in a while, i.e. there were no new translations.

Here’s an example of a PHP translation file as supported by WordPress 6.5:

<?php
return [
'project-id-version' => 'WordPress - 6.5.x - Development',
'report-msgid-bugs-to' => 'polyglots@example.com',
'messages' =>
[
'original' => 'translation',
'contextEOToriginal with context' => 'translation with context',
'plural0' => 'translation0' . "\0" . 'translation1',
'contextEOTplural0 with context' => 'translation0 with context' . "\0" . 'translation1 with context',
'Product' => 'Produkt' . "\0" . 'Produkte',
],
];

Note: EOT here stands for the “End of Transmission” character (U+0004, or "\4" in PHP). It’s the same delimiter as in gettext used to glue the context with the singular string.

Generating PHP translation files

If you would like to generate these PHP translation files yourself, version 4.0 of GlotPress, the plugin that powers translate.WordPress.org, already supports the new .l10n.php format.

In addition to that, WP-CLI 2.10.0 (i18n-command 2.6.0) provides a new wp i18n make-php command to create these PHP files from a given .po file. Examples:

# Create PHP files for all PO files in the current directory.
$ wp i18n make-php .

# Create a PHP file from a single PO file in a specific directory.
$ wp i18n make-php example-plugin-de_DE.po languages

If you are developing a WordPress plugin that deals with translations, you can also use the new WP_Translation_File class to convert an .mo file into a PHP file. Example:

$contents = WP_Translation_File::transform( $mofile, 'php' );
if ( $contents ) {
file_put_contents( $path_to_php_file, $contents );
}

New filters to customize this behavior

If you would like to disable the support for PHP files for some reason; for example, if you don’t have any yet in your project and want to prevent the extra file lookup operation, you can use the new translation_file_format 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 change the preferred format (default is php) like so:

add_filter(
'translation_file_format',
static function () {
return 'mo';
}
);

The existing load_textdomain_mofile filter can still be used to filter the .mo file path for loading translations for a specific text domain. However, it only works for .mo files. To filter the path for a translation file, be it a .l10n.php or a .mo file, use the new load_translation_file filter.

Working with the $l10n global variable

Previously, when loading translations, WordPress would store an instance of the MO class in the $l10n global variable. With WordPress 6.5, this will be an instance of a new WP_Translations class that acts as a shim with similar characteristics. If your project directly works with this global variable or the MO class in some way, this is an area to keep an eye on.

Cached list of language file paths

This another slight performance improvement but unrelated to the new localization library covered above.

In places such as get_available_languages() and WP_Textdomain_Registry, WordPress used to directly use the glob() function to retrieve all .mo files in a specific directory. This is important for just-in-time translation loading and generally knowing which translations are installed. However, on sites with a large number of language files, the glob() operation can become expensive.

Because of this, a new caching mechanism was introduced in #58919 / [57287]. The file lookup is now handled centrally in WP_Textdomain_Registry and stored in the object cache in the translations group, with the cache key having the format cached_mo_files_<hash>, where <hash> is the MD5 hash of the scanned directory, e.g. wp-content/languages. The cache is cleared whenever language packs are updated.

Also, the lookup now also scans for .l10n.php files in addition to .mo files, in case only the former exist on a site.

More questions? Please let us know

If you have any questions, please leave a comment below or file a new ticket on Trac under the I18N component if you’ve encountered any bugs.

Props to @joemcgill, @stevenlinx for review.

#6-5, #dev-notes, #dev-notes-6-5, #i18n

WordPress 6.5 adds AVIF support 

WordPress 6.5 supports AVIF, a modern image format that offers significant improvements in image quality and compression over previous formats like JPEG, PNG, and even WebP.  AVIF images can be up to 50% smaller than JPEGs while maintaining the same image quality. AVIF images also support a wide range of colors (including HDR) and produce sharper images than JPEGs, especially in areas with high detail.

From WordPress version 6.5 forward, you can upload and use AVIF images in WordPress just like you would a JPEG or PNG image today – as long as your hosting environment supports AVIF. Switching to the AVIF format for your images is likely to improve your site’s performance and your site visitor’s experience. 

How AVIF helps you

AVIF images are significantly smaller than their JPEG equivalents, so pages load more quickly and take less bandwidth to transmit. AVIF images still get all of the benefits of the responsive images, Fetch Priority, and lazy loading that WordPress supports by default. Finally, AVIFs are supported in all major browsers, so most sites can start using them today.

Creating AVIF images

Many image editing tools support exporting to AVIF. You can also use command line conversion tools or web based open sourceOpen Source Open Source denotes software for which the original source code is made freely available and may be redistributed and modified. Open Source **must be** delivered via a licensing model, see GPL. tools like Squoosh. Once you save your images as AVIF, upload them to WordPress and use them like you would any other image. WordPress can also create AVIFs for you automatically, for more details, see the FAQ item below.

Using AVIF images in WordPress

AVIF images work like any other image format in WordPress, with a few important notes:

AVIF in WordPress depends on support in your web server’s image processing library (WordPress has built-in support for both Imagick and LibGD for image processing). You can check for AVIF support in wp-adminadmin (and super admin) by visiting Tools -> Site Health, clicking the “Info” tab and expanding the “Media Handling” section, then finally looking for “AVIF” in the list of supported formats.

If your audience includes a significant number of users on an unsupported browser, either avoid using AVIF images, or enqueue a browser polyfill.

FAQ

Q: How do I adjust the compression level used for generated AVIF images?

The wp_editor_set_quality 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 to set the quality setting. The passed mime type enables setting by type, for example:

// Use a quality setting of 75 for AVIF images.
function filter_avif_quality( $quality, $mime_type ) {
if ( 'image/avif' === $mime_type ) {
return 75;
}
return $quality;
}
add_filter( 'wp_editor_set_quality', 'filter_avif_quality', 10, 2 );

How can I output lossless AVIF images?

Using a compression level of 100 will set AVIF to its lossless mode.

Q: Can WordPress create AVIF images when I upload JPEGs?

Yes. Developers can use the image_editor_output_format filter to specify this type of transformation for uploads. Here is an example:

// Output AVIFs for uploaded JPEGs
function filter_image_editor_output_format( $formats ) {
$formats['image/jpeg'] = 'image/avif';
return $formats;
}
add_filter( 'image_editor_output_format', 'filter_image_editor_output_format' );

If I use WordPress multisitemultisite Used to describe a WordPress installation with a network of multiple blogs, grouped by sites. This installation type has shared users tables, and creates separate database tables for each blog (wp_posts becomes wp_0_posts). See also network, blog, site, will all my sites work with AVIF images?

No. Multisite stores the file types that users are allowed to upload when a site is created. We are still working on improving this in #53167. In the meantime, to ensure all existing sites on a networknetwork (versus site, blog) allow AVIF files, you can use the site_option filter in a network mu-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 to add avif to the allowed file types for all network sites:

// Ensure all network sites include AVIF support.
function filter_site_option_upload_filetypes( $filetypes ) {
$filetypes = explode( ' ', $filetypes );
if ( ! in_array( 'avif', $filetypes, true ) ) {
$filetypes[] = 'avif';
}
return implode( ' ', $filetypes );
}
add_filter( 'site_option_upload_filetypes', 'filter_site_option_upload_filetypes' );


Thanks to @stevenlinx and @westonruter for reviewing this post.

#6-5, #core-images, #dev-notes, #dev-notes-6-5, #images

Main query loop handling for block themes in 6.4

In WordPress 6.4, a change has been applied to how the main query loopLoop The Loop is PHP code used by WordPress to display posts. Using The Loop, WordPress processes each post to be displayed on the current page, and formats it according to how it matches specified criteria within The Loop tags. Any HTML or PHP code in the Loop will be processed on each post. https://codex.wordpress.org/The_Loop. is being handled for 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. themes. For singular content, the output of block templates (e.g. single.html or page.html) will be automatically wrapped in the main query loop.

Classic theme background

Historically, classic themes have included a main query loop (sometimes referred to as just “the loop”) in every template where any kind of WordPress posts from the database would be displayed. For reference, this is what the loop roughly looks like in most classic themes:

if ( have_posts() ) {
	while ( have_posts() ) {
		the_post();

		// Render the post.
	}
}

While the loop is primarily intended to iterate over the posts in a list of posts (such as the blogblog (versus network, site) or a categoryCategory The 'category' taxonomy lets you group posts / content together that share a common bond. Categories are pre-defined and broad ranging. archive), even for singular content (e.g. a single post or page) this loop has been historically required for various reasons. For example, the in_the_loop() function is used by many plugins to check whether a post is currently being output.

The loop in block themes

Block themes do not use PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 5.6.20 or higher templates, so they do not manually output a loop like the one shared previously. Instead, blocks are responsible for handling it, typically the “Query loop” block (core/query). It automatically takes care of rendering the loop correctly.

However, the core/query block is only intended for use in archives, or any content that displays a list of posts. It would not be suitable for singular content as it wraps the posts in list markup, which would be semantically incorrect. Furthermore, while using a loop on singular content has been common knowledge for classic theme developers, end users of WordPress may not be familiar with that concept, and it can certainly be confusing when you learn about it. In other words, there is not a good reason to bother WordPress end users with having to use blocks correctly to make sure the loop is entered.

However, this means that for singular content, there is no coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. block available for handling the loop. Prior to 6.4, the loop was still being established for most cases even on singular content, but it was using a problematic workaround that forced the loop to start based on specific post blocks like “Content” and “Featured imageFeatured image A featured image is the main image used on your blog archive page and is pulled when the post or page is shared on social media. The image can be used to display in widget areas on your site or in a summary list of posts.” being rendered (core/post-content and core/post-featured-image). This workaround was not reliable as the blocks could also be intentionally used outside of the main query loop, and therefore led to other bugs, such as, #58027.

Current solution

To address the above, two changes [56507] and [57019] were made as part of WordPress 6.4 to automatically wrap the entire block template in a main query loop under the following circumstances:

  • The current main query is for singular content (via the is_singular() function).
  • There is indeed only a single post in the main query result.
  • The current block template is part of the current theme.
    • This is almost always the case. An exception is plugins that may inject their own block templates for specific content.
  • There post is still available to render (via the have_posts() function).

This is generally considered safe because in this situation the loop only contains the single post anyway that the current block template is for. A search through the WordPress 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 directory has not found relevant blocks where this would pose a problem. However, if you are developing a custom block plugin that makes specific assumptions about the main query loop, it is recommended that you check your block’s implementation to be compatible with this change in WordPress 6.4. This may especially be relevant for custom blocks that can be used alternatively to the “Query loop” block from WordPress core.

For example, if your custom block contains a main query loop similar to the example shown before, you could update it as follows to maintain compatibility with the new behavior:

if ( is_singular() && in_the_loop() ) {
	// Render the post.
} elseif ( have_posts() ) {
	while ( have_posts() ) {
		the_post();

		// Render the post.
	}
}

Please visit #58154 and #59225 for additional context on these changes.

Props to @gziolo and @webcommsat for peer review.

#6-4, #dev-notes, #dev-notes-6-4

Framework for storing revisions of Post Meta in 6.4

RevisionsRevisions The WordPress revisions system stores a record of each saved draft or published update. The revision system allows you to see what changes were made in each revision by dragging a slider (or using the Next/Previous buttons). The display indicates what has changed in each revision. are now supported for post metaMeta Meta is a term that refers to the inside workings of a group. For us, this is the team that works on internal WordPress sites like WordCamp Central and Make WordPress. on an opt-in basis. This feature is currently used by coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. for footnotes – enabling footnotes to be correctly previewed, stored in autosaved and stored and retrieved from revisions. In the future, this may be expanded to the featured imageFeatured image A featured image is the main image used on your blog archive page and is pulled when the post or page is shared on social media. The image can be used to display in widget areas on your site or in a summary list of posts. which is also stored in meta.

When registering a new meta field with register_meta or register_post_meta, the $args parameter accepts a new revisions_enabled argument. This defaults to false—set to true to opt in to revisions for this meta field. Note that the object type the meta is being created for must have revision support in order for the meta field to have revisions enabled. Attempting to enable meta revisions for a post type that doesn’t support revisions will result in a doing_it_wrong warning.

This feature is designed to lay the groundwork for future improvements to GutenbergGutenberg The Gutenberg project is the new Editor Interface for WordPress. The editor improves the process and experience of creating new content, making writing rich content much simpler. It uses ‘blocks’ to add richness rather than shortcodes, custom HTML etc. https://wordpress.org/gutenberg/ as part of Phase 3: Collaboration. Care has been made to do this in a backwards compatible manner, but if you are unhooking anything in the post save/update process, please read this entire note and test your code before WordPress 6.4 is released

A new wp_post_revision_meta_keys 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. is introduced, which can be used to filter the list of meta fields to be revisioned. The post type is also passed to the filter, which can be used to adjust revisioning of meta fields.

// Example to add a `my_product_price` meta field to revisions.
function enable_revisions_for_product_price_field( $revisioned_keys ) {
	if ( ! in_array( 'my_product_price', $revisioned_keys, true ) ) {
		$revisioned_keys[] = 'product_price';
	}
	return $revisioned_keys;
}
add_filter( 'wp_post_revision_meta_keys', 'enable_revisions_for_product_price_field' );

The _wp_put_post_revision action, which fires once a revision is saved, includes a new parameter—the post ID associated with the revision. This is used by core to copy the meta data from the main post to the revision.

The wp_creating_autosave action, which fires before an autosave is stored, includes a new parameter `$is_update`, added to indicate if the autosave is being updated or was newly created.

Meta revisions are also stored for autosaves and when previewing posts, fixing a long standing bugbug A bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority. (#20299) that caused live post meta to be overwritten when post meta was previewed. This is because using update_post_meta to save meta to a revision actually resulted in it being saved to the parent post. With this change, developers can now opt in to meta revisions and core will correctly attach the meta data to the revision. Meta that has revisions is also restored when restoring an autosave or revision. 

The REST APIREST API The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think “phone app” or “website”) can communicate with the data store (think “database” or “file system”) https://developer.wordpress.org/rest-api/. revisions endpoint now returns meta when stored as part of a revision and the autosaves endpoint now handles storing of meta.

The new meta revision features are enabled in core via hooksHooks In WordPress theme and development, hooks are functions that can be applied to an action or a Filter in WordPress. Actions are functions performed when a certain event occurs in WordPress. Filters allow you to modify certain functions. Arguments used to hook both filters and actions look the same.; removing the hooks will disable the features:

To disable post meta revisions entirely, use:

remove_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert', 9, 3 );

To disable storing post meta on autosaves, use:

remove_action( 'wp_creating_autosave', 'wp_autosave_post_revisioned_meta_fields' );

To disable restoring meta when restoring revisions or autosaves, use:

remove_action( 'wp_restore_post_revision', 'wp_restore_post_revision_meta', 10, 2 );

Important note: as part of this change, the timing for the storing of post revisions has been changed. Previously, revisions were created before post meta had been saved by the REST API on the post_updated hook.  Revisions are now hooked on the wp_after_insert_post action, fired after post meta has been saved. 

Special backwards compatibility consideration has been given to plugins that may be unhooking post_updated from wp_save_post_revision to disable revisions—in that case, core ensures revisions are still not created.

Note that if you are querying for meta directly, your query may need to be adjusted to take into account the possibility of meta values tied to revisions by making sure your query includes the post_parent field.


Props to @westonruter, @jorbin and @webcommsat for reviewing.

#6-4, #dev-notes, #dev-notes-6-4

WordPress 6.4 Field Guide

This guide shares more in-depth changes that you will find in 6.4 and is published in the Release Candidaterelease candidate One of the final stages in the version release cycle, this version signals the potential to be a final release to the public. Also see alpha (beta). cycle to help inform WordPress developers, extenders, and others. The release squad and many contributors across the global project have worked to bring these changes, and you can follow this work and add to the contribution using the ticketticket Created for both bug reports and feature development on the bug tracker. systems in Trac 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/.

Some fascinating stats! TracTrac An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress. has about 268 tickets, 113 of which are enhancements and feature requests, 134 bug fixes, and 21 other tasks. This time, there are more than 64 tickets with a focus on performance, 17 for accessibility, and 16 for modernizing code and applying coding standards. GutenbergGutenberg The Gutenberg project is the new Editor Interface for WordPress. The editor improves the process and experience of creating new content, making writing rich content much simpler. It uses ‘blocks’ to add richness rather than shortcodes, custom HTML etc. https://wordpress.org/gutenberg/’s GitHub repo brings more than 1,400 updates/changes, providing to coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. more than 420 enhancements, 445 bugbug A bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority. fixes, and 42 accessibility improvements.

Changes in 6.4 are spread across about 45 core components. Below is the breakdown of the most important ones.

You can also find all Developer Notes relating to 6.4 as they continue to be published until the release goes live. You can follow them using this tag.

Updated Nov 14, 2023: overview posts available on accessibility and performance improvements in 6.4.

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

WordPress 6.4 is bringing six Gutenberg releases into the core – 16.2, 16.3, 16.4, 16.5, 16.6, 16.7. You will find Block HooksHooks In WordPress theme and development, hooks are functions that can be applied to an action or a Filter in WordPress. Actions are functions performed when a certain event occurs in WordPress. Filters allow you to modify certain functions. Arguments used to hook both filters and actions look the same., the ability to register their own media categories, changes to the @wordpress/components package, updates for the user interface components, and many other changes.

WordPress 6.4 introduces Block Hooks (#53987), a feature that provides an extensibility mechanism for Block Themes. This is the first step in emulating WordPress’ Hooks concept that allows developers to extend Classic Themes using filters and actions.

From WordPress 6.4, extenders can register their own inserter media categories and provide users with more options from which to choose.

6.4 brings in a number of notable changes to the @wordpress/components package.

There are a number of other changes, including a new background image block support, fluid typography, disabled layout controls globally or on a block basis by theme.json, Stabilized APIs for InnerBlocks, and much more.

New dev notedev note Each important change in WordPress Core is documented in a developers note, (usually called dev note). Good dev notes generally include a description of the change, the decision that led to this change, and a description of how developers are supposed to work with that change. Dev notes are published on Make/Core blog during the beta phase of WordPress release cycle. Publishing dev notes is particularly important when plugin/theme authors and WordPress developers need to be aware of those changes.In general, all dev notes are compiled into a Field Guide at the beginning of the release candidate phase. (November 13, 2023) – For singular content, the output of block templates, for example, (single.html or page.html) will be automatically wrapped in the main query loopLoop The Loop is PHP code used by WordPress to display posts. Using The Loop, WordPress processes each post to be displayed on the current page, and formats it according to how it matches specified criteria within The Loop tags. Any HTML or PHP code in the Loop will be processed on each post. https://codex.wordpress.org/The_Loop..

Adminadmin (and super admin) notices

Two new functions abstract the HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. markup generation to reduce the maintenance burden, encourage consistency, and enable argument and message filtering for all admin notices used widely in WordPress Core and the extender community.

General

A developer note will be added later on the following change:

Introduce wp_trigger_error() to complement _doing_it_wrong() #57686

HTML 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.

WordPress 6.4 includes continued development of the HTML API, including the introduction of a minimal HTML Processor with the concept of breadcrumbs, and makes it possible to, for example, search for images that are direct children of a DIV.

Also included in 6.4, is the addition of a couple of CSSCSS Cascading Style Sheets./class helpers in the Tag Processor, which will make it possible to search for a tagtag A directory in Subversion. WordPress uses tags to store a single snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version control systems. (Not to be confused with post tags.) containing more than one class name, or to search for a tag not containing a given class name.

Media

New WordPress installations will now have attachment pages fully disabled for new sites. This will benefit SEO by avoiding attachment pages created by default, which were indexed by search engines and could have led to bad results for users and site owners. The change introduces a wp_attachment_pages_enabled database option to control the attachment pages’ behavior. In the dev note, there is information on how to update existing sites.

Additional performance improvements

A significant part of the 6.4 release brings performance improvements and greater efficiency to WordPress. An overview post on performance improvements in 6.4 is also available.

New functions get_options(), wp_prime_option_caches(), and wp_set_option_autoload_values() allow an enhanced performance of retrieving options from the database.

WordPress 6.4 brings many improvements to template loading.

  • Performance gains have been achieved by introducing caching using an object cache in a new method called WP_Theme::get_block_patterns().
  • Unnecessary checks were removed if a theme file existed in the theme functions that enhanced efficiency and performance. These improvements in the Themes API mean the current theme’s stylesheet directory is checked to ensure it matches the template directory, before further file existence checks are done. Improvements are also in the performance of get_block_theme_folders(). This is through a new method, WP_Theme::get_block_template_folders(), and improved error handling. The result is a quicker and more efficient lookup of block template folders within themes. WordPress developers and users can anticipate improved performance, reduced I/O overhead, and a smoother experience when working with block themes.

Improved image loading

Several enhancements to the wp_get_loading_optimization_attributes() function, which provides a central place to manage loading optimization attributes, specifically for images and iframes. 

Script Loader

In WordPress 6.4, script loading strategies are now employed for frontend scripts in core and bundled themes. For the most part, the defer loading strategy is used since it is more consistent in its loading behavior, in that a defer script always executes once the DOM has loaded; a script with async may actually block rendering if it is already cached. Additionally, loading with defer has been moved from the footer to the head so that they are discovered earlier while the document is loading and can execute sooner once the document is loaded.

Style loading

This dev note highlights the changes made in WordPress 6.4 to style loading. The main focus of the changes was to replace manually created style tags printed at the wp_head action with calls to wp_add_inline_style().

More performance-related changes

  • TaxonomyTaxonomy A taxonomy is a way to group things together. In WordPress, some common taxonomies are category, link, tag, or post format. https://codex.wordpress.org/Taxonomies#Default_Taxonomies.: The double sanitization in the get_term function has been stopped. This will prevent the unnecessary calls to sanitize_term, which was detrimental to performance. Trac ticket #58329.
  • Themes: The TEMPLATEPATH and STYLESHEETPATH constants have been deprecated. get_template_directory() and get_stylesheet_directory() should be used instead. Trac ticket #18298

And there’s more!

Some of the additional changes in 6.4 to highlight.

External libraries

jQuery has been updated to version 3.7.1. This release fixes a regressionregression A software bug that breaks or degrades something that previously worked. Regressions are often treated as critical bugs or blockers. Recent regressions may be given higher priorities. A "3.6 regression" would be a bug in 3.6 that worked as intended in 3.5. from jQuery 3.6.0 that resulted in rounded dimensions for elements in Chrome and Safari. Also, a (mostly) internal Sizzle method, jQuery.find.tokenize that was on the jQuery object was accidentally removed when they removed Sizzle in jQuery 3.7.0. That method has been restored.

Trac Ticket #59322

Users

WordPress 6.4 brings a number of key improvements to the HTML markup of the wp-login.php page to make its structure more optimal and allow developers to have more customized individual styling flexibility. #30685

Clarify the “Add New” links in the Admin for better accessibilityAccessibility Accessibility (commonly shortened to a11y) refers to the design of products, devices, services, or environments for people with disabilities. The concept of accessible design ensures both “direct access” (i.e. unassisted) and “indirect access” meaning compatibility with a person’s assistive technology (for example, computer screen readers). (https://en.wikipedia.org/wiki/Accessibility)

In WordPress 6.4, the default values for the add_new label changed to include the type of content. This matches add_new_item and provides more context for better accessibility. The default value is ‘Add New Type’ for both hierarchical and non-hierarchical types. If you’ve previously used a label such as:

'add_new' => _x( 'Add New', 'Book', 'my-plugin' )

you are encouraged to change it to

'add_new' => __( 'Add New Book', 'my-plugin' )

Trac ticket #47125

Props to @swissspidy for this dev note.

Integration testing requirement change

PHPUnit Polyfills 1.1.0 is required for running integration tests with WordPress 6.4.

Trac ticket #59510

Props to @jrf for the information for this dev note.

HTTPHTTP HTTP is an acronym for Hyper Text Transfer Protocol. HTTP is the underlying protocol used by the World Wide Web and this protocol defines how messages are formatted and transmitted, and what actions Web servers and browsers should take in response to various commands. API

  • HTTP API: WP_Http_Curl and WP_Http_Streams classes and filters have been deprecated as these classes have not been used in WordPress Core since the introduction of the Requests library. Trac ticket #58705

RevisionsRevisions The WordPress revisions system stores a record of each saved draft or published update. The revision system allows you to see what changes were made in each revision by dragging a slider (or using the Next/Previous buttons). The display indicates what has changed in each revision.

Revisions are now supported for post metaMeta Meta is a term that refers to the inside workings of a group. For us, this is the team that works on internal WordPress sites like WordCamp Central and Make WordPress. on an opt-in basis. Trac ticket #20564


Props to @costdev, @bph, @nalininonstopnewsuk, @codente, @spacedmonkey, @desrosj, @flixos90 for input on this Field GuideField guide The field guide is a type of blogpost published on Make/Core during the release candidate phase of the WordPress release cycle. The field guide generally lists all the dev notes published during the beta cycle. This guide is linked in the about page of the corresponding version of WordPress, in the release post and in the HelpHub version page., and @joemcgill, @clarkeemily, @cbringmann, @akshaya, @611shabnam, and @priethor for peer review.
Thank you to all those involved in collating, writing, and editing developer notes relating to 6.4.

#6-4, #dev-notes, #dev-notes-6-4, #field-guide

Updates to the HTML API in 6.4

WordPress 6.4 includes continued development of the HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. 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., including the introduction of a minimal HTML Processor in #58517 and the addition of a couple of CSSCSS Cascading Style Sheets./class helpers in the Tag Processor in #59209.

A minimal HTML Processor and its breadcrumbs.

When the HTML Processor was introduced in WordPress 6.2, it carried the stipulation that it did not understand HTML structure. While this is an intentional limitation so that the Tag Processor can maintain predictable performance and simplicity, it leaves certain conditions awkward and bugbug A bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority. prone. Consider, for example, the goal of finding an IMG element inside a surrounding DIV.

$processor = new WP_HTML_Tag_Processor( $html );

// Find the wrapping DIV element.
if ( ! $processor->next_tag( 'DIV' ) {
    return $html;
}

// Find the inner IMG element.
while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
    // Abort if leaving the DIV wrapper before finding the image.
    if ( 'DIV' === $processor->get_tag() && $processor->is_tag_closer() ) {
        return $html;
    }

    if ( 'IMG' === $processor->get_tag() ) {
        do_something_to_img();
        return $processor->get_updated_html();
    }
}

This code maintains the safety against unexpected HTML inputs in the way that a regex-based approach would not, but it’s already a bit cumbersome, and could be mistaken by an unexpected HTML layout. For example, it assumes that a closing DIV tagtag A directory in Subversion. WordPress uses tags to store a single snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version control systems. (Not to be confused with post tags.) will appear and that it will only appear after the IMG. It will fail to recognize the nested IMG in the following HTML because of the nested DIV before it.

<div class="profile">
    <div class="name">WordPress</div>
    <img src="wordpress-logo.png">
</div>

The new WP_HTML_Processor() class is being built in order to not only understand HTML syntax, but also its semantics – it understands HTML structure and all the quirks involved when discussing “malformed HTML.” In WordPress 6.4 the HTML Processor is available, and it adds a new concept of “breadcrumbs” to the API.

Breadcrumbs represent the path into an HTML document for a given element and will be familiar to those who navigate around a document in the browser development tools.

A browser displays the breadcrumbs for a selected element when inspecting the document.

Breadcrumbs always start with HTML as the start of the path, will list every ancestor of a given element, and include the element itself as the last segment in the path. The HTML Processor introduces a few ways to use these:

  • It’s possible to search for this structure by supplying array( 'breadcrumbs' => $breadcrumbs ) inside a call to next_tag().
  • The get_breadcrumbs() method reports the entire breadcrumb array from HTML to the matched element.
  • The matches_breadcrumbs( $breadcrumbs ) method indicates whether the matched element can be found at the given breadcrumbs.

Using the example above, it’s possible to search for images that are direct children of a DIV.

$processor = WP_HTML_Processor::create_fragment( $html );

if ( ! $processor->next_tag( array( 'breadcrumbs' => array( 'DIV', 'IMG' ) ) ) {
    return $html;
}

do_something_to_img();
return $processor->get_updated_html();

Properly handling this structure comes with additional costs, so the HTML Processor cannot provide the predictable performance that the Tag Processor can. It should still be fast enough to use though when rendering a 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.’s output.

One important note about using the HTML Processor is that it’s a work-in-progress and only supports a subset of allowable HTML. If it encounters a tag or a specific kind of HTML it doesn’t support, then it will abort processing to avoid corrupting your content. The HTML Processor reports if it gave up with the get_last_error() method. Currently, there’s only a small set of HTML that the HTML Processor supports, so don’t be surprised when it aborts before finding the tag you’re looking for. Each WordPress release will expand this support until it can read all possible HTML documents.

if ( $processor->next_tag( 'breadcrumbs' => array( 'figure', 'img' ) ) ) {
    // The tag was found in the HTML document.
    do_something_to_img();
    return $processor->get_updated_html();
}

if ( null === $processor->get_last_error() ) {
    // The tag was not in the HTML document.
} else {
    // It was not possible to determine if the tag was in the HTML document.
}

For more complicated queries, the matches_breadcrumbs() method can be used inside a next_tag() loopLoop The Loop is PHP code used by WordPress to display posts. Using The Loop, WordPress processes each post to be displayed on the current page, and formats it according to how it matches specified criteria within The Loop tags. Any HTML or PHP code in the Loop will be processed on each post. https://codex.wordpress.org/The_Loop.. The * value is a special wildcard term. It only matches one of any tag, so if no open element exists in its place then the match fails.

while ( $processor->next_tag( 'IMG' ) ) {
    // Skip images that are already inside a FIGURE element.
    if ( $processor->matches_breadcrumbs( array( 'FIGURE', 'IMG' ) ) ) {
        continue;
    }

    // Only process images that are great-grand-children of a BLOCKQUOTE element.
    if ( $processor->matches_breadcrumbs( array( 'BLOCKQUOTE', '*', '*', 'IMG' ) ) ) {
        do_something_to_img();
    }
}

The HTML Processor is a subclass of the Tag Processor and so retains all the underlying methods to read and modify the HTML. In the future, it will be possible to insert and remove entire tags and to read and modify the inner markup inside a tag. In WordPress 6.4, however, the only new feature is the concept of breadcrumbs.

CSS helpers for the Tag Processor

It’s been possible to search for a tag containing a specific class with next_tag( array( 'class_name' => $class_name ) ) but not possible to search for a tag containing more than one class name, or to search for a tag not containing a given class name. In WordPress 6.4, it’s possible to do this with the new has_class() method. This method does exactly what it sounds like: it reports if a matched tag contains the given class name in its class attribute.

$processor = new WP_HTML_Processor( $html );

while ( $processor->next_tag() ) {
    // Skip an element if it's not supposed to be processed.
    if ( $processor->has_class( 'data-wp-ignore' ) ) {
        continue;
    }

    if ( $processor->has_class( 'data-wp-context' ) && $processor->has_class( 'active' ) ) {
        // Process the context…
    }
}

The has_class() method knows how to split CSS class names properly. It’s valuable to rely on this method instead of manually processing the class attribute value to avoid several common pitfalls, especially when wanting to know about the presence or absence of multiple class names.


Props to @bph @codente @webcommsat and @nalininonstopnewsuk for peer review.

#6-4, #dev-notes, #dev-notes-6-4

Image loading optimization enhancements in 6.4

WordPress 6.4 comes with several enhancements to the wp_get_loading_optimization_attributes() function which was introduced in 6.3 as a central place to manage loading optimization attributes, specifically for images and iframes.

Quick recap: Loading optimization attributes are those such as loading="lazy" or fetchpriority="high", which can be added to certain HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. tags to optimize loading performance in the browser.

Simplified complex logic

The logic of the wp_get_loading_optimization_attributes() is generally quite complex, as it caters for several factors that influence when to apply certain loading optimization attributes. When the function was originally introduced, it was particularly hard to follow, since it included lots of early returns as well as closures. This was mostly due to maintaining its original code paths that came from the deprecated wp_get_loading_attr_default() function as much as possible to avoid breakage.

While the function logic is still complex in WordPress 6.4, it has been notably simplified, taking a more sequential and thus easier to follow approach. This facilitated the implementation of further enhancements such as those outlined below.

Please refer to 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. #58891 for additional details on these changes.

Managing the decoding="async" attribute

The decoding="async" attribute has been present on images by default since WordPress 6.1 (see Trac ticket #53232). As of WordPress 6.4, the logic for applying the attribute has been consolidated in the wp_get_loading_optimization_attributes() function, as the attribute is a perfect fit for that function.

Deprecated function

As part of that change, the wp_img_tag_add_decoding_attr() function has been deprecated, as its logic is now incorporated into wp_img_tag_add_loading_optimization_attrs(). Unless you are using the now deprecated function in your code, no changes should be needed after this update in regards to the decoding="async" attribute. The change merely enables control over the attribute in a more consistent manner (also see the new filters introduced below).

If you are using the deprecated function and want to use a fully backward compatible replacement, you can implement a custom wrapper function such as the following:

function myplugin_img_tag_add_decoding_attr( $image, $context ) {
	global $wp_version;

	// For WP >= 6.4.
	if ( version_compare( $wp_version, '6.4', '>=' ) ) {
		$image = wp_img_tag_add_loading_optimization_attrs( $image, $context );

		// Strip potential attributes added other than `decoding="async"`.
		return str_replace(
			array(
				' loading="lazy"',
				' fetchpriority="high"',
			),
			'',
			$image
		);
	}
 
    // For WP < 6.4.
    return wp_img_tag_add_decoding_attr( $image, $context );
}

Please refer to Trac ticket #58892 for additional details on these changes.

New filters to control loading optimization attributes

With WordPress 6.4, two filters have been added to wp_get_loading_optimization_attributes() which allow modifying or completely overriding the logic used to apply the loading optimization attributes:

  • The wp_get_loading_optimization_attributes 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 to modify the results from the WordPress coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. logic.
  • The pre_wp_get_loading_optimization_attributes filter can be used to use entirely custom logic and effectively short-circuit the core function by returning a value other than false.

Below are a few examples on how these two filters could be used.

Filter usage examples

You could use the wp_get_loading_optimization_attributes filter to ensure a specific image within post content receives the fetchpriority="high" attribute while other ones do not:

function set_fetchpriority_high_on_specific_image( $loading_attrs, $tag_name, $attr, $context ) {
	if ( 'img' === $tag_name ) {
		if (
			'the_content' === $context &&
			isset( $attr['src'] ) &&
			$attr['src'] === 'https://example.org/a-specific-image.jpg'
		) {
			$loading_attrs['fetchpriority'] = 'high';
		} else {
			unset( $loading_attrs['fetchpriority'] );
		}
	}
	return $loading_attrs;
}
add_filter(
	'wp_get_loading_optimization_attributes',
	'set_fetchpriority_high_on_specific_image',
	10,
	4
);

Alternatively, you could use the wp_get_loading_optimization_attributes filter to disable adding the fetchpriority="high" attribute entirely:

function disable_fetchpriority_high( $loading_attrs ) {
	unset( $loading_attrs['fetchpriority'] );
	return $loading_attrs;
}
add_filter(
	'wp_get_loading_optimization_attributes',
	'disable_fetchpriority_high'
);

You could implement entirely custom logic in 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 to detect which images appear in the viewport using client-side 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 or an external service and replace the function’s default logic with that:

function override_loading_optimization_attributes( $override, $tag_name, $attr, $context ) {
	// Bail if another filter callback already overrode this.
	if ( false !== $override ) {
		return $override;
	}

	// Use custom logic to determine whether image is LCP and whether it appears above the fold.
	if ( 'img' === $tag_name ) {
		$is_lcp      = custom_function_to_detect_whether_image_is_lcp_element( $attr );
		$in_viewport = custom_function_to_detect_whether_image_is_above_the_fold( $attr );

		/*
		 * Always add `decoding="async"`.
		 * Add `fetchpriority="high"` only to the LCP image.
		 * Add `loading="lazy"` to any image below the fold / outside the viewport.
		 */
		$loading_attrs = array( 'decoding' => 'async' );
		if ( $is_lcp ) {
			$loading_attrs['fetchpriority'] = 'high';
		} elseif ( ! $in_viewport ) {
			$loading_attrs['loading'] = 'lazy';
		}
		return $loading_attrs;
	}

	return $override;
}
add_filter(
	'pre_wp_get_loading_optimization_attributes',
	'override_loading_optimization_attributes',
	10,
	4
);

Please refer to Trac ticket #58893 for additional details on these changes.

Support for custom context values

As explained in the 6.3 dev note for the wp_get_loading_optimization_attributes() function, initially it only supported specific context values used by WordPress core. This made it confusing to write custom code making use of that function as you would have been forced to use a context string used elsewhere in core in order to get the performance benefits.

This limitation has been addressed in WordPress 6.4: The function now supports arbitrary contexts, and for the most part does not apply context-specific optimizations. There are two exceptions to this which are the “template_part_header” and “get_header_image_tag” contexts. Images within these contexts will always be interpreted to be in the 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.. The list of these two context strings is filterable with a new wp_loading_optimization_force_header_contexts filter, which allows images with other contexts to be always assumed to appear above the fold.

Context filter usage example

Below is an example: Assume that your plugin has a particular header image rendering function where it is safe to assume any image rendered with that function appears above the fold. You can rely on an arbitrary context specific to your function and then force WordPress core to consider images with that context to appear above the fold.

function force_myplugin_special_header_context_above_the_fold( $header_contexts ) {
	$header_contexts['myplugin_special_header'] = true;
	return $header_contexts;
}
add_filter(
	'wp_loading_optimization_force_header_contexts',
	'force_myplugin_special_header_context_above_the_fold'
);

Your image rendering function then needs to call wp_get_loading_optimization_attributes( 'img', $attr, 'myplugin_special_header' ) for the above filter to take effect.

Please refer to Trac ticket #58894 for additional details on these changes.

Hook priority change for wp_filter_content_tags()

The wp_filter_content_tags() was originally introduced in WordPress 5.5 as the foundation for lazy-loading as well as other optimizations for certain tags in a content blob. More recently a problem was identified that the function is called earlier than do_shortcode(), which means that any images added by shortcodes will not be able to make use of the performance benefits from wp_filter_content_tags().

To address that limitation, the hook priority with which wp_filter_content_tags() is hooked into the various filters (“the_content”, “the_excerpt”, “widget_text_content”, and “widget_block_content”) has been changed from the default 10 to 12. For context, the do_shortcode() function has hook priority 11.

While this is technically a breaking change, careful consideration and research across the WordPress plugin directory went into this decision. No plugins in the directory are affected by this change, given that there is only limited direct usage of the wp_filter_content_tags() function, and such usage has not been specific to the core filters. Still, in case you use the wp_filter_content_tags() function directly in your code, you may want to double check that this hook priority change does not result in a problem.

If your plugin processes custom content with the wp_filter_content_tags() function, it is encouraged to call that function after parsing other content such as blocks and shortcodes. The core change will not cause problems if that is currently not the case in your code, however it is recommended to have your custom logic follow a similar order.

Please refer to Trac ticket #58853 for additional details on these changes.

Props to @westonruter and @webcommsat for review and proofreading.

#6-4, #dev-notes, #dev-notes-6-4, #performance