Skip to content

Commit

Permalink
Merge pull request #35 from 10up/enhancement/get_eligible_loading_str…
Browse files Browse the repository at this point in the history
…ategy-ML2-12

update `get_eligible_loading_strategy` and `do_item` method - milestone 2
  • Loading branch information
kt-12 authored Mar 1, 2023
2 parents bb14332 + 2b3d11c commit 16c4634
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 3 deletions.
111 changes: 110 additions & 1 deletion src/wp-includes/class-wp-scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,18 @@ public function do_item( $handle, $group = false ) {
return true;
}

$strategy = $this->get_eligible_loading_strategy( $handle );
if( '' !== $strategy ) {
$strategy = ' '.$strategy;
}
$tag = $translations . $cond_before . $before_handle;
$tag .= sprintf( "<script%s src='%s' id='%s-js'></script>\n", $this->type_attr, $src, esc_attr( $handle ) );
$tag .= sprintf(
"<script%s src='%s' id='%s-js'%s></script>\n",
$this->type_attr,
esc_url( $src ),
esc_attr( $handle ),
$strategy
);
$tag .= $after_handle . $cond_after;

/**
Expand Down Expand Up @@ -757,6 +767,105 @@ private function get_normalized_script_args( $handle, $args = array() ) {
return wp_parse_args( $args, $default_args );
}

/**
* Get all of the scripts that depend on a script.
*
* @param string $handle The script handle.
* @return array Array of script handles.
*/
private function get_dependents( $handle ) {
$dependents = array();

// Iterate over all registered scripts, finding ones that depend on the script.
foreach ( $this->registered as $registered_handle => $args ) {
if ( in_array( $handle, $args->deps, true ) ) {
$dependents[] = $registered_handle;
}
}
return $dependents;
}

/**
* Get the strategy assigned during script registration.
*
* @param string $handle The script handle.
* @return string|bool Strategy set during script registration. False if none was set.
*/
private function get_intended_strategy( $handle ) {
$script_args = $this->get_data( $handle, 'script_args' );
return isset( $script_args['strategy'] ) ? $script_args['strategy'] : false;
}

/**
* Check if all of a scripts dependents are deferrable which is required to maintain execution order.
*
* @param string $handle The script handle.
* @param array $checked An array of already checked script handles, used to avoid looping recursion.
* @return bool True if all dependents are deferrable, false otherwise.
*/
private function all_dependents_are_deferrable( $handle, $checked = array() ) {
// If this node was already checked, this script can be deferred and the branch ends.
if ( in_array( $handle, $checked, true ) ) {
return true;
}
$checked[] = $handle;
$dependents = $this->get_dependents( $handle );

// If there are no dependents remaining to consider, the script can be deferred and the branch ends.
if ( empty( $dependents ) ) {
return true;
}

// Consider each dependent and check if it is deferrable.
foreach ( $dependents as $dependent ) {
// If the dependent script is not using the defer or async strategy, no script in the chain is deferrable.
if ( ! in_array( $this->get_intended_strategy( $dependent ), array( 'defer', 'async' ), true ) ) {
return false;
}

// Recursively check all dependents.
if ( ! $this->all_dependents_are_deferrable( $dependent, $checked ) ) {
return false;
}
}
return true;
}

/**
* Get the most eligible loading strategy for a script.
*
* @param string $handle The registered handle of the script.
* @return string $strategy return the final strategy.
*/
private function get_eligible_loading_strategy( $handle = '' ) {
if ( ! isset( $this->registered[ $handle ] ) ) {
return '';
}

$intended_strategy = $this->get_intended_strategy( $handle );
/*
* Handle known blocking strategy scenarios.
*
* blocking if script args not set.
* blocking if explicitly set.
*/
if ( ! $intended_strategy || 'blocking' === $intended_strategy ) {
return '';
}

// Handling async strategy scenarios.
if ( 'async' === $intended_strategy && empty( $this->registered[ $handle ]->deps ) && empty( $this->get_dependents( $handle ) ) ) {
return 'async';
}

// Handling defer strategy scenarios.
if ( $this->all_dependents_are_deferrable( $handle ) ) {
return 'defer';
}

return '';
}

