Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Script Loading Strategy - ML2 #13 - Add get_eligible_loading_strategy method #35

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
20917f9
get_eligible_loading_strategy logic
kt-12 Feb 22, 2023
db02a89
Deferring strategy refined
kt-12 Feb 23, 2023
1114f35
Merge branch 'enhancement/wp-script-api-milestone1' into enhancement/…
kt-12 Feb 23, 2023
af30f17
Bug fix
kt-12 Feb 23, 2023
1d3c887
Merge branch 'enhancement/wp-script-api-milestone2' into enhancement/…
kt-12 Feb 24, 2023
2ed92d3
removing a few lines with function
kt-12 Feb 24, 2023
552d91a
removing default script strategy
kt-12 Feb 24, 2023
1eacd05
do_item strategy support
kt-12 Feb 24, 2023
4542775
bug fix
kt-12 Feb 24, 2023
60b460a
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 24, 2023
d89ab9a
Strategy name update.
kt-12 Feb 24, 2023
4105504
fixing bug
kt-12 Feb 24, 2023
ceb670f
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 24, 2023
a9eef9f
strategy test cases
kt-12 Feb 26, 2023
c2c1d07
cs fixes
kt-12 Feb 26, 2023
9eb3f89
opt-in + remove blocking after dep
kt-12 Feb 26, 2023
6cc7dec
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 26, 2023
35279fc
complex condition towards end
kt-12 Feb 26, 2023
74e4aef
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 26, 2023
1de0f31
if user not entered
kt-12 Feb 26, 2023
d5ebbc3
place comment in order
kt-12 Feb 26, 2023
8f14aad
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 26, 2023
5795b4c
restore default values
kt-12 Feb 26, 2023
0ebfaa5
phpcs fixes
kt-12 Feb 26, 2023
f6b0483
Update text
kt-12 Feb 26, 2023
7b36d21
Readability
kt-12 Feb 26, 2023
f71319c
consistent with other docblock descriptions
kt-12 Feb 26, 2023
624e797
casting the result
kt-12 Feb 26, 2023
b7334b7
wordpress coding standards
kt-12 Feb 26, 2023
2cb01be
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' of ht…
kt-12 Feb 26, 2023
5305f1c
restore test case
kt-12 Feb 27, 2023
073684a
add temporary ticket id
kt-12 Feb 27, 2023
5e343b8
fix test bug
kt-12 Feb 27, 2023
b56c0e1
restore test case
kt-12 Feb 27, 2023
9caf8bb
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 27, 2023
b70ee6c
temporary ticket added for testing
kt-12 Feb 27, 2023
dbf6da9
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 27, 2023
2deadcf
remove extra line
kt-12 Feb 27, 2023
fcaa62e
remove after dependency function
kt-12 Feb 27, 2023
150ba8b
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 27, 2023
d578e50
fix doc block text
kt-12 Feb 27, 2023
5262bf7
Correct grammar
kt-12 Feb 27, 2023
2970f1a
Fix doc block string.
kt-12 Feb 27, 2023
28af60d
change variable name
kt-12 Feb 27, 2023
f746dfd
doc block changes, better word
kt-12 Feb 27, 2023
00a50d1
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 27, 2023
d4bbaff
spacing issue.
kt-12 Feb 27, 2023
01e1d4f
update placeholder with numbered placeholder
kt-12 Feb 28, 2023
a367951
Change ticket id to trac id
kt-12 Feb 28, 2023
51e3e41
Switching from numbered to normal substitution
kt-12 Feb 28, 2023
62cbf60
Breaking test into multiple test functions
kt-12 Feb 28, 2023
0f80794
breaking test to dataprovider
kt-12 Feb 28, 2023
f20cfea
add async and defer check in dependent check
kt-12 Feb 28, 2023
e406a13
Merge branch 'enhancement/get_eligible_loading_strategy-ML2-12' into …
kt-12 Feb 28, 2023
b961108
defer with async dependent
kt-12 Feb 28, 2023
70fb6c6
Add failure messages
kt-12 Feb 28, 2023
2b3d11c
Merge pull request #38 from 10up/enhancement/do_item-ML2-14
kt-12 Feb 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This needs to be optimised further. Better way of doing this is creating a dependents list as a hash table (added as a Dependencies to WP_Script) and updated dependents while each script is getting registered. get_dependent will simply fetch dependent from that hash table.

$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;
10upsimon marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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.
kt-12 marked this conversation as resolved.
Show resolved Hide resolved
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 ) ) {
10upsimon marked this conversation as resolved.
Show resolved Hide resolved
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