-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Add support for printing script modules in footer #9867
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 13 commits
b64aab8
1caf38a
4115360
05584fc
ac4973c
040c249
e55cf3e
c794fad
acaeceb
8bc8f69
43bb39b
377989d
09897f5
8dfc49a
45a40a3
c8ccf33
5aef0c7
3e96498
9915940
858ef91
4203864
8c71e84
e60e4fa
dbdd3a1
78c7e7a
40e1260
aeb22f0
9ba920a
60fd75f
2438a12
6b62d90
1bba6a3
2c35487
2a695c5
47a0013
b652fdd
de83bd2
379cba0
7c2e9c5
b1b7043
58044f1
0c46399
49c3d5b
2950c7d
4459c6f
cde9ccf
04ec8ac
4fc5e27
bf733b2
922dcf0
d91f8c3
c0179e6
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 |
|---|---|---|
|
|
@@ -50,6 +50,14 @@ class WP_Script_Modules { | |
| */ | ||
| private $dependents_map = array(); | ||
|
|
||
| /** | ||
| * Holds the script module identifiers that have been printed. | ||
| * | ||
| * @since 6.9.0 | ||
| * @var string[] | ||
| */ | ||
| private $done = array(); | ||
|
|
||
| /** | ||
| * Registers the script module if no script module with that script module | ||
| * identifier has already been registered. | ||
|
|
@@ -84,6 +92,7 @@ class WP_Script_Modules { | |
| * @param array $args { | ||
| * Optional. An array of additional args. Default empty array. | ||
| * | ||
| * @type bool $in_footer Whether to print the script module in the footer. Default 'false'. Optional. | ||
| * @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional. | ||
| * } | ||
| */ | ||
|
|
@@ -110,6 +119,8 @@ public function register( string $id, string $src, array $deps = array(), $versi | |
| } | ||
| } | ||
|
|
||
| $in_footer = isset( $args['in_footer'] ) && (bool) $args['in_footer']; | ||
|
|
||
| $fetchpriority = 'auto'; | ||
| if ( isset( $args['fetchpriority'] ) ) { | ||
| if ( $this->is_valid_fetchpriority( $args['fetchpriority'] ) ) { | ||
|
|
@@ -132,6 +143,7 @@ public function register( string $id, string $src, array $deps = array(), $versi | |
| 'src' => $src, | ||
| 'version' => $version, | ||
| 'dependencies' => $dependencies, | ||
| 'in_footer' => $in_footer, | ||
| 'fetchpriority' => $fetchpriority, | ||
| ); | ||
| } | ||
|
|
@@ -217,6 +229,7 @@ public function set_fetchpriority( string $id, string $priority ): bool { | |
| * @param array $args { | ||
| * Optional. An array of additional args. Default empty array. | ||
| * | ||
| * @type bool $in_footer Whether to print the script module in the footer. Default 'false'. Optional. | ||
| * @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional. | ||
| * } | ||
| */ | ||
|
|
@@ -263,10 +276,17 @@ public function deregister( string $id ) { | |
| * @since 6.5.0 | ||
| */ | ||
| public function add_hooks() { | ||
| $position = wp_is_block_theme() ? 'wp_head' : 'wp_footer'; | ||
| add_action( $position, array( $this, 'print_import_map' ) ); | ||
| add_action( $position, array( $this, 'print_enqueued_script_modules' ) ); | ||
| add_action( $position, array( $this, 'print_script_module_preloads' ) ); | ||
| add_action( wp_is_block_theme() ? 'wp_head' : 'wp_footer', array( $this, 'print_import_map' ) ); | ||
| if ( wp_is_block_theme() ) { | ||
b1ink0 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Modules can only be printed in the head for block themes because only with | ||
| // block themes will import map be fully populated by modules discovered by | ||
| // rendering the block template. In classic themes, modules are enqueued during | ||
| // template rendering, thus the import map must be printed in the footer, | ||
| // followed by all enqueued modules. | ||
westonruter marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| add_action( 'wp_head', array( $this, 'print_head_enqueued_script_modules' ) ); | ||
| } | ||
| add_action( 'wp_footer', array( $this, 'print_enqueued_script_modules' ) ); | ||
| add_action( wp_is_block_theme() ? 'wp_head' : 'wp_footer', array( $this, 'print_script_module_preloads' ) ); | ||
|
||
|
|
||
| add_action( 'admin_print_footer_scripts', array( $this, 'print_import_map' ) ); | ||
| add_action( 'admin_print_footer_scripts', array( $this, 'print_enqueued_script_modules' ) ); | ||
|
|
@@ -311,31 +331,71 @@ private function get_highest_fetchpriority( array $ids ): string { | |
| } | ||
|
|
||
| /** | ||
| * Prints the enqueued script modules using script tags with type="module" | ||
| * attributes. | ||
| * Prints the enqueued script modules in head. | ||
| * | ||
| * @since 6.9.0 | ||
| */ | ||
| public function print_head_enqueued_script_modules() { | ||
| $script_modules = $this->get_marked_for_enqueue(); | ||
| $sorted_script_modules_ids = $this->get_sorted_dependencies( array_keys( $script_modules ) ); | ||
| foreach ( $sorted_script_modules_ids as $id ) { | ||
| if ( isset( $script_modules[ $id ] ) && ! $script_modules[ $id ]['in_footer'] ) { | ||
westonruter marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // If any dependency is set to be printed in footer, skip printing this module in head. | ||
| $dependencies = $this->get_dependencies( array( $id ) ); | ||
| foreach ( $dependencies as $dependency_id => $dependency ) { | ||
| if ( in_array( $dependency_id, $this->queue, true ) && $dependency['in_footer'] ) { | ||
| continue 2; | ||
| } | ||
| } | ||
| $this->done[] = $id; | ||
westonruter marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| $this->print_script_module( $id, $script_modules[ $id ] ); | ||
westonruter marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Prints the enqueued script modules in footer. | ||
| * | ||
| * @since 6.5.0 | ||
| */ | ||
| public function print_enqueued_script_modules() { | ||
| foreach ( $this->get_marked_for_enqueue() as $id => $script_module ) { | ||
| $args = array( | ||
| 'type' => 'module', | ||
| 'src' => $this->get_src( $id ), | ||
| 'id' => $id . '-js-module', | ||
| ); | ||
|
|
||
| $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']; | ||
| $script_modules = $this->get_marked_for_enqueue(); | ||
| $sorted_script_modules_ids = $this->get_sorted_dependencies( array_keys( $script_modules ) ); | ||
| foreach ( $sorted_script_modules_ids as $id ) { | ||
| if ( isset( $script_modules[ $id ] ) ) { | ||
| $this->done[] = $id; | ||
| $this->print_script_module( $id, $script_modules[ $id ] ); | ||
westonruter marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| wp_print_script_tag( $args ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Prints the enqueued script module using script tags with type="module" | ||
| * attributes. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @param string $id The script module identifier. | ||
| * @param array $module The script module to print. | ||
| */ | ||
| private function print_script_module( string $id, array $script_module ) { | ||
westonruter marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| $args = array( | ||
| 'type' => 'module', | ||
| 'src' => $this->get_src( $id ), | ||
| 'id' => $id . '-js-module', | ||
| ); | ||
|
|
||
| $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 static dependencies of the enqueued script modules using | ||
| * link tags with rel="modulepreload" attributes. | ||
|
|
@@ -520,6 +580,52 @@ private function get_recursive_dependents( string $id ): array { | |
| return array_unique( $get( $id ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Sorts the given script module identifiers based on their dependencies. | ||
| * | ||
| * It will return a list of script module identifiers sorted in the order | ||
| * they should be printed, so that dependencies are printed before the script | ||
| * modules that depend on them. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @param string[] $ids The identifiers of the script modules to sort. | ||
| * @return string[] Sorted list of script module identifiers. | ||
| */ | ||
| private function get_sorted_dependencies( array $ids ): array { | ||
| $sorted = array(); | ||
| $sorter = function ( array $ids, bool $recursion = false ) use ( &$sorter, &$sorted ) { | ||
| foreach ( $ids as $id ) { | ||
| if ( in_array( $id, $this->done, true ) || in_array( $id, $sorted, true ) ) { // Already done. | ||
| continue; | ||
| } | ||
|
|
||
| $keep_going = true; | ||
| if ( ! isset( $this->registered[ $id ] ) ) { | ||
| $keep_going = false; // Item doesn't exist. | ||
| } elseif ( array_diff( array_column( $this->registered[ $id ]['dependencies'], 'id' ), array_keys( $this->registered ) ) ) { | ||
| $keep_going = false; // Item requires dependencies that don't exist. | ||
| } elseif ( ! $sorter( array_column( $this->registered[ $id ]['dependencies'], 'id' ), true ) ) { | ||
| $keep_going = false; // Item requires dependencies that don't exist. | ||
| } | ||
|
|
||
| if ( ! $keep_going ) { // Either item or its dependencies don't exist. | ||
| if ( $recursion ) { | ||
| return false; // Abort this branch. | ||
| } else { | ||
| continue; // We're at the top level. Move on to the next one. | ||
| } | ||
| } | ||
|
|
||
| $sorted[] = $id; | ||
| } | ||
| return true; | ||
| }; | ||
| $sorter( $ids ); | ||
|
|
||
| return array_unique( $sorted ); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the versioned URL for a script module src. | ||
| * | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.