/**
* Resets class properties.
*
Expand Down
136 changes: 134 additions & 2 deletions tests/phpunit/tests/dependencies/scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,138 @@ public function test_wp_enqueue_script() {
$this->assertSame( '', get_echo( 'wp_print_scripts' ) );
}

/**
* Test valid async loading strategy case.
*
* @ticket 12009
*/
public function test_loading_strategy_with_valid_async_registration() {
// No dependents, No dependencies then async.
wp_enqueue_script( 'main-script-a1', '/main-script-a1.js', array(), null, array( 'strategy' => 'async' ) );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='/main-script-a1.js' id='main-script-a1-js' async></script>\n";
$this->assertSame( $expected, $output );
}

/**
* Test invalid async loading strategy cases.
*
* @ticket 12009
*/
public function test_loading_strategy_with_invalid_async_registration() {
// If any dependencies then it's not async. Since dependency is blocking(/defer) final strategy will be defer.
wp_enqueue_script( 'dependency-script-a2', '/dependency-script-a2.js', array(), null );
wp_enqueue_script( 'main-script-a2', '/main-script-a2.js', array( 'dependency-script-a2' ), null, array( 'strategy' => 'async' ) );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='/main-script-a2.js' id='main-script-a2-js' defer></script>";
$this->assertStringContainsString( $expected, $output, 'Expected defer.' );

// If any dependent then it's not async. Since dependent is not set to defer the final strategy will be blocking.
wp_enqueue_script( 'main-script-a3', '/main-script-a3.js', array(), null, array( 'strategy' => 'async' ) );
wp_enqueue_script( 'dependent-script-a3', '/dependent-script-a3.js', array( 'main-script-a3' ), null );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='/main-script-a3.js' id='main-script-a3-js'></script>";
$this->assertStringContainsString( $expected, $output, 'Expected blocking.' );
}

/**
* Test valid defer loading strategy cases.
*
* @ticket 12009
* @dataProvider data_loading_strategy_with_valid_defer_registration
*/
public function test_loading_strategy_with_valid_defer_registration( $expected, $output, $message ) {
$this->assertStringContainsString( $expected, $output, $message );
}

public function data_loading_strategy_with_valid_defer_registration() {
$data = array();

// No dependents, No dependencies and defer strategy.
wp_enqueue_script( 'main-script-d1', 'http://example.com/main-script-d1.js', array(), null, array( 'strategy' => 'defer' ) );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='http://example.com/main-script-d1.js' id='main-script-d1-js' defer></script>\n";
array_push( $data, array( $expected, $output, 'Expected defer, as there is no dependent or dependency' ) );

// Main script is defer and all dependencies are either defer/blocking.
wp_enqueue_script( 'dependency-script-d2-1', 'http://example.com/dependency-script-d2-1.js', array(), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'dependency-script-d2-2', 'http://example.com/dependency-script-d2-2.js', array(), null, array( 'strategy' => 'blocking' ) );
wp_enqueue_script( 'dependency-script-d2-3', 'http://example.com/dependency-script-d2-3.js', array( 'dependency-script-d2-2' ), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'main-script-d2', 'http://example.com/main-script-d2.js', array( 'dependency-script-d2-1', 'dependency-script-d2-3' ), null, array( 'strategy' => 'defer' ) );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='http://example.com/main-script-d2.js' id='main-script-d2-js' defer></script>\n";
array_push( $data, array( $expected, $output, 'Expected defer, as all dependencies are either deferred or blocking' ) );

// Main script is defer and all dependent are defer.
wp_enqueue_script( 'main-script-d3', 'http://example.com/main-script-d3.js', array(), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'dependent-script-d3-1', 'http://example.com/dependent-script-d3-1.js', array( 'main-script-d3' ), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'dependent-script-d3-2', 'http://example.com/dependent-script-d3-2.js', array( 'dependent-script-d3-1' ), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'dependent-script-d3-3', 'http://example.com/dependent-script-d3-3.js', array( 'dependent-script-d3-2' ), null, array( 'strategy' => 'defer' ) );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='http://example.com/main-script-d3.js' id='main-script-d3-js' defer></script>\n";
array_push( $data, array( $expected, $output, 'Expected defer, as all dependents have defer loading strategy' ) );

return $data;
}

