diff --git a/wp-includes/class-wp-scripts.php b/wp-includes/class-wp-scripts.php
index ddaa270c6d..857f1e948b 100644
--- a/wp-includes/class-wp-scripts.php
+++ b/wp-includes/class-wp-scripts.php
@@ -133,6 +133,24 @@ class WP_Scripts extends WP_Dependencies {
*/
private $type_attr = '';
+ /**
+ * Holds a mapping of dependents (as handles) for a given script handle.
+ * Used to optimize recursive dependency tree checks.
+ *
+ * @since 6.3.0
+ * @var array
+ */
+ private $dependents_map = array();
+
+ /**
+ * Holds a reference to the delayed (non-blocking) script loading strategies.
+ * Used by methods that validate loading strategies.
+ *
+ * @since 6.3.0
+ * @var string[]
+ */
+ private $delayed_strategies = array( 'defer', 'async' );
+
/**
* Constructor.
*
@@ -284,29 +302,27 @@ class WP_Scripts extends WP_Dependencies {
$ver = $ver ? $ver . '&' . $this->args[ $handle ] : $this->args[ $handle ];
}
- $src = $obj->src;
- $cond_before = '';
- $cond_after = '';
- $conditional = isset( $obj->extra['conditional'] ) ? $obj->extra['conditional'] : '';
+ $src = $obj->src;
+ $strategy = $this->get_eligible_loading_strategy( $handle );
+ $intended_strategy = (string) $this->get_data( $handle, 'strategy' );
+ $cond_before = '';
+ $cond_after = '';
+ $conditional = isset( $obj->extra['conditional'] ) ? $obj->extra['conditional'] : '';
+
+ if ( ! $this->is_delayed_strategy( $intended_strategy ) ) {
+ $intended_strategy = '';
+ }
if ( $conditional ) {
$cond_before = "\n";
}
- $before_handle = $this->print_inline_script( $handle, 'before', false );
- $after_handle = $this->print_inline_script( $handle, 'after', false );
+ $before_script = $this->get_inline_script_tag( $handle, 'before' );
+ $after_script = $this->get_inline_script_tag( $handle, 'after' );
- if ( $before_handle ) {
- $before_handle = sprintf( "\n", $this->type_attr, esc_attr( $handle ), $before_handle );
- }
-
- if ( $after_handle ) {
- $after_handle = sprintf( "\n", $this->type_attr, esc_attr( $handle ), $after_handle );
- }
-
- if ( $before_handle || $after_handle ) {
- $inline_script_tag = $cond_before . $before_handle . $after_handle . $cond_after;
+ if ( $before_script || $after_script ) {
+ $inline_script_tag = $cond_before . $before_script . $after_script . $cond_after;
} else {
$inline_script_tag = '';
}
@@ -333,7 +349,10 @@ class WP_Scripts extends WP_Dependencies {
*/
$srce = apply_filters( 'script_loader_src', $src, $handle );
- if ( $this->in_default_dir( $srce ) && ( $before_handle || $after_handle || $translations_stop_concat ) ) {
+ if (
+ $this->in_default_dir( $srce )
+ && ( $before_script || $after_script || $translations_stop_concat || $this->is_delayed_strategy( $strategy ) )
+ ) {
$this->do_concat = false;
// Have to print the so-far concatenated scripts right away to maintain the right order.
@@ -390,9 +409,16 @@ class WP_Scripts extends WP_Dependencies {
return true;
}
- $tag = $translations . $cond_before . $before_handle;
- $tag .= sprintf( "\n", $this->type_attr, $src, esc_attr( $handle ) );
- $tag .= $after_handle . $cond_after;
+ $tag = $translations . $cond_before . $before_script;
+ $tag .= sprintf(
+ "\n",
+ $this->type_attr,
+ $src, // Value is escaped above.
+ esc_attr( $handle ),
+ $strategy ? " {$strategy}" : '',
+ $intended_strategy ? " data-wp-strategy='{$intended_strategy}'" : ''
+ );
+ $tag .= $after_script . $cond_after;
/**
* Filters the HTML script tag of an enqueued script.
@@ -445,29 +471,97 @@ class WP_Scripts extends WP_Dependencies {
* Prints inline scripts registered for a specific handle.
*
* @since 4.5.0
+ * @deprecated 6.3.0 Use methods get_inline_script_tag() or get_inline_script_data() instead.
*
- * @param string $handle Name of the script to add the inline script to.
+ * @param string $handle Name of the script to print inline scripts for.
* Must be lowercase.
* @param string $position Optional. Whether to add the inline script
* before the handle or after. Default 'after'.
- * @param bool $display Optional. Whether to print the script
- * instead of just returning it. Default true.
- * @return string|false Script on success, false otherwise.
+ * @param bool $display Optional. Whether to print the script tag
+ * instead of just returning the script data. Default true.
+ * @return string|false Script data on success, false otherwise.
*/
public function print_inline_script( $handle, $position = 'after', $display = true ) {
- $output = $this->get_data( $handle, $position );
+ _deprecated_function( __METHOD__, '6.3.0', 'WP_Scripts::get_inline_script_data() or WP_Scripts::get_inline_script_tag()' );
+ $output = $this->get_inline_script_data( $handle, $position );
if ( empty( $output ) ) {
return false;
}
- $output = trim( implode( "\n", $output ), "\n" );
-
if ( $display ) {
- printf( "\n", $this->type_attr, esc_attr( $handle ), esc_attr( $position ), $output );
+ echo $this->get_inline_script_tag( $handle, $position );
+ }
+ return $output;
+ }
+
+ /**
+ * Gets data for inline scripts registered for a specific handle.
+ *
+ * @since 6.3.0
+ *
+ * @param string $handle Name of the script to get data for.
+ * Must be lowercase.
+ * @param string $position Optional. Whether to add the inline script
+ * before the handle or after. Default 'after'.
+ * @return string Inline script, which may be empty string.
+ */
+ public function get_inline_script_data( $handle, $position = 'after' ) {
+ $data = $this->get_data( $handle, $position );
+ if ( empty( $data ) || ! is_array( $data ) ) {
+ return '';
}
- return $output;
+ return trim( implode( "\n", $data ), "\n" );
+ }
+
+ /**
+ * Gets unaliased dependencies.
+ *
+ * An alias is a dependency whose src is false. It is used as a way to bundle multiple dependencies in a single
+ * handle. This in effect flattens an alias dependency tree.
+ *
+ * @since 6.3.0
+ *
+ * @param string[] $deps Dependency handles.
+ * @return string[] Unaliased handles.
+ */
+ private function get_unaliased_deps( array $deps ) {
+ $flattened = array();
+ foreach ( $deps as $dep ) {
+ if ( ! isset( $this->registered[ $dep ] ) ) {
+ continue;
+ }
+
+ if ( $this->registered[ $dep ]->src ) {
+ $flattened[] = $dep;
+ } elseif ( $this->registered[ $dep ]->deps ) {
+ array_push( $flattened, ...$this->get_unaliased_deps( $this->registered[ $dep ]->deps ) );
+ }
+ }
+ return $flattened;
+ }
+
+ /**
+ * Gets tags for inline scripts registered for a specific handle.
+ *
+ * @since 6.3.0
+ *
+ * @param string $handle Name of the script to get associated inline script tag for.
+ * Must be lowercase.
+ * @param string $position Optional. Whether to get tag for inline
+ * scripts in the before or after position. Default 'after'.
+ * @return string Inline script, which may be empty string.
+ */
+ public function get_inline_script_tag( $handle, $position = 'after' ) {
+ $js = $this->get_inline_script_data( $handle, $position );
+ if ( empty( $js ) ) {
+ return '';
+ }
+
+ $id = "{$handle}-js-{$position}";
+
+ return wp_get_inline_script_tag( $js, compact( 'id' ) );
}
/**
@@ -714,6 +808,199 @@ JS;
return false;
}
+ /**
+ * This overrides the add_data method from WP_Dependencies, to support normalizing of $args.
+ *
+ * @since 6.3.0
+ *
+ * @param string $handle Name of the item. Should be unique.
+ * @param string $key The data key.
+ * @param mixed $value The data value.
+ * @return bool True on success, false on failure.
+ */
+ public function add_data( $handle, $key, $value ) {
+ if ( ! isset( $this->registered[ $handle ] ) ) {
+ return false;
+ }
+
+ if ( 'strategy' === $key ) {
+ if ( ! empty( $value ) && ! $this->is_delayed_strategy( $value ) ) {
+ _doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ /* translators: 1: $strategy, 2: $handle */
+ __( 'Invalid strategy `%1$s` defined for `%2$s` during script registration.' ),
+ $value,
+ $handle
+ ),
+ '6.3.0'
+ );
+ return false;
+ } elseif ( ! $this->registered[ $handle ]->src && $this->is_delayed_strategy( $value ) ) {
+ _doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ /* translators: 1: $strategy, 2: $handle */
+ __( 'Cannot supply a strategy `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
+ $value,
+ $handle
+ ),
+ '6.3.0'
+ );
+ return false;
+ }
+ }
+ return parent::add_data( $handle, $key, $value );
+ }
+
+ /**
+ * Gets all dependents of a script.
+ *
+ * @since 6.3.0
+ *
+ * @param string $handle The script handle.
+ * @return string[] Script handles.
+ */
+ private function get_dependents( $handle ) {
+ // Check if dependents map for the handle in question is present. If so, use it.
+ if ( isset( $this->dependents_map[ $handle ] ) ) {
+ return $this->dependents_map[ $handle ];
+ }
+
+ $dependents = array();
+
+ // Iterate over all registered scripts, finding dependents of the script passed to this method.
+ foreach ( $this->registered as $registered_handle => $args ) {
+ if ( in_array( $handle, $args->deps, true ) ) {
+ $dependents[] = $registered_handle;
+ }
+ }
+
+ // Add the handles dependents to the map to ease future lookups.
+ $this->dependents_map[ $handle ] = $dependents;
+
+ return $dependents;
+ }
+
+ /**
+ * Checks if the strategy passed is a valid delayed (non-blocking) strategy.
+ *
+ * @since 6.3.0
+ *
+ * @param string $strategy The strategy to check.
+ * @return bool True if $strategy is one of the delayed strategies, otherwise false.
+ */
+ private function is_delayed_strategy( $strategy ) {
+ return in_array(
+ $strategy,
+ $this->delayed_strategies,
+ true
+ );
+ }
+
+ /**
+ * Gets the best eligible loading strategy for a script.
+ *
+ * @since 6.3.0
+ *
+ * @param string $handle The script handle.
+ * @return string The best eligible loading strategy.
+ */
+ private function get_eligible_loading_strategy( $handle ) {
+ $eligible = $this->filter_eligible_strategies( $handle );
+
+ // Bail early once we know the eligible strategy is blocking.
+ if ( empty( $eligible ) ) {
+ return '';
+ }
+
+ return in_array( 'async', $eligible, true ) ? 'async' : 'defer';
+ }
+
+ /**
+ * Filter the list of eligible loading strategies for a script.
+ *
+ * @since 6.3.0
+ *
+ * @param string $handle The script handle.
+ * @param string[]|null $eligible Optional. The list of strategies to filter. Default null.
+ * @param array
` instead of in the `
`. - * Default 'false'. + * @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 wp_register_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) { +function wp_register_script( $handle, $src, $deps = array(), $ver = false, $args = array() ) { + if ( ! is_array( $args ) ) { + $args = array( + 'in_footer' => (bool) $args, + ); + } _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); $wp_scripts = wp_scripts(); $registered = $wp_scripts->add( $handle, $src, $deps, $ver ); - if ( $in_footer ) { + if ( ! empty( $args['in_footer'] ) ) { $wp_scripts->add_data( $handle, 'group', 1 ); } - + if ( ! empty( $args['strategy'] ) ) { + $wp_scripts->add_data( $handle, 'strategy', $args['strategy'] ); + } return $registered; } @@ -331,6 +344,7 @@ function wp_deregister_script( $handle ) { * @see WP_Dependencies::enqueue() * * @since 2.1.0 + * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array. * * @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. @@ -340,24 +354,36 @@ function wp_deregister_script( $handle ) { * 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 bool $in_footer Optional. Whether to enqueue the script before `` instead of in the `
`. - * Default 'false'. + * @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 wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $in_footer = false ) { +function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $args = array() ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); $wp_scripts = wp_scripts(); - if ( $src || $in_footer ) { + if ( $src || ! empty( $args ) ) { $_handle = explode( '?', $handle ); + if ( ! is_array( $args ) ) { + $args = array( + 'in_footer' => (bool) $args, + ); + } if ( $src ) { $wp_scripts->add( $_handle[0], $src, $deps, $ver ); } - - if ( $in_footer ) { + if ( ! empty( $args['in_footer'] ) ) { $wp_scripts->add_data( $_handle[0], 'group', 1 ); } + if ( ! empty( $args['strategy'] ) ) { + $wp_scripts->add_data( $_handle[0], 'strategy', $args['strategy'] ); + } } $wp_scripts->enqueue( $handle ); diff --git a/wp-includes/version.php b/wp-includes/version.php index 4ecc1b38b7..38985acf6d 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.3-alpha-56032'; +$wp_version = '6.3-alpha-56033'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.