From e499918e17f173197a07fb0e198a89c269fa7387 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 15 Jun 2023 17:00:48 -0700 Subject: [PATCH 01/12] Refer to aliases instead of bundles --- src/wp-includes/class-wp-scripts.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 3a5fa6b3170ce..10cd0e0b67002 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -929,7 +929,7 @@ public function add_data( $handle, $key, $value ) { __METHOD__, sprintf( /* translators: 1: $strategy, 2: $handle */ - __( 'Cannot supply a strategy `%1$s` for script `%2$s` because it does not have a `src` value.' ), + __( 'Cannot supply a strategy `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ), $value, $handle ), @@ -1035,7 +1035,7 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che // Consider each dependent and check if it is delayed. foreach ( $dependents as $dependent ) { - // If the dependent script has no src (as it represents a script bundle), ignore it for consideration. + // If the dependent script has no src (as it represents an alias for a set of items), ignore it from consideration. if ( empty( $this->registered[ $dependent ]->src ) ) { continue; } @@ -1082,7 +1082,7 @@ private function get_eligible_loading_strategy( $handle ) { /* * Handle known blocking strategy scenarios. * - An empty strategy is synonymous with blocking. - * - A script bundle (where $src is false) must always be blocking since the after inline script cannot be + * - A script alias (where $src is false) must always be blocking since the after inline script cannot be * delayed as there is no external script tag and thus no load event at which the inline script can be run. */ if ( empty( $intended_strategy ) || empty( $this->registered[ $handle ]->src ) ) { From f3d3c44b12037a3625b3cc69869db51ccffeaff8 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 15 Jun 2023 22:23:40 -0700 Subject: [PATCH 02/12] Improve handling of dependency aliases --- src/wp-includes/class-wp-scripts.php | 111 +++++++++++++++---- tests/phpunit/tests/dependencies/scripts.php | 13 +-- 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 10cd0e0b67002..ad5093dfbd0c9 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -551,6 +551,33 @@ public function get_inline_script_data( $handle, $position = 'after' ) { 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 ( ! array_key_exists( $dep, $this->registered ) ) { + 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. * @@ -568,14 +595,20 @@ public function get_inline_script_tag( $handle, $position = 'after' ) { return ''; } - $id = "{$handle}-js-{$position}"; - $deps = $this->registered[ $handle ]->deps; - + $id = "{$handle}-js-{$position}"; if ( $this->should_delay_inline_script( $handle, $position ) ) { $attributes = array( 'id' => $id, 'type' => 'text/plain', ); + + /* + * Note that any dependency aliases need to be flattened because an alias is a bundle of dependencies + * and their handles won't appear on any specific scripts. Since no script will appear in the DOM for + * an alias, there won't be any way to keep track of when it has loaded. Therefore, we only keep track of + * the aliased dependencies (the leaf nodes of the alias dependency tree as it were). + */ + $deps = $this->get_unaliased_deps( $this->registered[ $handle ]->deps ); if ( $deps ) { $attributes['data-wp-deps'] = implode( ',', $deps ); } @@ -648,7 +681,18 @@ private function should_delay_inline_script( $handle, $position ) { * dependency's after inline script. */ foreach ( $deps as $dep ) { - if ( $this->is_delayed_strategy( $this->get_eligible_loading_strategy( $dep ) ) ) { + if ( ! array_key_exists( $dep, $this->registered ) ) { + continue; + } + + // If the dependency is an alias, look at its members. + if ( ! $this->registered[ $dep ]->src ) { + foreach ( $this->registered[ $dep ]->deps as $alias_dep ) { + if ( $this->is_delayed_strategy( $this->get_eligible_loading_strategy( $alias_dep ) ) ) { + return true; + } + } + } elseif ( $this->is_delayed_strategy( $this->get_eligible_loading_strategy( $dep ) ) ) { return true; } } @@ -1028,36 +1072,54 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che $checked[ $handle ] = true; $dependents = $this->get_dependents( $handle ); - // If there are no dependents remaining to consider, the script can be deferred. + // If there are no dependents remaining to consider, the script can be delayed. if ( empty( $dependents ) ) { return true; } // Consider each dependent and check if it is delayed. foreach ( $dependents as $dependent ) { - // If the dependent script has no src (as it represents an alias for a set of items), ignore it from consideration. - if ( empty( $this->registered[ $dependent ]->src ) ) { + if ( ! array_key_exists( $dependent, $this->registered ) ) { continue; } - // If the dependency is not enqueued, ignore it for consideration. + // If the dependency is not enqueued, exclude it from consideration. if ( ! $this->query( $dependent, 'enqueued' ) ) { continue; } - // If the dependent script is not using the defer or async strategy, no script in the chain is delayed. - $strategy = $this->get_data( $dependent, 'strategy' ); - if ( $async_only ) { - if ( 'async' !== $strategy ) { + // Handle script alias case (where it has no src). + if ( ! $this->registered[ $dependent ]->src ) { + // A script alias cannot be delayed if it has inline scripts since there is no load event. + if ( + $this->get_data( $dependent, 'before' ) || + $this->get_data( $dependent, 'after' ) + ) { return false; } - } elseif ( ! $this->is_delayed_strategy( $strategy ) ) { - return false; - } - // Recursively check all dependents. - if ( ! $this->has_only_delayed_dependents( $dependent, $async_only, $checked ) ) { - return false; + // Now check whether all members of the alias are delayed. + foreach ( $this->get_dependents( $dependent ) as $alias_dependent ) { + if ( ! $this->has_only_delayed_dependents( $alias_dependent, $async_only, $checked ) ) { + return false; + } + } + } else { + + // If the dependent script is not using the defer or async strategy, no script in the chain is delayed. + $strategy = $this->get_data( $dependent, 'strategy' ); + if ( $async_only ) { + if ( 'async' !== $strategy ) { + return false; + } + } elseif ( ! $this->is_delayed_strategy( $strategy ) ) { + return false; + } + + // Recursively check all dependents. + if ( ! $this->has_only_delayed_dependents( $dependent, $async_only, $checked ) ) { + return false; + } } } @@ -1073,7 +1135,7 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che * @return string $strategy The best eligible loading strategy. */ private function get_eligible_loading_strategy( $handle ) { - if ( ! isset( $this->registered[ $handle ] ) ) { + if ( ! array_key_exists( $handle, $this->registered ) ) { return ''; } @@ -1085,7 +1147,16 @@ private function get_eligible_loading_strategy( $handle ) { * - A script alias (where $src is false) must always be blocking since the after inline script cannot be * delayed as there is no external script tag and thus no load event at which the inline script can be run. */ - if ( empty( $intended_strategy ) || empty( $this->registered[ $handle ]->src ) ) { + if ( empty( $intended_strategy ) ) { + return ''; + } + + // Aliases that have before/after inline scripts can never be delayed since there is no load event. + if ( ! $this->registered[ $handle ]->src && ( + $this->get_data( $handle, 'before' ) || + $this->get_data( $handle, 'after' ) + ) + ) { return ''; } diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index 68615fbf50b7c..3d8e9ca387fa4 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -810,7 +810,6 @@ public function data_provider_to_test_various_strategy_dependency_chains() { $handle1 = 'blocking-bundle-of-none'; $handle2 = 'defer-dependent-of-blocking-bundle-of-none'; - // Note that jQuery is registered like this. wp_register_script( $handle1, false, array(), null ); $this->add_test_inline_script( $handle1, 'before' ); $this->add_test_inline_script( $handle1, 'after' ); @@ -831,7 +830,7 @@ public function data_provider_to_test_various_strategy_dependency_chains() { scriptEventLog.push( "defer-dependent-of-blocking-bundle-of-none: before inline" ) - HTML @@ -844,9 +843,9 @@ public function data_provider_to_test_various_strategy_dependency_chains() { $handle3 = 'blocking-bundle-member-two'; $handle4 = 'defer-dependent-of-blocking-bundle-of-two'; - wp_register_script( $handle1, false, array(), null ); - $this->enqueue_test_script( $handle2, 'blocking', array( $handle1 ) ); - $this->enqueue_test_script( $handle3, 'blocking', array( $handle1 ) ); + wp_register_script( $handle1, false, array( $handle2, $handle3 ), null ); + $this->enqueue_test_script( $handle2, 'blocking' ); + $this->enqueue_test_script( $handle3, 'blocking' ); $this->enqueue_test_script( $handle4, 'defer', array( $handle1 ) ); foreach ( array( $handle2, $handle3, $handle4 ) as $handle ) { @@ -873,7 +872,7 @@ public function data_provider_to_test_various_strategy_dependency_chains() { scriptEventLog.push( "defer-dependent-of-blocking-bundle-of-two: before inline" ) - HTML @@ -906,7 +905,7 @@ public function data_provider_to_test_various_strategy_dependency_chains() { scriptEventLog.push( "defer-dependent-of-defer-bundle-of-none: before inline" ) - HTML From b06affea012e74b259524d058de128652fcc1028 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 15 Jun 2023 22:34:28 -0700 Subject: [PATCH 03/12] Add key exists check to mirror parent add_data method --- src/wp-includes/class-wp-scripts.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index ad5093dfbd0c9..93630a7a5af2a 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -955,6 +955,10 @@ public function in_default_dir( $src ) { * @return bool True on success, false on failure. */ public function add_data( $handle, $key, $value ) { + if ( ! array_key_exists( $handle, $this->registered ) ) { + return false; + } + if ( 'strategy' === $key ) { if ( ! empty( $value ) && ! $this->is_delayed_strategy( $value ) ) { _doing_it_wrong( @@ -968,7 +972,7 @@ public function add_data( $handle, $key, $value ) { '6.3.0' ); return false; - } elseif ( empty( $this->registered[ $handle ]->src ) && $this->is_delayed_strategy( $value ) ) { + } elseif ( ! $this->registered[ $handle ]->src && $this->is_delayed_strategy( $value ) ) { _doing_it_wrong( __METHOD__, sprintf( From bbec078b275a64b582d9cc23a54b168dc7632562 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 15 Jun 2023 22:36:37 -0700 Subject: [PATCH 04/12] Add test case to show deferral of jquery --- tests/phpunit/tests/dependencies/scripts.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index 3d8e9ca387fa4..fbb242b38f1bf 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -1045,6 +1045,22 @@ public function data_provider_to_test_various_strategy_dependency_chains() { +HTML + , + ), + 'jquery-deferred' => array( + 'set_up' => function () { + $wp_scripts = wp_scripts(); + wp_default_scripts( $wp_scripts ); + foreach ( $wp_scripts->registered['jquery']->deps as $jquery_dep ) { + $wp_scripts->registered[ $jquery_dep ]->add_data( 'strategy', 'defer' ); + } + wp_enqueue_script( 'theme-functions', 'https://example.com/theme-functions.js', array( 'jquery' ), null, array( 'strategy' => 'defer' ) ); + }, + 'expected_markup' => << + + HTML , ), From 47b2cbccfc7641d141994c5ec0a28e356d12da3e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 16 Jun 2023 10:24:13 -0700 Subject: [PATCH 05/12] Use isset() instead of array_key_exists() for perf --- src/wp-includes/class-wp-scripts.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 93630a7a5af2a..8e623e2a7d668 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -955,7 +955,7 @@ public function in_default_dir( $src ) { * @return bool True on success, false on failure. */ public function add_data( $handle, $key, $value ) { - if ( ! array_key_exists( $handle, $this->registered ) ) { + if ( ! isset( $this->registered[ $handle ] ) ) { return false; } @@ -1022,7 +1022,7 @@ public function has_delayed_inline_script( array $handles ) { */ private function get_dependents( $handle ) { // Check if dependents map for the handle in question is present. If so, use it. - if ( array_key_exists( $handle, $this->dependents_map ) ) { + if ( isset( $this->dependents_map[ $handle ] ) ) { return $this->dependents_map[ $handle ]; } @@ -1069,7 +1069,7 @@ private function is_delayed_strategy( $strategy ) { */ private function has_only_delayed_dependents( $handle, $async_only = false, $checked = array() ) { // If this node was already checked, this script can be delayed and the branch ends. - if ( array_key_exists( $handle, $checked ) ) { + if ( isset( $checked[ $handle ] ) ) { return true; } @@ -1083,7 +1083,7 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che // Consider each dependent and check if it is delayed. foreach ( $dependents as $dependent ) { - if ( ! array_key_exists( $dependent, $this->registered ) ) { + if ( ! isset( $this->registered[ $dependent ] ) ) { continue; } @@ -1139,7 +1139,7 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che * @return string $strategy The best eligible loading strategy. */ private function get_eligible_loading_strategy( $handle ) { - if ( ! array_key_exists( $handle, $this->registered ) ) { + if ( ! isset( $this->registered[ $handle ] ) ) { return ''; } From 805370e7c1f12193355f7722e67d20322c5d5af9 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 16 Jun 2023 10:29:54 -0700 Subject: [PATCH 06/12] Use more isset() instead of array_key_exists() for perf --- src/wp-includes/class-wp-scripts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 8e623e2a7d668..25ad90d29ce26 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -565,7 +565,7 @@ public function get_inline_script_data( $handle, $position = 'after' ) { private function get_unaliased_deps( array $deps ) { $flattened = array(); foreach ( $deps as $dep ) { - if ( ! array_key_exists( $dep, $this->registered ) ) { + if ( ! isset( $this->registered[ $dep ] ) ) { continue; } @@ -681,7 +681,7 @@ private function should_delay_inline_script( $handle, $position ) { * dependency's after inline script. */ foreach ( $deps as $dep ) { - if ( ! array_key_exists( $dep, $this->registered ) ) { + if ( ! isset( $this->registered[ $dep ] ) ) { continue; } From c28fa9aa4cc13a7b1d376ba5e8a7b62c90d2d814 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 16 Jun 2023 10:40:01 -0700 Subject: [PATCH 07/12] Simplify conditionals by using ternary --- src/wp-includes/class-wp-scripts.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 25ad90d29ce26..1ca14c1254899 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -1112,11 +1112,10 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che // If the dependent script is not using the defer or async strategy, no script in the chain is delayed. $strategy = $this->get_data( $dependent, 'strategy' ); - if ( $async_only ) { - if ( 'async' !== $strategy ) { - return false; - } - } elseif ( ! $this->is_delayed_strategy( $strategy ) ) { + if ( $async_only ? + 'async' !== $strategy : + ! $this->is_delayed_strategy( $strategy ) + ) { return false; } From e72fcc0a72f4bd7b2bca4a6a8eaa2bf5c216b27c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 16 Jun 2023 10:46:12 -0700 Subject: [PATCH 08/12] Factor out logic into has_inline_script helper method --- src/wp-includes/class-wp-scripts.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 1ca14c1254899..73d8ad91353e5 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -1095,10 +1095,7 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che // Handle script alias case (where it has no src). if ( ! $this->registered[ $dependent ]->src ) { // A script alias cannot be delayed if it has inline scripts since there is no load event. - if ( - $this->get_data( $dependent, 'before' ) || - $this->get_data( $dependent, 'after' ) - ) { + if ( $this->has_inline_script( $dependent ) ) { return false; } @@ -1155,11 +1152,7 @@ private function get_eligible_loading_strategy( $handle ) { } // Aliases that have before/after inline scripts can never be delayed since there is no load event. - if ( ! $this->registered[ $handle ]->src && ( - $this->get_data( $handle, 'before' ) || - $this->get_data( $handle, 'after' ) - ) - ) { + if ( ! $this->registered[ $handle ]->src && $this->has_inline_script( $handle ) ) { return ''; } @@ -1176,6 +1169,19 @@ private function get_eligible_loading_strategy( $handle ) { return ''; } + /** + * 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. + * @return bool Whether the handle has an inline script (either before or after). + */ + private function has_inline_script( $handle ) { + return $this->get_data( $handle, 'before' ) || $this->get_data( $handle, 'after' ); + } + /** * Resets class properties. * From 57582d75b1b312c88cb409ab2f33dbae17452c76 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 16 Jun 2023 11:15:20 -0700 Subject: [PATCH 09/12] Account for aliases having nested aliases --- src/wp-includes/class-wp-scripts.php | 3 +- tests/phpunit/tests/dependencies/scripts.php | 51 ++++++++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 73d8ad91353e5..70c9514b11a98 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -687,7 +687,7 @@ private function should_delay_inline_script( $handle, $position ) { // If the dependency is an alias, look at its members. if ( ! $this->registered[ $dep ]->src ) { - foreach ( $this->registered[ $dep ]->deps as $alias_dep ) { + foreach ( $this->get_unaliased_deps( $this->registered[ $dep ]->deps ) as $alias_dep ) { if ( $this->is_delayed_strategy( $this->get_eligible_loading_strategy( $alias_dep ) ) ) { return true; } @@ -1106,7 +1106,6 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che } } } else { - // If the dependent script is not using the defer or async strategy, no script in the chain is delayed. $strategy = $this->get_data( $dependent, 'strategy' ); if ( $async_only ? diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index fbb242b38f1bf..d562f1f7cd587 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -633,15 +633,15 @@ public function test_has_only_delayed_dependents( $set_up, $async_only, $expecte } /** - * Enqueue test script with before/after inline scripts. + * Register test script. * * @param string $handle Dependency handle to enqueue. * @param string $strategy Strategy to use for dependency. * @param string[] $deps Dependencies for the script. * @param bool $in_footer Whether to print the script in the footer. */ - protected function enqueue_test_script( $handle, $strategy, $deps = array(), $in_footer = false ) { - wp_enqueue_script( + protected function register_test_script( $handle, $strategy, $deps = array(), $in_footer = false ) { + wp_register_script( $handle, add_query_arg( array( @@ -657,6 +657,19 @@ protected function enqueue_test_script( $handle, $strategy, $deps = array(), $in } } + /** + * Enqueue test script. + * + * @param string $handle Dependency handle to enqueue. + * @param string $strategy Strategy to use for dependency. + * @param string[] $deps Dependencies for the script. + * @param bool $in_footer Whether to print the script in the footer. + */ + protected function enqueue_test_script( $handle, $strategy, $deps = array(), $in_footer = false ) { + $this->register_test_script( $handle, $strategy, $deps, $in_footer ); + wp_enqueue_script( $handle ); + } + /** * Adds test inline script. * @@ -1061,6 +1074,38 @@ public function data_provider_to_test_various_strategy_dependency_chains() { +HTML + , + ), + 'nested-aliases' => array( + 'set_up' => function () { + $outer_alias_handle = 'outer-bundle-of-two'; + $inner_alias_handle = 'inner-bundle-of-two'; + + // The outer alias contains a blocking member, as well as a nested alias that contains defer scripts. + wp_register_script( $outer_alias_handle, false, array( $inner_alias_handle, 'outer-bundle-leaf-member' ), null ); + $this->register_test_script( 'outer-bundle-leaf-member', 'blocking', array() ); + + // Inner alias only contains delay scripts. + wp_register_script( $inner_alias_handle, false, array( 'inner-bundle-member-one', 'inner-bundle-member-two' ), null ); + $this->register_test_script( 'inner-bundle-member-one', 'defer', array() ); + $this->register_test_script( 'inner-bundle-member-two', 'defer', array() ); + + $this->enqueue_test_script( 'defer-dependent-of-nested-aliases', 'defer', array( $outer_alias_handle ) ); + $this->add_test_inline_script( 'defer-dependent-of-nested-aliases', 'before' ); + $this->add_test_inline_script( 'defer-dependent-of-nested-aliases', 'after' ); + }, + 'expected_markup' => $this->get_delayed_inline_script_loader_script_tag() . << + + + + + HTML , ), From 4af0d235f52a4b7cfb4432fdc6123494c47cdbd4 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 16 Jun 2023 11:50:30 -0700 Subject: [PATCH 10/12] Avoid needlessly recursing among dependencies when only looking at dependents --- src/wp-includes/class-wp-scripts.php | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 70c9514b11a98..eaede952cdd01 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -1092,19 +1092,12 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che continue; } - // Handle script alias case (where it has no src). + // Handle script alias case (where it has no src). Here, the strategy doesn't matter, but only whether there are inline scripts. if ( ! $this->registered[ $dependent ]->src ) { // A script alias cannot be delayed if it has inline scripts since there is no load event. if ( $this->has_inline_script( $dependent ) ) { return false; } - - // Now check whether all members of the alias are delayed. - foreach ( $this->get_dependents( $dependent ) as $alias_dependent ) { - if ( ! $this->has_only_delayed_dependents( $alias_dependent, $async_only, $checked ) ) { - return false; - } - } } else { // If the dependent script is not using the defer or async strategy, no script in the chain is delayed. $strategy = $this->get_data( $dependent, 'strategy' ); @@ -1114,11 +1107,11 @@ private function has_only_delayed_dependents( $handle, $async_only = false, $che ) { return false; } + } - // Recursively check all dependents. - if ( ! $this->has_only_delayed_dependents( $dependent, $async_only, $checked ) ) { - return false; - } + // Recursively check all dependents. + if ( ! $this->has_only_delayed_dependents( $dependent, $async_only, $checked ) ) { + return false; } } @@ -1173,8 +1166,8 @@ private function get_eligible_loading_strategy( $handle ) { * * @since 6.3.0 * - * @param string $handle Name of the script to get data for. - * Must be lowercase. + * @param string $handle Name of the script to get data for. + * Must be lowercase. * @return bool Whether the handle has an inline script (either before or after). */ private function has_inline_script( $handle ) { From 9f66c06ab074e14ec0a392e6ae5257df7d8226bb Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 16 Jun 2023 12:03:46 -0700 Subject: [PATCH 11/12] Add test ensuring that an async alias gets converted to defer when there is a defer dependency --- tests/phpunit/tests/dependencies/scripts.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index d562f1f7cd587..b38e31e2e1b2b 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -1106,6 +1106,26 @@ public function data_provider_to_test_various_strategy_dependency_chains() { +HTML + , + ), + + 'async-alias-members-with-defer-dependency' => array( + 'set_up' => function () { + $alias_handle = 'async-alias'; + $async_handle1 = 'async1'; + $async_handle2 = 'async2'; + + wp_register_script( $alias_handle, false, array( $async_handle1, $async_handle2 ), null ); + $this->register_test_script( $async_handle1, 'async', array() ); + $this->register_test_script( $async_handle2, 'async', array() ); + + $this->enqueue_test_script( 'defer-dependent-of-async-aliases', 'defer', array( $alias_handle ) ); + }, + 'expected_markup' => $this->get_delayed_inline_script_loader_script_tag() . << + + HTML , ), From 9cb97ab2b5cdf0dba122fd74499bafb1c87e56ef Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 16 Jun 2023 13:26:27 -0700 Subject: [PATCH 12/12] Remove erroneous expected delayed-inline-script-loader --- tests/phpunit/tests/dependencies/scripts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index b38e31e2e1b2b..d6e27c3c63c22 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -1122,7 +1122,7 @@ public function data_provider_to_test_various_strategy_dependency_chains() { $this->enqueue_test_script( 'defer-dependent-of-async-aliases', 'defer', array( $alias_handle ) ); }, - 'expected_markup' => $this->get_delayed_inline_script_loader_script_tag() . << <<