/**
* Test valid defer loading with async dependent.
*
* @ticket 12009
*/
public function test_defer_with_async_dependent() {
// case with one async dependent.
wp_enqueue_script( 'main-script-d4', '/main-script-d4.js', array(), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'dependent-script-d4-1', '/dependent-script-d4-1.js', array( 'main-script-d4' ), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'dependent-script-d4-2', '/dependent-script-d4-2.js', array( 'dependent-script-d4-1' ), null, array( 'strategy' => 'async' ) );
wp_enqueue_script( 'dependent-script-d4-3', '/dependent-script-d4-3.js', array( 'dependent-script-d4-2' ), null, array( 'strategy' => 'defer' ) );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js' defer></script>\n";
$expected .= "<script type='text/javascript' src='/dependent-script-d4-1.js' id='dependent-script-d4-1-js' defer></script>\n";
$expected .= "<script type='text/javascript' src='/dependent-script-d4-2.js' id='dependent-script-d4-2-js' defer></script>\n";
$expected .= "<script type='text/javascript' src='/dependent-script-d4-3.js' id='dependent-script-d4-3-js' defer></script>\n";

$this->assertSame( $expected, $output );
}

/**
* Test invalid defer loading strategy case.
*
* @ticket 12009
*/
public function test_loading_strategy_with_invalid_defer_registration() {
// Main script is defer and all dependent are not defer. Then main script will have blocking(or no) strategy.
wp_enqueue_script( 'main-script-d4', '/main-script-d4.js', array(), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'dependent-script-d4-1', '/dependent-script-d4-1.js', array( 'main-script-d4' ), null, array( 'strategy' => 'defer' ) );
wp_enqueue_script( 'dependent-script-d4-2', '/dependent-script-d4-2.js', array( 'dependent-script-d4-1' ), null, array( 'strategy' => 'blocking' ) );
wp_enqueue_script( 'dependent-script-d4-3', '/dependent-script-d4-3.js', array( 'dependent-script-d4-2' ), null, array( 'strategy' => 'defer' ) );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js'></script>\n";
$this->assertStringContainsString( $expected, $output );
}

/**
* Test valid blocking loading strategy cases.
*
* @ticket 12009
*/
public function test_loading_strategy_with_valid_blocking_registration() {
wp_enqueue_script( 'main-script-b1', '/main-script-b1.js', array(), null, array( 'strategy' => 'blocking' ) );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='/main-script-b1.js' id='main-script-b1-js'></script>\n";
$this->assertSame( $expected, $output );

// strategy args not set.
wp_enqueue_script( 'main-script-b2', '/main-script-b2.js', array(), null, array() );
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='/main-script-b2.js' id='main-script-b2-js'></script>\n";
$this->assertSame( $expected, $output );
}

/**
* Test old and new in_footer logic.
*
* @ticket 12009
*/
public function test_old_and_new_in_footer_scripts() {
// Scripts in head.
Expand Down Expand Up @@ -98,6 +228,8 @@ public function test_old_and_new_in_footer_scripts() {

/**
* Test normalized script args.
*
* @ticket 12009
*/
public function test_get_normalized_script_args() {
global $wp_scripts;
Expand Down Expand Up @@ -131,12 +263,12 @@ public function test_get_normalized_script_args() {
$this->assertSame( false, $wp_scripts->get_data( 'defaults-no-args', 'script_args' ) );

// Test backward compatibility.
$args = array(
$expected_args = array(
'in_footer' => true,
'strategy' => 'blocking',
);
wp_enqueue_script( 'footer-old', '/footer-async.js', array(), null, true );
$this->assertSame( $args, $wp_scripts->get_data( 'footer-old', 'script_args' ) );
$this->assertSame( $expected_args, $wp_scripts->get_data( 'footer-old', 'script_args' ) );
}

/**
Expand Down

0 comments on commit 16c4634

Please sign in to comment.