Skip to content

Instantly share code, notes, and snippets.

@adamsilverstein
Last active August 9, 2023 21:00
Show Gist options
  • Save adamsilverstein/ec18b67a72ff74dec12624e989e23142 to your computer and use it in GitHub Desktop.
Save adamsilverstein/ec18b67a72ff74dec12624e989e23142 to your computer and use it in GitHub Desktop.
WordPress `wp_enqueue_script`: use `async` or `defer` in a backwards compatible manner
<?php
/**
* Register scripts with a `defer` or `async` strategy in a backwards compatible manner.
*
* From WordPress 6.3 onwards, the `wp_register_script` function accepts an `$args` array that
* can include a `strategy` key with a value of either `async` or `defer`.
*
* This helper function handles the backwards compatibility for older versions of WordPress. When a
* `strategy` key is present in the `$args` array (and is either `defer` or `async`), the
* `script_loader_tag` filter is used to add the attribute to the script tag.
*
* Note that for older versions of WordPress, dependencies is not considered - the attribute is added unconditionally.
*
* When support for WP<6.3 is no longer required, simply replace all instances of this function with
* `wp_register_script()`.
*
* @see wp_register_script()
*
* @param string $handle Name of the script. Should be unique.
* @param string|false $src Full URL of the script, or path of the script relative to the WordPress root directory.
* If source is set to false, script is an alias of other scripts it depends on.
* @param string[] $deps Optional. An array of registered script handles this script depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param array|bool $args {
* Optional. An array of additional script loading strategies. Default empty array.
* Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
*
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
* }
* @return bool Whether the script has been registered. True on success, false on failure.
*/
function wpnext_register_script( $handle, $src, $deps, $ver, $args ) {
// If >= 6.3, re-use wrapper function signature.
if ( version_compare( strtok( get_bloginfo( 'version' ), '-' ), '6.3', '>=' ) ) {
wp_register_script(
$handle,
$src,
$deps,
$ver,
$args
);
} else {
wp_register_script(
$handle,
$src,
$deps,
$ver,
isset( $args['in_footer'] ) ? $args['in_footer'] : false
);
if ( isset( $args['strategy'] ) ) {
wp_script_add_data( $handle, 'strategy', $args['strategy'] );
}
}
}
/**
* Enqueue scripts with a `defer` or `async` strategy in a backwards compatible manner.
*
* From WordPress 6.3 onwards, the `wp_enqueue_script` function accepts an `$args` array that
* can include a `strategy` key with a value of either `async` or `defer`.
*
* This helper function handles the backwards compatibility for older versions of WordPress. When a
* `strategy` key is present in the `$args` array (and is either `defer` or `async`), the
* `script_loader_tag` filter is used to add the attribute to the script tag. Note that
* for older versions of WordPress, dependency is not managed and the attribute is added unconditionally.
*
* When support for WP<6.3 is no longer required, simply replace all instances of this function with
* `wp_enqueue_script()`.
*
* @see wp_enqueue_script()
*
* @param string $handle Name of the script. Should be unique.
* @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
* Default empty.
* @param string[] $deps Optional. An array of registered script handles this script depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param array|bool $args {
* Optional. An array of additional script loading strategies. Default empty array.
* Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
*
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
* }
*/
function wpnext_enqueue_script( $handle, $src, $deps, $ver, $args ) {
wpnext_register_script( $handle, $src, $deps, $ver, $args );
wp_enqueue_script( $handle );
}
if ( version_compare( get_bloginfo( 'version' ), '6.3', '<' ) ) {
add_filter(
'script_loader_tag',
static function( $tag, $handle ) {
$strategy = wp_scripts()->get_data( $handle, 'strategy' );
if ( in_array( $strategy, array( 'async', 'defer' ), true ) && false === strpos( $tag, $strategy) ) {
$tag = str_replace( '<script ', '<script ' . $strategy . ' ', $tag );
}
return $tag;
},
10,
2
);
}
@westonruter
Copy link

@adamsilverstein Here's a hybrid of the two approaches: https://gist.github.com/westonruter/9694840a1cb940e66bdfb650e34c325e

Also I think the wrapper for wp_enqueue_script() can reduce a lot of code by simply calling wp_enqueue_script() after calling the wrapper for wp_register_script().

@adamsilverstein
Copy link
Author

@adamsilverstein Here's a hybrid of the two approaches: westonruter/9694840a1cb940e66bdfb650e34c325e

Great!

Also I think the wrapper for wp_enqueue_script() can reduce a lot of code by simply calling wp_enqueue_script() after calling the wrapper for wp_register_script().

Nice!

I'll update here based on that (except the WP_HTML_Tag_Processor bit so the code can work with older WP versions)

@westonruter
Copy link

@adamsilverstein I would just suggest making the replacement logic a bit more robust, for example:

$tag = str_replace( '<script ', '<script ' . $args['strategy'] . ' ', $tag );

This would prevent situations where the occurrence of ' src' somewhere in the string is mistakenly replaced, for example in this string:

<script src="/foo.js" class="foo-js src-local"></script>

@adamsilverstein
Copy link
Author

@adamsilverstein I would just suggest making the replacement logic a bit more robust, for example:

Good suggestion. Done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment