-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Script Loader: Bump fetchpriority for dependencies to be as high as recursive dependents for scripts and script modules
#9770
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
Changes from all commits
ee14eb6
0222ca9
e2d4676
c76ceb0
e652cbd
3dd0e0f
c59f440
e453a53
546e7a7
a03337a
0b0f328
334a82b
cf809c8
3f5ae8d
4dd633f
95789ba
86c13c9
98abb51
eada641
05e3173
ed8a53f
9d021ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,15 @@ class WP_Script_Modules { | |
| */ | ||
| private $a11y_available = false; | ||
|
|
||
| /** | ||
| * Holds a mapping of dependents (as IDs) for a given script ID. | ||
| * Used to optimize recursive dependency tree checks. | ||
| * | ||
| * @since 6.9.0 | ||
| * @var array<string, string[]> | ||
| */ | ||
| private $dependents_map = array(); | ||
|
|
||
| /** | ||
| * Registers the script module if no script module with that script module | ||
| * identifier has already been registered. | ||
|
|
@@ -269,6 +278,38 @@ public function add_hooks() { | |
| add_action( 'admin_print_footer_scripts', array( $this, 'print_a11y_script_module_html' ), 20 ); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the highest fetch priority for the provided script IDs. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @param string[] $ids Script module IDs. | ||
| * @return string Highest fetch priority for the provided script module IDs. | ||
| */ | ||
| private function get_highest_fetchpriority( array $ids ): string { | ||
| static $priorities = array( | ||
| 'low', | ||
| 'auto', | ||
| 'high', | ||
| ); | ||
| $high_priority_index = count( $priorities ) - 1; | ||
|
|
||
| $highest_priority_index = 0; | ||
| foreach ( $ids as $id ) { | ||
| if ( isset( $this->registered[ $id ] ) ) { | ||
| $highest_priority_index = max( | ||
| $highest_priority_index, | ||
| array_search( $this->registered[ $id ]['fetchpriority'], $priorities, true ) | ||
| ); | ||
| if ( $high_priority_index === $highest_priority_index ) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return $priorities[ $highest_priority_index ]; | ||
| } | ||
|
|
||
| /** | ||
| * Prints the enqueued script modules using script tags with type="module" | ||
| * attributes. | ||
|
|
@@ -282,15 +323,21 @@ public function print_enqueued_script_modules() { | |
| 'src' => $this->get_src( $id ), | ||
| 'id' => $id . '-js-module', | ||
| ); | ||
| if ( 'auto' !== $script_module['fetchpriority'] ) { | ||
| $args['fetchpriority'] = $script_module['fetchpriority']; | ||
|
|
||
| $dependents = $this->get_recursive_dependents( $id ); | ||
| $fetchpriority = $this->get_highest_fetchpriority( array_merge( array( $id ), $dependents ) ); | ||
| if ( 'auto' !== $fetchpriority ) { | ||
| $args['fetchpriority'] = $fetchpriority; | ||
| } | ||
| if ( $fetchpriority !== $script_module['fetchpriority'] ) { | ||
| $args['data-wp-fetchpriority'] = $script_module['fetchpriority']; | ||
| } | ||
| wp_print_script_tag( $args ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Prints the the static dependencies of the enqueued script modules using | ||
| * Prints the static dependencies of the enqueued script modules using | ||
| * link tags with rel="modulepreload" attributes. | ||
| * | ||
| * If a script module is marked for enqueue, it will not be preloaded. | ||
|
|
@@ -301,12 +348,20 @@ public function print_script_module_preloads() { | |
| foreach ( $this->get_dependencies( array_unique( $this->queue ), array( 'static' ) ) as $id => $script_module ) { | ||
| // Don't preload if it's marked for enqueue. | ||
| if ( ! in_array( $id, $this->queue, true ) ) { | ||
| echo sprintf( | ||
| '<link rel="modulepreload" href="%s" id="%s"%s>', | ||
| $enqueued_dependents = array_intersect( $this->get_recursive_dependents( $id ), $this->queue ); | ||
| $highest_fetchpriority = $this->get_highest_fetchpriority( $enqueued_dependents ); | ||
| printf( | ||
| '<link rel="modulepreload" href="%s" id="%s"', | ||
| esc_url( $this->get_src( $id ) ), | ||
| esc_attr( $id . '-js-modulepreload' ), | ||
| 'auto' !== $script_module['fetchpriority'] ? sprintf( ' fetchpriority="%s"', esc_attr( $script_module['fetchpriority'] ) ) : '' | ||
| esc_attr( $id . '-js-modulepreload' ) | ||
| ); | ||
| if ( 'auto' !== $highest_fetchpriority ) { | ||
| printf( ' fetchpriority="%s"', esc_attr( $highest_fetchpriority ) ); | ||
| } | ||
| if ( $highest_fetchpriority !== $script_module['fetchpriority'] && 'auto' !== $script_module['fetchpriority'] ) { | ||
| printf( ' data-wp-fetchpriority="%s"', esc_attr( $script_module['fetchpriority'] ) ); | ||
| } | ||
| echo ">\n"; | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -374,18 +429,20 @@ private function get_marked_for_enqueue(): array { | |
| * Default is both. | ||
| * @return array[] List of dependencies, keyed by script module identifier. | ||
| */ | ||
| private function get_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ) { | ||
| private function get_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array { | ||
| return array_reduce( | ||
| $ids, | ||
| function ( $dependency_script_modules, $id ) use ( $import_types ) { | ||
| $dependencies = array(); | ||
| foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) { | ||
| if ( | ||
| in_array( $dependency['import'], $import_types, true ) && | ||
| isset( $this->registered[ $dependency['id'] ] ) && | ||
| ! isset( $dependency_script_modules[ $dependency['id'] ] ) | ||
| ) { | ||
| $dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ]; | ||
| if ( isset( $this->registered[ $id ] ) ) { | ||
| foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) { | ||
| if ( | ||
| in_array( $dependency['import'], $import_types, true ) && | ||
| isset( $this->registered[ $dependency['id'] ] ) && | ||
| ! isset( $dependency_script_modules[ $dependency['id'] ] ) | ||
| ) { | ||
| $dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ]; | ||
| } | ||
| } | ||
| } | ||
| return array_merge( $dependency_script_modules, $dependencies, $this->get_dependencies( array_keys( $dependencies ), $import_types ) ); | ||
|
|
@@ -394,6 +451,75 @@ function ( $dependency_script_modules, $id ) use ( $import_types ) { | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Gets all dependents of a script module. | ||
| * | ||
| * This is not recursive. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @see WP_Scripts::get_dependents() | ||
| * | ||
| * @param string $id The script ID. | ||
| * @return string[] Script module IDs. | ||
| */ | ||
| private function get_dependents( string $id ): array { | ||
| // Check if dependents map for the handle in question is present. If so, use it. | ||
| if ( isset( $this->dependents_map[ $id ] ) ) { | ||
| return $this->dependents_map[ $id ]; | ||
| } | ||
|
Comment on lines
+468
to
+470
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no invalidation in this caching. That's OK right now because we only start exploring the graph when it's time to start outputting tags. The method is private, and it's likely fine, but there is an implicit timing constraint here where this can contain inaccurate information if scripts are added later.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I just wrote in #9867 (comment), I think we should explicitly warn and noop when attempting to register/enqueue a script module after the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's critical to figure. Once the importmap is printed, there's nothing to be done, so the restriction seems reasonable. If this were in an output buffer (something I know you've been working on), there could be a possibility of updating the importmap later before flushing. There's starting to be support for multiple importmaps, but FireFox does not support it. In the future we may be able to print an early and a late importmap. |
||
|
|
||
| $dependents = array(); | ||
|
|
||
| // Iterate over all registered scripts, finding dependents of the script passed to this method. | ||
| foreach ( $this->registered as $registered_id => $args ) { | ||
| if ( in_array( $id, wp_list_pluck( $args['dependencies'], 'id' ), true ) ) { | ||
| $dependents[] = $registered_id; | ||
| } | ||
| } | ||
|
|
||
| // Add the module's dependents to the map to ease future lookups. | ||
| $this->dependents_map[ $id ] = $dependents; | ||
|
|
||
| return $dependents; | ||
| } | ||
|
|
||
| /** | ||
| * Gets all recursive dependents of a script module. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @see WP_Scripts::get_dependents() | ||
| * | ||
| * @param string $id The script ID. | ||
| * @return string[] Script module IDs. | ||
| */ | ||
| private function get_recursive_dependents( string $id ): array { | ||
| $get = function ( string $id, array $checked = array() ) use ( &$get ): array { | ||
|
|
||
| // If by chance an unregistered script module is checked or there is a recursive dependency, return early. | ||
| if ( ! isset( $this->registered[ $id ] ) || isset( $checked[ $id ] ) ) { | ||
| return array(); | ||
| } | ||
|
|
||
| // Mark this script module as checked to guard against infinite recursion. | ||
| $checked[ $id ] = true; | ||
|
|
||
| $dependents = array(); | ||
| foreach ( $this->get_dependents( $id ) as $dependent ) { | ||
| $dependents = array_merge( | ||
| $dependents, | ||
| array( $dependent ), | ||
| $get( $dependent, $checked ) | ||
| ); | ||
| } | ||
|
|
||
| return $dependents; | ||
| }; | ||
|
|
||
| return array_unique( $get( $id ) ); | ||
sirreal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Gets the versioned URL for a script module src. | ||
| * | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.