Make WordPress Core

Changeset 60999


Ignore:
Timestamp:
10/21/2025 12:43:51 AM (12 months ago)
Author:
westonruter
Message:

Script Loader: Add support for printing script modules at wp_footer.

This brings API parity with WP_Scripts by implementing opt-in support for printing in the footer via an in_footer argument. This argument can be supplied via the $args array passed to wp_enqueue_script_module() or wp_register_script_module(), alongside the existing fetchpriority key introduced in #61734. It can also be set for previously-registered script modules via WP_Script_Modules::set_in_footer(). This is not applicable to classic themes since modules are enqueued while blocks are rendered after wp_head has completed, so all script modules are printed in the footer anyway; the importmap script must be printed after all script modules have been enqueued.

Script modules used for interactive blocks (with the Interactivity API) are automatically printed in the footer. Such script modules should be deprioritized because they are not in the critical rendering path due to interactive blocks using server-side rendering. Script modules remain printed at wp_head by default, although this default should be revisited since they have deferred execution (and they are printed in the footer for classic themes already, as previously noted). Moving a script module to the footer ensures that its loading does not contend with the loading of critical resources, such as the LCP element's image resource, and LCP is improved as a result.

This also improves specificity of some PHP types, it ensures that script modules can't be registered with an empty ID, and it prevents printing script modules with empty src URLs.

Developed in ​https://github.com/WordPress/wordpress-develop/pull/9867

Follow-up to [60704].

Props b1ink0, westonruter, jonsurrell, peterwilsoncc, vipulpatil, mindctrl.
See #61734.
Fixes #63486.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks.php

    r60939 r60999  
    183183        ) {
    184184                $args['fetchpriority'] = 'low';
     185                $args['in_footer']     = true;
    185186        }
    186187
  • trunk/src/wp-includes/class-wp-script-modules.php

    r60951 r60999  
    1919         *
    2020         * @since 6.5.0
    21          * @var array[]
     21         * @var array<string, array<string, mixed>>
    2222         */
    2323        private $registered = array();
    … …  
    3030         */
    3131        private $queue = array();
     32
     33        /**
     34         * Holds the script module identifiers that have been printed.
     35         *
     36         * @since 6.9.0
     37         * @var string[]
     38         */
     39        private $done = array();
    3240
    3341        /**
    … …  
    5058         */
    5159        private $dependents_map = array();
     60
     61        /**
     62         * Holds the valid values for fetchpriority.
     63         *
     64         * @since 6.9.0
     65         * @var string[]
     66         */
     67        private $priorities = array(
     68                'low',
     69                'auto',
     70                'high',
     71        );
    5272
    5373        /**
    … …  
    85105         *     Optional. An array of additional args. Default empty array.
    86106         *
     107         *     @type bool                $in_footer     Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional.
    87108         *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
    88109         * }
    89110         */
    90111        public function register( string $id, string $src, array $deps = array(), $version = false, array $args = array() ) {
     112                if ( '' === $id ) {
     113                        _doing_it_wrong( __METHOD__, __( 'Non-empty string required for id.' ), '6.9.0' );
     114                        return;
     115                }
     116
    91117                if ( ! isset( $this->registered[ $id ] ) ) {
    92118                        $dependencies = array();
    … …  
    111137                        }
    112138
     139                        $in_footer = isset( $args['in_footer'] ) && (bool) $args['in_footer'];
     140
    113141                        $fetchpriority = 'auto';
    114142                        if ( isset( $args['fetchpriority'] ) ) {
    … …  
    133161                                'version'       => $version,
    134162                                'dependencies'  => $dependencies,
     163                                'in_footer'     => $in_footer,
    135164                                'fetchpriority' => $fetchpriority,
    136165                        );
    … …  
    158187         */
    159188        private function is_valid_fetchpriority( $priority ): bool {
    160                 return in_array( $priority, array( 'auto', 'low', 'high' ), true );
     189                return in_array( $priority, $this->priorities, true );
    161190        }
    162191
    … …  
    190219
    191220                $this->registered[ $id ]['fetchpriority'] = $priority;
     221                return true;
     222        }
     223
     224        /**
     225         * Sets whether a script module should be printed in the footer.
     226         *
     227         * This is only relevant in block themes.
     228         *
     229         * @since 6.9.0
     230         *
     231         * @param string           $id        Script module identifier.
     232         * @param bool             $in_footer Whether to print in the footer.
     233         * @return bool Whether setting the printing location was successful.
     234         */
     235        public function set_in_footer( string $id, bool $in_footer ): bool {
     236                if ( ! isset( $this->registered[ $id ] ) ) {
     237                        return false;
     238                }
     239                $this->registered[ $id ]['in_footer'] = $in_footer;
    192240                return true;
    193241        }
    … …  
    229277         *     Optional. An array of additional args. Default empty array.
    230278         *
     279         *     @type bool                $in_footer     Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional.
    231280         *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
    232281         * }
    233282         */
    234283        public function enqueue( string $id, string $src = '', array $deps = array(), $version = false, array $args = array() ) {
     284                if ( '' === $id ) {
     285                        _doing_it_wrong( __METHOD__, __( 'Non-empty string required for id.' ), '6.9.0' );
     286                        return;
     287                }
     288
    235289                if ( ! in_array( $id, $this->queue, true ) ) {
    236290                        $this->queue[] = $id;
    … …  
    275329         */
    276330        public function add_hooks() {
    277                 $position = wp_is_block_theme() ? 'wp_head' : 'wp_footer';
     331                $is_block_theme = wp_is_block_theme();
     332                $position       = $is_block_theme ? 'wp_head' : 'wp_footer';
    278333                add_action( $position, array( $this, 'print_import_map' ) );
    279                 add_action( $position, array( $this, 'print_enqueued_script_modules' ) );
     334                if ( $is_block_theme ) {
     335                        /*
     336                         * Modules can only be printed in the head for block themes because only with
     337                         * block themes will import map be fully populated by modules discovered by
     338                         * rendering the block template. In classic themes, modules are enqueued during
     339                         * template rendering, thus the import map must be printed in the footer,
     340                         * followed by all enqueued modules.
     341                         */
     342                        add_action( 'wp_head', array( $this, 'print_head_enqueued_script_modules' ) );
     343                }
     344                add_action( 'wp_footer', array( $this, 'print_enqueued_script_modules' ) );
    280345                add_action( $position, array( $this, 'print_script_module_preloads' ) );
    281346
    … …  
    296361         *
    297362         * @param string[] $ids Script module IDs.
    298          * @return string Highest fetch priority for the provided script module IDs.
     363         * @return 'auto'|'low'|'high' Highest fetch priority for the provided script module IDs.
    299364         */
    300365        private function get_highest_fetchpriority( array $ids ): string {
    301                 static $priorities   = array(
    302                         'low',
    303                         'auto',
    304                         'high',
    305                 );
    306                 $high_priority_index = count( $priorities ) - 1;
     366                static $high_priority_index = null;
     367                if ( null === $high_priority_index ) {
     368                        $high_priority_index = count( $this->priorities ) - 1;
     369                }
    307370
    308371                $highest_priority_index = 0;
    309372                foreach ( $ids as $id ) {
    310373                        if ( isset( $this->registered[ $id ] ) ) {
    311                                 $highest_priority_index = max(
     374                                $highest_priority_index = (int) max(
    312375                                        $highest_priority_index,
    313                                         array_search( $this->registered[ $id ]['fetchpriority'], $priorities, true )
     376                                        (int) array_search( $this->registered[ $id ]['fetchpriority'], $this->priorities, true )
    314377                                );
    315378                                if ( $high_priority_index === $highest_priority_index ) {
    … …  
    319382                }
    320383
    321                 return $priorities[ $highest_priority_index ];
    322         }
    323 
    324         /**
    325          * Prints the enqueued script modules using script tags with type="module"
     384                return $this->priorities[ $highest_priority_index ];
     385        }
     386
     387        /**
     388         * Prints the enqueued script modules in head.
     389         *
     390         * This is only used in block themes.
     391         *
     392         * @since 6.9.0
     393         */
     394        public function print_head_enqueued_script_modules() {
     395                foreach ( $this->get_sorted_dependencies( $this->queue ) as $id ) {
     396                        if (
     397                                isset( $this->registered[ $id ] ) &&
     398                                ! $this->registered[ $id ]['in_footer']
     399                        ) {
     400                                // If any dependency is set to be printed in footer, skip printing this module in head.
     401                                $dependencies = $this->get_dependencies( array( $id ) );
     402                                foreach ( $dependencies as $dependency_id ) {
     403                                        if (
     404                                                in_array( $dependency_id, $this->queue, true ) &&
     405                                                isset( $this->registered[ $dependency_id ] ) &&
     406                                                $this->registered[ $dependency_id ]['in_footer']
     407                                        ) {
     408                                                continue 2;
     409                                        }
     410                                }
     411                                $this->print_script_module( $id );
     412                        }
     413                }
     414        }
     415
     416        /**
     417         * Prints the enqueued script modules in footer.
     418         *
     419         * @since 6.5.0
     420         */
     421        public function print_enqueued_script_modules() {
     422                foreach ( $this->get_sorted_dependencies( $this->queue ) as $id ) {
     423                        $this->print_script_module( $id );
     424                }
     425        }
     426
     427        /**
     428         * Prints the enqueued script module using script tags with type="module"
    326429         * attributes.
    327430         *
    328          * @since 6.5.0
    329          */
    330         public function print_enqueued_script_modules() {
    331                 foreach ( $this->get_marked_for_enqueue() as $id => $script_module ) {
    332                         $args = array(
    333                                 'type' => 'module',
    334                                 'src'  => $this->get_src( $id ),
    335                                 'id'   => $id . '-js-module',
    336                         );
    337 
    338                         $dependents    = $this->get_recursive_dependents( $id );
    339                         $fetchpriority = $this->get_highest_fetchpriority( array_merge( array( $id ), $dependents ) );
    340                         if ( 'auto' !== $fetchpriority ) {
    341                                 $args['fetchpriority'] = $fetchpriority;
    342                         }
    343                         if ( $fetchpriority !== $script_module['fetchpriority'] ) {
    344                                 $args['data-wp-fetchpriority'] = $script_module['fetchpriority'];
    345                         }
    346                         wp_print_script_tag( $args );
    347                 }
     431         * @since 6.9.0
     432         *
     433         * @param string $id The script module identifier.
     434         */
     435        private function print_script_module( string $id ) {
     436                if ( in_array( $id, $this->done, true ) || ! in_array( $id, $this->queue, true ) ) {
     437                        return;
     438                }
     439
     440                $this->done[] = $id;
     441
     442                $src = $this->get_src( $id );
     443                if ( '' === $src ) {
     444                        return;
     445                }
     446
     447                $attributes = array(
     448                        'type' => 'module',
     449                        'src'  => $src,
     450                        'id'   => $id . '-js-module',
     451                );
     452
     453                $script_module = $this->registered[ $id ];
     454                $dependents    = $this->get_recursive_dependents( $id );
     455                $fetchpriority = $this->get_highest_fetchpriority( array_merge( array( $id ), $dependents ) );
     456                if ( 'auto' !== $fetchpriority ) {
     457                        $attributes['fetchpriority'] = $fetchpriority;
     458                }
     459                if ( $fetchpriority !== $script_module['fetchpriority'] ) {
     460                        $attributes['data-wp-fetchpriority'] = $script_module['fetchpriority'];
     461                }
     462                wp_print_script_tag( $attributes );
    348463        }
    349464
    … …  
    357472         */
    358473        public function print_script_module_preloads() {
    359                 foreach ( $this->get_dependencies( array_unique( $this->queue ), array( 'static' ) ) as $id => $script_module ) {
     474                $dependency_ids = $this->get_sorted_dependencies( $this->queue, array( 'static' ) );
     475                foreach ( $dependency_ids as $id ) {
    360476                        // Don't preload if it's marked for enqueue.
    361                         if ( ! in_array( $id, $this->queue, true ) ) {
    362                                 $enqueued_dependents   = array_intersect( $this->get_recursive_dependents( $id ), $this->queue );
    363                                 $highest_fetchpriority = $this->get_highest_fetchpriority( $enqueued_dependents );
    364                                 printf(
    365                                         '<link rel="modulepreload" href="%s" id="%s"',
    366                                         esc_url( $this->get_src( $id ) ),
    367                                         esc_attr( $id . '-js-modulepreload' )
    368                                 );
    369                                 if ( 'auto' !== $highest_fetchpriority ) {
    370                                         printf( ' fetchpriority="%s"', esc_attr( $highest_fetchpriority ) );
    371                                 }
    372                                 if ( $highest_fetchpriority !== $script_module['fetchpriority'] && 'auto' !== $script_module['fetchpriority'] ) {
    373                                         printf( ' data-wp-fetchpriority="%s"', esc_attr( $script_module['fetchpriority'] ) );
    374                                 }
    375                                 echo ">\n";
    376                         }
     477                        if ( in_array( $id, $this->queue, true ) ) {
     478                                continue;
     479                        }
     480
     481                        $src = $this->get_src( $id );
     482                        if ( '' === $src ) {
     483                                continue;
     484                        }
     485
     486                        $enqueued_dependents   = array_intersect( $this->get_recursive_dependents( $id ), $this->queue );
     487                        $highest_fetchpriority = $this->get_highest_fetchpriority( $enqueued_dependents );
     488                        printf(
     489                                '<link rel="modulepreload" href="%s" id="%s"',
     490                                esc_url( $src ),
     491                                esc_attr( $id . '-js-modulepreload' )
     492                        );
     493                        if ( 'auto' !== $highest_fetchpriority ) {
     494                                printf( ' fetchpriority="%s"', esc_attr( $highest_fetchpriority ) );
     495                        }
     496                        if ( $highest_fetchpriority !== $this->registered[ $id ]['fetchpriority'] && 'auto' !== $this->registered[ $id ]['fetchpriority'] ) {
     497                                printf( ' data-wp-fetchpriority="%s"', esc_attr( $this->registered[ $id ]['fetchpriority'] ) );
     498                        }
     499                        echo ">\n";
    377500                }
    378501        }
    … …  
    387510                if ( ! empty( $import_map['imports'] ) ) {
    388511                        wp_print_inline_script_tag(
    389                                 wp_json_encode( $import_map, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
     512                                (string) wp_json_encode( $import_map, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
    390513                                array(
    391514                                        'type' => 'importmap',
    … …  
    406529        private function get_import_map(): array {
    407530                $imports = array();
    408                 foreach ( $this->get_dependencies( array_unique( $this->queue ) ) as $id => $script_module ) {
    409                         $imports[ $id ] = $this->get_src( $id );
     531                foreach ( $this->get_dependencies( $this->queue ) as $id ) {
     532                        $src = $this->get_src( $id );
     533                        if ( '' !== $src ) {
     534                                $imports[ $id ] = $src;
     535                        }
    410536                }
    411537                return array( 'imports' => $imports );
    … …  
    414540        /**
    415541         * Retrieves the list of script modules marked for enqueue.
     542         *
     543         * Even though this is a private method and is unused in core, there are ecosystem plugins accessing it via the
     544         * Reflection API. The ecosystem should rather use {@see self::get_queue()}.
    416545         *
    417546         * @since 6.5.0
    … …  
    427556
    428557        /**
    429          * Retrieves all the dependencies for the given script module identifiers,
    430          * filtered by import types.
     558         * Retrieves all the dependencies for the given script module identifiers, filtered by import types.
    431559         *
    432560         * It will consolidate an array containing a set of unique dependencies based
    … …  
    438566         * @param string[] $ids          The identifiers of the script modules for which to gather dependencies.
    439567         * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both.
    440          *                               Default is both.
    441          * @return array[] List of dependencies, keyed by script module identifier.
     568         *                                         Default is both.
     569         * @return string[] List of IDs for script module dependencies.
    442570         */
    443571        private function get_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array {
    444                 return array_reduce(
    445                         $ids,
    446                         function ( $dependency_script_modules, $id ) use ( $import_types ) {
    447                                 $dependencies = array();
    448                                 if ( isset( $this->registered[ $id ] ) ) {
    449                                         foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) {
    450                                                 if (
    451                                                         in_array( $dependency['import'], $import_types, true ) &&
    452                                                         isset( $this->registered[ $dependency['id'] ] ) &&
    453                                                         ! isset( $dependency_script_modules[ $dependency['id'] ] )
    454                                                 ) {
    455                                                         $dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ];
    456                                                 }
    457                                         }
     572                $all_dependencies = array();
     573                $id_queue         = $ids;
     574
     575                while ( ! empty( $id_queue ) ) {
     576                        $id = array_shift( $id_queue );
     577                        if ( ! isset( $this->registered[ $id ] ) ) {
     578                                continue;
     579                        }
     580
     581                        foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) {
     582                                if (
     583                                        ! isset( $all_dependencies[ $dependency['id'] ] ) &&
     584                                        in_array( $dependency['import'], $import_types, true ) &&
     585                                        isset( $this->registered[ $dependency['id'] ] )
     586                                ) {
     587                                        $all_dependencies[ $dependency['id'] ] = true;
     588
     589                                        // Add this dependency to the list to get dependencies for.
     590                                        $id_queue[] = $dependency['id'];
    458591                                }
    459                                 return array_merge( $dependency_script_modules, $dependencies, $this->get_dependencies( array_keys( $dependencies ), $import_types ) );
    460                         },
    461                         array()
    462                 );
     592                        }
     593                }
     594
     595                return array_keys( $all_dependencies );
    463596        }
    464597
    … …  
    507640         */
    508641        private function get_recursive_dependents( string $id ): array {
    509                 $get = function ( string $id, array $checked = array() ) use ( &$get ): array {
    510 
    511                         // If by chance an unregistered script module is checked or there is a recursive dependency, return early.
    512                         if ( ! isset( $this->registered[ $id ] ) || isset( $checked[ $id ] ) ) {
    513                                 return array();
    514                         }
    515 
    516                         // Mark this script module as checked to guard against infinite recursion.
    517                         $checked[ $id ] = true;
    518 
    519                         $dependents = array();
    520                         foreach ( $this->get_dependents( $id ) as $dependent ) {
    521                                 $dependents = array_merge(
    522                                         $dependents,
    523                                         array( $dependent ),
    524                                         $get( $dependent, $checked )
    525                                 );
    526                         }
    527 
    528                         return $dependents;
    529                 };
    530 
    531                 return array_unique( $get( $id ) );
     642                $dependents = array();
     643                $id_queue   = array( $id );
     644                $processed  = array();
     645
     646                while ( ! empty( $id_queue ) ) {
     647                        $current_id = array_shift( $id_queue );
     648
     649                        // Skip unregistered or already-processed script modules.
     650                        if ( ! isset( $this->registered[ $current_id ] ) || isset( $processed[ $current_id ] ) ) {
     651                                continue;
     652                        }
     653
     654                        // Mark as processed to guard against infinite loops from circular dependencies.
     655                        $processed[ $current_id ] = true;
     656
     657                        // Find the direct dependents of the current script.
     658                        foreach ( $this->get_dependents( $current_id ) as $dependent_id ) {
     659                                // Only add the dependent if we haven't found it before.
     660                                if ( ! isset( $dependents[ $dependent_id ] ) ) {
     661                                        $dependents[ $dependent_id ] = true;
     662
     663                                        // Add dependency to the queue.
     664                                        $id_queue[] = $dependent_id;
     665                                }
     666                        }
     667                }
     668
     669                return array_keys( $dependents );
     670        }
     671
     672        /**
     673         * Sorts the given script module identifiers based on their dependencies.
     674         *
     675         * It will return a list of script module identifiers sorted in the order
     676         * they should be printed, so that dependencies are printed before the script
     677         * modules that depend on them.
     678         *
     679         * @since 6.9.0
     680         *
     681         * @param string[] $ids          The identifiers of the script modules to sort.
     682         * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both.
     683         *                                         Default is both.
     684         * @return string[] Sorted list of script module identifiers.
     685         */
     686        private function get_sorted_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array {
     687                $sorted = array();
     688
     689                foreach ( $ids as $id ) {
     690                        $this->sort_item_dependencies( $id, $import_types, $sorted );
     691                }
     692
     693                return array_unique( $sorted );
     694        }
     695
     696        /**
     697         * Recursively sorts the dependencies for a single script module identifier.
     698         *
     699         * @since 6.9.0
     700         *
     701         * @param string   $id           The identifier of the script module to sort.
     702         * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both.
     703         * @param string[] &$sorted      The array of sorted identifiers, passed by reference.
     704         * @return bool True on success, false on failure (e.g., missing dependency).
     705         */
     706        private function sort_item_dependencies( string $id, array $import_types, array &$sorted ): bool {
     707                // If already processed, don't do it again.
     708                if ( in_array( $id, $sorted, true ) ) {
     709                        return true;
     710                }
     711
     712                // If the item doesn't exist, fail.
     713                if ( ! isset( $this->registered[ $id ] ) ) {
     714                        return false;
     715                }
     716
     717                $dependency_ids = array();
     718                foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) {
     719                        if ( in_array( $dependency['import'], $import_types, true ) ) {
     720                                $dependency_ids[] = $dependency['id'];
     721                        }
     722                }
     723
     724                // If the item requires dependencies that do not exist, fail.
     725                if ( count( array_diff( $dependency_ids, array_keys( $this->registered ) ) ) > 0 ) {
     726                        return false;
     727                }
     728
     729                // Recursively process dependencies.
     730                foreach ( $dependency_ids as $dependency_id ) {
     731                        if ( ! $this->sort_item_dependencies( $dependency_id, $import_types, $sorted ) ) {
     732                                // A dependency failed to resolve, so this branch fails.
     733                                return false;
     734                        }
     735                }
     736
     737                // All dependencies are sorted, so we can now add the current item.
     738                $sorted[] = $id;
     739
     740                return true;
    532741        }
    533742
    … …  
    552761                $src           = $script_module['src'];
    553762
    554                 if ( false === $script_module['version'] ) {
    555                         $src = add_query_arg( 'ver', get_bloginfo( 'version' ), $src );
    556                 } elseif ( null !== $script_module['version'] ) {
    557                         $src = add_query_arg( 'ver', $script_module['version'], $src );
     763                if ( '' !== $src ) {
     764                        if ( false === $script_module['version'] ) {
     765                                $src = add_query_arg( 'ver', get_bloginfo( 'version' ), $src );
     766                        } elseif ( null !== $script_module['version'] ) {
     767                                $src = add_query_arg( 'ver', $script_module['version'], $src );
     768                        }
    558769                }
    559770
    … …  
    567778                 */
    568779                $src = apply_filters( 'script_module_loader_src', $src, $id );
     780                if ( ! is_string( $src ) ) {
     781                        $src = '';
     782                }
    569783
    570784                return $src;
    … …  
    682896
    683897                                wp_print_inline_script_tag(
    684                                         wp_json_encode(
     898                                        (string) wp_json_encode(
    685899                                                $data,
    686900                                                $json_encode_flags
  • trunk/src/wp-includes/script-modules.php

    r60931 r60999  
    6565 *     Optional. An array of additional args. Default empty array.
    6666 *
     67 *     @type bool                $in_footer     Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional.
    6768 *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
    6869 * }
    … …  
    108109 *     Optional. An array of additional args. Default empty array.
    109110 *
     111 *     @type bool                $in_footer     Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional.
    110112 *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
    111113 * }
    … …  
    184186                /*
    185187                 * The Interactivity API is designed with server-side rendering as its primary goal, so all of its script modules
    186                  * should be loaded with low fetchpriority since they should not be needed in the critical rendering path.
    187                  * Also, the @wordpress/a11y script module is intended to be used as a dynamic import dependency, in which case
    188                  * the fetchpriority is irrelevant. See <https://make.wordpress.org/core/2024/10/14/updates-to-script-modules-in-6-7/>.
     188                 * should be loaded with low fetchpriority and printed in the footer since they should not be needed in the
     189                 * critical rendering path. Also, the @wordpress/a11y script module is intended to be used as a dynamic import
     190                 * dependency, in which case the fetchpriority is irrelevant. See <https://make.wordpress.org/core/2024/10/14/updates-to-script-modules-in-6-7/>.
    189191                 * However, in case it is added as a static import dependency, the fetchpriority is explicitly set to be 'low'
    190192                 * since the module should not be involved in the critical rendering path, and if it is, its fetchpriority will
    … …  
    198200                ) {
    199201                        $args['fetchpriority'] = 'low';
     202                        $args['in_footer']     = true;
    200203                }
    201204
  • trunk/tests/phpunit/tests/script-modules/wpScriptModules.php

    r60951 r60999  
    5757         */
    5858        public function get_enqueued_script_modules(): array {
    59                 $modules = array();
    60 
    61                 $p = new WP_HTML_Tag_Processor( get_echo( array( $this->script_modules, 'print_enqueued_script_modules' ) ) );
    62                 while ( $p->next_tag( array( 'tag' => 'SCRIPT' ) ) ) {
    63                         $this->assertSame( 'module', $p->get_attribute( 'type' ) );
    64                         $this->assertIsString( $p->get_attribute( 'id' ) );
    65                         $this->assertIsString( $p->get_attribute( 'src' ) );
    66                         $this->assertStringEndsWith( '-js-module', $p->get_attribute( 'id' ) );
    67 
    68                         $id             = preg_replace( '/-js-module$/', '', (string) $p->get_attribute( 'id' ) );
    69                         $fetchpriority  = $p->get_attribute( 'fetchpriority' );
    70                         $modules[ $id ] = array_merge(
    71                                 array(
    72                                         'url'           => $p->get_attribute( 'src' ),
    73                                         'fetchpriority' => is_string( $fetchpriority ) ? $fetchpriority : 'auto',
    74                                 ),
    75                                 ...array_map(
    76                                         static function ( $attribute_name ) use ( $p ) {
    77                                                 return array( $attribute_name => $p->get_attribute( $attribute_name ) );
    78                                         },
    79                                         $p->get_attribute_names_with_prefix( 'data-' )
    80                                 )
    81                         );
    82                 }
     59                $get_modules = function ( string $html, bool $in_footer ): array {
     60                        $modules = array();
     61                        $p       = new WP_HTML_Tag_Processor( $html );
     62                        while ( $p->next_tag( array( 'tag' => 'SCRIPT' ) ) ) {
     63                                $this->assertSame( 'module', $p->get_attribute( 'type' ) );
     64                                $this->assertIsString( $p->get_attribute( 'id' ) );
     65                                $this->assertIsString( $p->get_attribute( 'src' ) );
     66                                $this->assertStringEndsWith( '-js-module', $p->get_attribute( 'id' ) );
     67
     68                                $id             = preg_replace( '/-js-module$/', '', (string) $p->get_attribute( 'id' ) );
     69                                $fetchpriority  = $p->get_attribute( 'fetchpriority' );
     70                                $modules[ $id ] = array_merge(
     71                                        array(
     72                                                'url'           => $p->get_attribute( 'src' ),
     73                                                'fetchpriority' => is_string( $fetchpriority ) ? $fetchpriority : 'auto',
     74                                                'in_footer'     => $in_footer,
     75                                        ),
     76                                        ...array_map(
     77                                                static function ( $attribute_name ) use ( $p ) {
     78                                                        return array( $attribute_name => $p->get_attribute( $attribute_name ) );
     79                                                },
     80                                                $p->get_attribute_names_with_prefix( 'data-' )
     81                                        )
     82                                );
     83                        }
     84                        return $modules;
     85                };
     86
     87                $modules = array_merge(
     88                        $get_modules( get_echo( array( $this->script_modules, 'print_head_enqueued_script_modules' ) ), false ),
     89                        $get_modules( get_echo( array( $this->script_modules, 'print_enqueued_script_modules' ) ), true )
     90                );
    8391
    8492                return $modules;
    … …  
    148156
    149157        /**
     158         * Test wp_register_script_module() with empty ID.
     159         *
     160         * @ticket 63486
     161         *
     162         * @expectedIncorrectUsage WP_Script_Modules::register
     163         *
     164         * @covers ::wp_register_script_module
     165         * @covers WP_Script_Modules::register
     166         */
     167        public function test_register_with_empty_id() {
     168                wp_register_script_module( '', '/null-and-void.js' );
     169                $this->assertArrayNotHasKey( '', $this->get_registered_script_modules( wp_script_modules() ) );
     170        }
     171
     172        /**
     173         * Test wp_enqueue_script_module() with empty ID.
     174         *
     175         * @ticket 63486
     176         *
     177         * @expectedIncorrectUsage WP_Script_Modules::enqueue
     178         *
     179         * @covers ::wp_enqueue_script_module
     180         * @covers WP_Script_Modules::enqueue
     181         */
     182        public function test_enqueue_with_empty_id() {
     183                wp_enqueue_script_module( '', '/null-and-void.js' );
     184                $this->assertArrayNotHasKey( '', $this->get_registered_script_modules( wp_script_modules() ) );
     185                $this->assertNotContains( '', wp_script_modules()->get_queue() );
     186        }
     187
     188        /**
    150189         * Tests various ways of registering, enqueueing, dequeuing, and deregistering a script module.
    151190         *
    … …  
    153192         *
    154193         * @ticket 56313
     194         * @ticket 63486
    155195         *
    156196         * @dataProvider data_test_register_and_enqueue_script_module
    … …  
    164204         * @covers ::wp_deregister_script_module()
    165205         * @covers WP_Script_Modules::deregister()
     206         * @covers WP_Script_Modules::get_queue()
     207         * @covers WP_Script_Modules::get_marked_for_enqueue()
    166208         * @covers WP_Script_Modules::set_fetchpriority()
     209         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    167210         * @covers WP_Script_Modules::print_enqueued_script_modules()
    168211         * @covers WP_Script_Modules::print_import_map()
    … …  
    180223                        }
    181224                };
     225
     226                $reflection_class       = new ReflectionClass( wp_script_modules() );
     227                $get_marked_for_enqueue = $reflection_class->getMethod( 'get_marked_for_enqueue' );
     228                if ( PHP_VERSION_ID < 80100 ) {
     229                        $get_marked_for_enqueue->setAccessible( true );
     230                }
    182231
    183232                $register_and_enqueue = static function ( ...$args ) use ( $use_global_function, $only_enqueue ) {
    … …  
    201250                // Minimal args.
    202251                $register_and_enqueue( 'a', '/a.js' );
     252                $this->assertSame( array( 'a' ), wp_script_modules()->get_queue(), 'Expected queue to match.' );
     253                $marked_for_enqueue = $get_marked_for_enqueue->invoke( wp_script_modules() );
     254                $this->assertSame( wp_script_modules()->get_queue(), array_keys( $marked_for_enqueue ), 'Expected get_queue() to match keys returned by get_marked_for_enqueue().' );
     255                $this->assertIsArray( $marked_for_enqueue['a'], 'Expected script module "a" to have an array entry.' );
     256                $this->assertSame( '/a.js', $marked_for_enqueue['a']['src'], 'Expected script module "a" to have the given src.' );
    203257
    204258                // One Dependency.
    … …  
    206260                $register_and_enqueue( 'b', '/b.js', array( 'b-dep' ) );
    207261                $this->assertTrue( wp_script_modules()->set_fetchpriority( 'b', 'low' ) );
     262                $this->assertSame( array( 'a', 'b' ), wp_script_modules()->get_queue() );
    208263
    209264                // Two dependencies with different formats and a false version.
    … …  
    222277                        false
    223278                );
     279                $this->assertSame( array( 'a', 'b', 'c' ), wp_script_modules()->get_queue() );
    224280
    225281                // Two dependencies, one imported statically and the other dynamically, with a null version.
    … …  
    241297                        null
    242298                );
     299                $this->assertSame( array( 'a', 'b', 'c', 'd' ), wp_script_modules()->get_queue() );
    243300
    244301                // No dependencies, with a string version version.
    … …  
    249306                        '1.0.0'
    250307                );
     308                $this->assertSame( array( 'a', 'b', 'c', 'd', 'e' ), wp_script_modules()->get_queue() );
    251309
    252310                // No dependencies, with a string version and fetch priority.
    … …  
    258316                        array( 'fetchpriority' => 'auto' )
    259317                );
     318                $this->assertSame( array( 'a', 'b', 'c', 'd', 'e', 'f' ), wp_script_modules()->get_queue() );
    260319
    261320                // No dependencies, with a string version and fetch priority of low.
    … …  
    267326                        array( 'fetchpriority' => 'low' )
    268327                );
     328                $this->assertSame( array( 'a', 'b', 'c', 'd', 'e', 'f', 'g' ), wp_script_modules()->get_queue() );
    269329
    270330                // No dependencies, with a string version and fetch priority of high.
    … …  
    276336                        array( 'fetchpriority' => 'high' )
    277337                );
     338                $this->assertSame( array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' ), wp_script_modules()->get_queue() );
     339
     340                // Register and enqueue something which we'll dequeue right away.
     341                $register_and_enqueue(
     342                        'i',
     343                        '/i.js',
     344                        array(),
     345                        '3.0.0'
     346                );
     347                $this->assertSame( array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ), wp_script_modules()->get_queue() );
     348
     349                // Register and enqueue something which we'll deregister right away.
     350                $register_and_enqueue(
     351                        'j',
     352                        '/j.js',
     353                        array(),
     354                        '3.0.0'
     355                );
     356                $this->assertSame( array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' ), wp_script_modules()->get_queue() );
     357
     358                // Make sure unregister functions work.
     359                $deregister_id = 'j';
     360                $this->assertArrayHasKey( 'j', $this->get_registered_script_modules( $this->script_modules ) );
     361                if ( $use_global_function ) {
     362                        wp_deregister_script_module( $deregister_id );
     363                } else {
     364                        wp_script_modules()->deregister( $deregister_id );
     365                }
     366                $this->assertArrayNotHasKey( 'j', $this->get_registered_script_modules( $this->script_modules ) );
     367                $this->assertSame( array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ), wp_script_modules()->get_queue() );
     368
     369                // Make sure dequeue functions work.
     370                $dequeue_id = 'i';
     371                $this->assertArrayHasKey( 'i', $this->get_registered_script_modules( $this->script_modules ) );
     372                if ( $use_global_function ) {
     373                        wp_dequeue_script_module( $dequeue_id );
     374                } else {
     375                        wp_script_modules()->dequeue( $dequeue_id );
     376                }
     377                $this->assertArrayHasKey( 'i', $this->get_registered_script_modules( $this->script_modules ) );
     378                $this->assertSame( array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' ), wp_script_modules()->get_queue() );
    278379
    279380                $actual = array(
    … …  
    309410                                                'url'           => '/a.js?ver=99.9.9',
    310411                                                'fetchpriority' => 'auto',
     412                                                'in_footer'     => false,
    311413                                        ),
    312414                                        'b' => array(
    313415                                                'url'           => '/b.js?ver=99.9.9',
    314416                                                'fetchpriority' => 'low',
     417                                                'in_footer'     => false,
    315418                                        ),
    316419                                        'c' => array(
    317420                                                'url'           => '/c.js?ver=99.9.9',
    318421                                                'fetchpriority' => 'auto',
     422                                                'in_footer'     => false,
    319423                                        ),
    320424                                        'd' => array(
    321425                                                'url'           => '/d.js',
    322426                                                'fetchpriority' => 'auto',
     427                                                'in_footer'     => false,
    323428                                        ),
    324429                                        'e' => array(
    325430                                                'url'           => '/e.js?ver=1.0.0',
    326431                                                'fetchpriority' => 'auto',
     432                                                'in_footer'     => false,
    327433                                        ),
    328434                                        'f' => array(
    329435                                                'url'           => '/f.js?ver=2.0.0',
    330436                                                'fetchpriority' => 'auto',
     437                                                'in_footer'     => false,
    331438                                        ),
    332439                                        'g' => array(
    333440                                                'url'           => '/g.js?ver=2.0.0',
    334441                                                'fetchpriority' => 'low',
     442                                                'in_footer'     => false,
    335443                                        ),
    336444                                        'h' => array(
    337445                                                'url'           => '/h.js?ver=3.0.0',
    338446                                                'fetchpriority' => 'high',
     447                                                'in_footer'     => false,
    339448                                        ),
    340449                                ),
    … …  
    346455                                        'd-dynamic-dep' => '/d-dynamic-dep.js?ver=99.9.9',
    347456                                ),
    348                         ),
    349                         $actual,
    350                         "Snapshot:\n" . var_export( $actual, true )
    351                 );
    352 
    353                 // Dequeue the first half of the scripts.
    354                 foreach ( array( 'a', 'b', 'c', 'd' ) as $id ) {
    355                         if ( $use_global_function ) {
    356                                 wp_dequeue_script_module( $id );
    357                         } else {
    358                                 wp_script_modules()->dequeue( $id );
    359                         }
    360                 }
    361 
    362                 $actual = array(
    363                         'preload_links' => $this->get_preloaded_script_modules(),
    364                         'script_tags'   => $this->get_enqueued_script_modules(),
    365                         'import_map'    => $this->get_import_map(),
    366                 );
    367                 $this->assertSame(
    368                         array(
    369                                 'preload_links' => array(),
    370                                 'script_tags'   => array(
    371                                         'e' => array(
    372                                                 'url'           => '/e.js?ver=1.0.0',
    373                                                 'fetchpriority' => 'auto',
    374                                         ),
    375                                         'f' => array(
    376                                                 'url'           => '/f.js?ver=2.0.0',
    377                                                 'fetchpriority' => 'auto',
    378                                         ),
    379                                         'g' => array(
    380                                                 'url'           => '/g.js?ver=2.0.0',
    381                                                 'fetchpriority' => 'low',
    382                                         ),
    383                                         'h' => array(
    384                                                 'url'           => '/h.js?ver=3.0.0',
    385                                                 'fetchpriority' => 'high',
    386                                         ),
    387                                 ),
    388                                 'import_map'    => array(),
    389                         ),
    390                         $actual,
    391                         "Snapshot:\n" . var_export( $actual, true )
    392                 );
    393 
    394                 // Unregister the remaining scripts.
    395                 foreach ( array( 'e', 'f', 'g', 'h' ) as $id ) {
    396                         if ( $use_global_function ) {
    397                                 wp_dequeue_script_module( $id );
    398                         } else {
    399                                 wp_script_modules()->dequeue( $id );
    400                         }
    401                 }
    402 
    403                 $actual = array(
    404                         'preload_links' => $this->get_preloaded_script_modules(),
    405                         'script_tags'   => $this->get_enqueued_script_modules(),
    406                         'import_map'    => $this->get_import_map(),
    407                 );
    408                 $this->assertSame(
    409                         array(
    410                                 'preload_links' => array(),
    411                                 'script_tags'   => array(),
    412                                 'import_map'    => array(),
    413457                        ),
    414458                        $actual,
    … …  
    443487         *
    444488         * @ticket 56313
     489         * @ticket 63486
    445490         *
    446491         * @covers WP_Script_Modules::register()
    447492         * @covers WP_Script_Modules::enqueue()
     493         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    448494         * @covers WP_Script_Modules::print_enqueued_script_modules()
    449495         * @covers WP_Script_Modules::set_fetchpriority()
     496         * @covers WP_Script_Modules::set_in_footer()
    450497         */
    451498        public function test_wp_enqueue_script_module() {
    452499                $this->script_modules->register( 'foo', '/foo.js' );
    453                 $this->script_modules->register( 'bar', '/bar.js', array(), false, array( 'fetchpriority' => 'high' ) );
     500                $this->script_modules->register(
     501                        'bar',
     502                        '/bar.js',
     503                        array(),
     504                        false,
     505                        array(
     506                                'fetchpriority' => 'high',
     507                                'in_footer'     => true,
     508                        )
     509                );
    454510                $this->script_modules->register( 'baz', '/baz.js' );
    455511                $this->assertTrue( $this->script_modules->set_fetchpriority( 'baz', 'low' ) );
     512                $this->assertTrue( $this->script_modules->set_in_footer( 'baz', true ) );
    456513                $this->script_modules->enqueue( 'foo' );
    457514                $this->script_modules->enqueue( 'bar' );
    … …  
    463520                $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
    464521                $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
     522                $this->assertFalse( $enqueued_script_modules['foo']['in_footer'] );
    465523                $this->assertStringStartsWith( '/bar.js', $enqueued_script_modules['bar']['url'] );
    466524                $this->assertSame( 'high', $enqueued_script_modules['bar']['fetchpriority'] );
     525                $this->assertTrue( $enqueued_script_modules['bar']['in_footer'] );
    467526                $this->assertStringStartsWith( '/baz.js', $enqueued_script_modules['baz']['url'] );
    468527                $this->assertSame( 'low', $enqueued_script_modules['baz']['fetchpriority'] );
     528                $this->assertTrue( $enqueued_script_modules['baz']['in_footer'] );
     529        }
     530
     531        /**
     532         * Tests that no script is printed for a script without a src.
     533         *
     534         * @ticket 63486
     535         *
     536         * @covers WP_Script_Modules::register()
     537         * @covers WP_Script_Modules::enqueue()
     538         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
     539         * @covers WP_Script_Modules::print_enqueued_script_modules()
     540         * @covers WP_Script_Modules::get_src()
     541         */
     542        public function test_wp_enqueue_script_module_with_empty_src() {
     543                wp_enqueue_script_module( 'with-src', '/src.js' );
     544                wp_register_script_module( 'without-src', '' );
     545                wp_register_script_module( 'without-src-but-filtered', '' );
     546                wp_enqueue_script_module( 'without-src' );
     547                wp_enqueue_script_module( 'without-src-but-filtered' );
     548                $this->assertSame( array( 'with-src', 'without-src', 'without-src-but-filtered' ), wp_script_modules()->get_queue() );
     549                add_filter(
     550                        'script_module_loader_src',
     551                        static function ( $src, $id ) {
     552                                if ( 'without-src-but-filtered' === $id ) {
     553                                        $src = '/was-empty-but-added-via-filter.js';
     554                                }
     555                                return $src;
     556                        },
     557                        10,
     558                        2
     559                );
     560                $actual = get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) );
     561                $this->assertEqualHTML(
     562                        '
     563                                <script type="module" src="/src.js?ver=6.9-alpha-60093-src" id="with-src-js-module"></script>
     564                                <script type="module" src="/was-empty-but-added-via-filter.js" id="without-src-but-filtered-js-module"></script>
     565                        ',
     566                        $actual,
     567                        '<body>',
     568                        "Expected only one SCRIPT tag to be printed. Snapshot:\n$actual"
     569                );
    469570        }
    470571
    … …  
    473574        *
    474575        * @ticket 56313
     576        * @ticket 63486
    475577        *
    476578        * @covers WP_Script_Modules::register()
    477579        * @covers WP_Script_Modules::enqueue()
    478580        * @covers WP_Script_Modules::dequeue()
     581        * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    479582        * @covers WP_Script_Modules::print_enqueued_script_modules()
    480583        */
    … …  
    571674        *
    572675        * @ticket 56313
     676        * @ticket 63486
    573677        *
    574678        * @covers WP_Script_Modules::register()
    575679        * @covers WP_Script_Modules::enqueue()
     680        * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    576681        * @covers WP_Script_Modules::print_enqueued_script_modules()
    577682        */
    … …  
    594699         *
    595700         * @ticket 56313
     701         * @ticket 63486
    596702         *
    597703         * @covers WP_Script_Modules::register()
    598704         * @covers WP_Script_Modules::enqueue()
    599705         * @covers WP_Script_Modules::dequeue()
     706         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    600707         * @covers WP_Script_Modules::print_enqueued_script_modules()
    601708         */
    … …  
    9611068         *
    9621069         * @ticket 56313
     1070         * @ticket 63486
    9631071         *
    9641072         * @covers WP_Script_Modules::register()
    9651073         * @covers WP_Script_Modules::enqueue()
     1074         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    9661075         * @covers WP_Script_Modules::print_enqueued_script_modules()
    9671076         * @covers WP_Script_Modules::print_import_map()
    … …  
    9991108         *
    10001109         * @ticket 56313
     1110         * @ticket 63486
    10011111         *
    10021112         * @covers WP_Script_Modules::enqueue()
     1113         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    10031114         * @covers WP_Script_Modules::print_enqueued_script_modules()
    10041115         */
    … …  
    10171128         *
    10181129         * @ticket 56313
     1130         * @ticket 63486
    10191131         *
    10201132         * @covers WP_Script_Modules::enqueue()
     1133         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    10211134         * @covers WP_Script_Modules::print_enqueued_script_modules()
    10221135         */
    … …  
    10361149         *
    10371150         * @ticket 56313
     1151         * @ticket 63486
    10381152         *
    10391153         * @covers WP_Script_Modules::enqueue()
     1154         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    10401155         * @covers WP_Script_Modules::print_enqueued_script_modules()
    10411156         */
    … …  
    10621177         *
    10631178         * @ticket 56313
     1179         * @ticket 63486
    10641180         *
    10651181         * @covers WP_Script_Modules::register()
    10661182         * @covers WP_Script_Modules::enqueue()
     1183         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
    10671184         * @covers WP_Script_Modules::print_enqueued_script_modules()
    10681185         * @covers WP_Script_Modules::print_import_map()
    … …  
    13161433
    13171434        /**
     1435         * Tests ways of setting in_footer.
     1436         *
     1437         * @ticket 63486
     1438         * @ticket 63486
     1439         *
     1440         * @covers ::wp_register_script_module
     1441         * @covers ::wp_enqueue_script_module
     1442         * @covers WP_Script_Modules::set_in_footer
     1443         */
     1444        public function test_in_footer_methods() {
     1445                wp_register_script_module( 'default', '/default.js', array(), null );
     1446                wp_enqueue_script_module( 'default' );
     1447
     1448                wp_register_script_module( 'in-footer-via-register', '/in-footer-via-register.js', array(), null, array( 'in_footer' => true ) );
     1449                wp_enqueue_script_module( 'in-footer-via-register' );
     1450
     1451                wp_enqueue_script_module( 'in-footer-via-enqueue', '/in-footer-via-enqueue.js', array(), null, array( 'in_footer' => true ) );
     1452
     1453                wp_enqueue_script_module( 'not-in-footer-via-enqueue', '/not-in-footer-via-enqueue.js', array(), null, array( 'in_footer' => false ) );
     1454
     1455                wp_enqueue_script_module( 'in-footer-via-override', '/in-footer-via-override.js' );
     1456                wp_script_modules()->set_in_footer( 'in-footer-via-override', true );
     1457
     1458                wp_enqueue_script_module( 'not-in-footer-via-override', '/not-in-footer-via-override.js', array(), null, array( 'in_footer' => true ) );
     1459                wp_script_modules()->set_in_footer( 'not-in-footer-via-override', false );
     1460
     1461                $actual_head   = get_echo( array( wp_script_modules(), 'print_head_enqueued_script_modules' ) );
     1462                $actual_footer = get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) );
     1463
     1464                $this->assertEqualHTML(
     1465                        $actual_head,
     1466                        '
     1467                                <script type="module" src="/default.js" id="default-js-module"></script>
     1468                                <script type="module" src="/not-in-footer-via-enqueue.js" id="not-in-footer-via-enqueue-js-module"></script>
     1469                                <script type="module" src="/not-in-footer-via-override.js" id="not-in-footer-via-override-js-module"></script>
     1470                        ',
     1471                        '<body>',
     1472                        "Expected equal script modules in the HEAD. Snapshot:\n$actual_head"
     1473                );
     1474                $this->assertEqualHTML(
     1475                        $actual_footer,
     1476                        '
     1477                                <script type="module" src="/in-footer-via-register.js" id="in-footer-via-register-js-module"></script>
     1478                                <script type="module" src="/in-footer-via-enqueue.js" id="in-footer-via-enqueue-js-module"></script>
     1479                                <script type="module" src="/in-footer-via-override.js?ver=6.9-alpha-60093-src" id="in-footer-via-override-js-module"></script>
     1480                        ',
     1481                        '<body>',
     1482                        "Expected equal script modules in the footer. Snapshot:\n$actual_footer"
     1483                );
     1484        }
     1485
     1486        /**
    13181487         * Tests that a script module with an invalid fetchpriority value gets a value of auto.
    13191488         *
    … …  
    13781547                                                        'url'                   => '/bajo.js',
    13791548                                                        'fetchpriority'         => 'high',
     1549                                                        'in_footer'             => false,
    13801550                                                        'data-wp-fetchpriority' => 'low',
    13811551                                                ),
    … …  
    14001570                                                        'url'                   => '/auto.js',
    14011571                                                        'fetchpriority'         => 'high',
     1572                                                        'in_footer'             => false,
    14021573                                                        'data-wp-fetchpriority' => 'auto',
    14031574                                                ),
    … …  
    14131584                                'expected' => array(
    14141585                                        'preload_links' => array(
    1415                                                 'auto' => array(
    1416                                                         'url'           => '/auto.js',
    1417                                                         'fetchpriority' => 'high',
    1418                                                 ),
    14191586                                                'bajo' => array(
    14201587                                                        'url'                   => '/bajo.js',
    … …  
    14221589                                                        'data-wp-fetchpriority' => 'low',
    14231590                                                ),
     1591                                                'auto' => array(
     1592                                                        'url'           => '/auto.js',
     1593                                                        'fetchpriority' => 'high',
     1594                                                ),
    14241595                                        ),
    14251596                                        'script_tags'   => array(
    … …  
    14271598                                                        'url'           => '/alto.js',
    14281599                                                        'fetchpriority' => 'high',
     1600                                                        'in_footer'     => false,
    14291601                                                ),
    14301602                                        ),
    … …  
    15401712                $actual  .= get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) );
    15411713                $expected = '
    1542                         <link rel="modulepreload" href="/b.js" id="b-js-modulepreload" fetchpriority="low">
    1543                         <link rel="modulepreload" href="/c.js" id="c-js-modulepreload" fetchpriority="low">
     1714                        <link rel="modulepreload" href="/z.js" id="z-js-modulepreload" fetchpriority="high">
    15441715                        <link rel="modulepreload" href="/d.js" id="d-js-modulepreload" fetchpriority="high">
    15451716                        <link rel="modulepreload" href="/e.js" id="e-js-modulepreload" fetchpriority="low">
    1546                         <link rel="modulepreload" href="/z.js" id="z-js-modulepreload" fetchpriority="high">
     1717                        <link rel="modulepreload" href="/c.js" id="c-js-modulepreload" fetchpriority="low">
     1718                        <link rel="modulepreload" href="/b.js" id="b-js-modulepreload" fetchpriority="low">
    15471719                        <link rel="modulepreload" href="/y.js" id="y-js-modulepreload" fetchpriority="high">
    15481720                        <script type="module" src="/a.js" id="a-js-module" fetchpriority="low"></script>
    … …  
    15851757                $actual  .= get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) );
    15861758                $expected = '
    1587                         <link rel="modulepreload" href="/a.js" id="a-js-modulepreload" fetchpriority="low" data-wp-fetchpriority="high">
    15881759                        <link rel="modulepreload" href="/d.js" id="d-js-modulepreload" fetchpriority="low">
    15891760                        <link rel="modulepreload" href="/e.js" id="e-js-modulepreload" fetchpriority="high" data-wp-fetchpriority="low">
     1761                        <link rel="modulepreload" href="/a.js" id="a-js-modulepreload" fetchpriority="low" data-wp-fetchpriority="high">
    15901762                        <link rel="modulepreload" href="/b.js" id="b-js-modulepreload">
     1763                        <link rel="modulepreload" href="/f.js" id="f-js-modulepreload" fetchpriority="high">
    15911764                        <link rel="modulepreload" href="/c.js" id="c-js-modulepreload" fetchpriority="high">
    1592                         <link rel="modulepreload" href="/f.js" id="f-js-modulepreload" fetchpriority="high">
    15931765                        <script type="module" src="/x.js" id="x-js-module" fetchpriority="low"></script>
    15941766                        <script type="module" src="/y.js" id="y-js-module"></script>
    … …  
    16011773         * Tests that default script modules are printed as expected.
    16021774         *
     1775         * @ticket 63486
     1776         *
    16031777         * @covers ::wp_default_script_modules
    16041778         * @covers WP_Script_Modules::print_script_module_preloads
     1779         * @covers WP_Script_Modules::print_head_enqueued_script_modules
    16051780         * @covers WP_Script_Modules::print_enqueued_script_modules
    16061781         */
    … …  
    16101785                wp_enqueue_script_module( '@wordpress/block-library/navigation/view' );
    16111786
    1612                 $actual  = get_echo( array( wp_script_modules(), 'print_script_module_preloads' ) );
    1613                 $actual .= get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) );
    1614 
    1615                 $actual = $this->normalize_markup_for_snapshot( $actual );
    1616 
    1617                 $expected = '
    1618                         <link rel="modulepreload" href="/wp-includes/js/dist/script-modules/interactivity/debug.min.js" id="@wordpress/interactivity-js-modulepreload" fetchpriority="low">
    1619                         <script type="module" src="/wp-includes/js/dist/script-modules/a11y/index.min.js" id="@wordpress/a11y-js-module" fetchpriority="low"></script>
    1620                         <script type="module" src="/wp-includes/js/dist/script-modules/block-library/navigation/view.min.js" id="@wordpress/block-library/navigation/view-js-module" fetchpriority="low"></script>
    1621                 ';
    1622                 $this->assertEqualHTML( $expected, $actual, '<body>', "Snapshot:\n$actual" );
     1787                $actual_preloads = $this->normalize_markup_for_snapshot( get_echo( array( wp_script_modules(), 'print_script_module_preloads' ) ) );
     1788                $this->assertEqualHTML(
     1789                        '
     1790                                <link rel="modulepreload" href="/wp-includes/js/dist/script-modules/interactivity/debug.min.js" id="@wordpress/interactivity-js-modulepreload" fetchpriority="low">
     1791                        ',
     1792                        $actual_preloads,
     1793                        '<body>',
     1794                        "Snapshot:\n$actual_preloads"
     1795                );
     1796
     1797                $actual_head_script_modules = $this->normalize_markup_for_snapshot( get_echo( array( wp_script_modules(), 'print_head_enqueued_script_modules' ) ) );
     1798                $this->assertEqualHTML(
     1799                        '',
     1800                        $actual_head_script_modules,
     1801                        '<body>',
     1802                        "Snapshot:\n$actual_head_script_modules"
     1803                );
     1804
     1805                $actual_footer_script_modules = $this->normalize_markup_for_snapshot( get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) ) );
     1806                $this->assertEqualHTML(
     1807                        '
     1808                                <script type="module" src="/wp-includes/js/dist/script-modules/a11y/index.min.js" id="@wordpress/a11y-js-module" fetchpriority="low"></script>
     1809                                <script type="module" src="/wp-includes/js/dist/script-modules/block-library/navigation/view.min.js" id="@wordpress/block-library/navigation/view-js-module" fetchpriority="low"></script>
     1810                        ',
     1811                        $actual_footer_script_modules,
     1812                        '<body>',
     1813                        "Snapshot:\n$actual_footer_script_modules"
     1814                );
    16231815        }
    16241816
    … …  
    16471839                $expected = '
    16481840                        <link rel="modulepreload" href="/wp-includes/js/dist/script-modules/a11y/index.min.js" id="@wordpress/a11y-js-modulepreload" fetchpriority="high" data-wp-fetchpriority="low">
     1841                        <link rel="modulepreload" href="/wp-includes/js/dist/script-modules/interactivity/debug.min.js" id="@wordpress/interactivity-js-modulepreload" fetchpriority="high" data-wp-fetchpriority="low">
    16491842                        <link rel="modulepreload" href="/wp-includes/js/dist/script-modules/block-library/navigation/view.min.js" id="@wordpress/block-library/navigation/view-js-modulepreload" fetchpriority="high" data-wp-fetchpriority="low">
    1650                         <link rel="modulepreload" href="/wp-includes/js/dist/script-modules/interactivity/debug.min.js" id="@wordpress/interactivity-js-modulepreload" fetchpriority="high" data-wp-fetchpriority="low">
    16511843                        <script type="module" src="/super-important-module.js" id="super-important-js-module" fetchpriority="high"></script>
    16521844                ';
    … …  
    17311923                );
    17321924        }
     1925
     1926        /**
     1927         * Tests various ways of printing and dependency ordering of script modules.
     1928         *
     1929         * This ensures that the global function aliases pass all the same parameters as the class methods.
     1930         *
     1931         * @ticket 63486
     1932         *
     1933         * @dataProvider data_test_register_and_enqueue_script_module
     1934         *
     1935         * @covers ::wp_register_script_module()
     1936         * @covers WP_Script_Modules::register()
     1937         * @covers ::wp_enqueue_script_module()
     1938         * @covers WP_Script_Modules::enqueue()
     1939         * @covers ::wp_dequeue_script_module()
     1940         * @covers WP_Script_Modules::dequeue()
     1941         * @covers ::wp_deregister_script_module()
     1942         * @covers WP_Script_Modules::deregister()
     1943         * @covers WP_Script_Modules::set_fetchpriority()
     1944         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
     1945         * @covers WP_Script_Modules::print_enqueued_script_modules()
     1946         * @covers WP_Script_Modules::print_import_map()
     1947         * @covers WP_Script_Modules::print_script_module_preloads()
     1948         */
     1949        public function test_script_module_printing_and_dependency_ordering( bool $use_global_function, bool $only_enqueue ) {
     1950                global $wp_version;
     1951                $wp_version = '99.9.9';
     1952
     1953                $register = static function ( ...$args ) use ( $use_global_function ) {
     1954                        if ( $use_global_function ) {
     1955                                wp_register_script_module( ...$args );
     1956                        } else {
     1957                                wp_script_modules()->register( ...$args );
     1958                        }
     1959                };
     1960
     1961                $register_and_enqueue = static function ( ...$args ) use ( $use_global_function, $only_enqueue ) {
     1962                        if ( $use_global_function ) {
     1963                                if ( $only_enqueue ) {
     1964                                        wp_enqueue_script_module( ...$args );
     1965                                } else {
     1966                                        wp_register_script_module( ...$args );
     1967                                        wp_enqueue_script_module( $args[0] );
     1968                                }
     1969                        } else {
     1970                                if ( $only_enqueue ) {
     1971                                        wp_script_modules()->enqueue( ...$args );
     1972                                } else {
     1973                                        wp_script_modules()->register( ...$args );
     1974                                        wp_script_modules()->enqueue( $args[0] );
     1975                                }
     1976                        }
     1977                };
     1978
     1979                $deregister = static function ( array $ids ) use ( $use_global_function ) {
     1980                        foreach ( $ids as $id ) {
     1981                                if ( $use_global_function ) {
     1982                                        wp_deregister_script_module( $id );
     1983                                } else {
     1984                                        wp_script_modules()->deregister( $id );
     1985                                }
     1986                        }
     1987                };
     1988
     1989                // Test script module is placed in footer when in_footer is true.
     1990                $register_and_enqueue( 'a', '/a.js', array(), '1.0.0', array( 'in_footer' => true ) );
     1991
     1992                $actual = array(
     1993                        'preload_links' => $this->get_preloaded_script_modules(),
     1994                        'script_tags'   => $this->get_enqueued_script_modules(),
     1995                        'import_map'    => $this->get_import_map(),
     1996                );
     1997                $this->assertSame(
     1998                        array(
     1999                                'preload_links' => array(),
     2000                                'script_tags'   => array(
     2001                                        'a' => array(
     2002                                                'url'           => '/a.js?ver=1.0.0',
     2003                                                'fetchpriority' => 'auto',
     2004                                                'in_footer'     => true,
     2005                                        ),
     2006                                ),
     2007                                'import_map'    => array(),
     2008                        ),
     2009                        $actual,
     2010                        "Snapshot:\n" . var_export( $actual, true )
     2011                );
     2012
     2013                $deregister( array( 'a' ) );
     2014
     2015                // Test that dependant also gets placed in footer when its dependency is in footer.
     2016                $register_and_enqueue( 'b', '/b.js', array(), '1.0.0', array( 'in_footer' => true ) );
     2017                $register_and_enqueue( 'c', '/c.js', array( 'b' ), '1.0.0' );
     2018
     2019                $actual = array(
     2020                        'preload_links' => $this->get_preloaded_script_modules(),
     2021                        'script_tags'   => $this->get_enqueued_script_modules(),
     2022                        'import_map'    => $this->get_import_map(),
     2023                );
     2024                $this->assertSame(
     2025                        array(
     2026                                'preload_links' => array(),
     2027                                'script_tags'   => array(
     2028                                        'b' => array(
     2029                                                'url'           => '/b.js?ver=1.0.0',
     2030                                                'fetchpriority' => 'auto',
     2031                                                'in_footer'     => true,
     2032                                        ),
     2033                                        'c' => array(
     2034                                                'url'           => '/c.js?ver=1.0.0',
     2035                                                'fetchpriority' => 'auto',
     2036                                                'in_footer'     => true,
     2037                                        ),
     2038                                ),
     2039                                'import_map'    => array(
     2040                                        'b' => '/b.js?ver=1.0.0',
     2041                                ),
     2042                        ),
     2043                        $actual,
     2044                        "Snapshot:\n" . var_export( $actual, true )
     2045                );
     2046
     2047                $deregister( array( 'b', 'c ' ) );
     2048
     2049                // Test that registered dependency in footer doesn't place dependant in footer.
     2050                $register( 'd', '/d.js', array(), '1.0.0', array( 'in_footer' => true ) );
     2051                $register_and_enqueue( 'e', '/e.js', array( 'd' ), '1.0.0' );
     2052
     2053                $actual = array(
     2054                        'preload_links' => $this->get_preloaded_script_modules(),
     2055                        'script_tags'   => $this->get_enqueued_script_modules(),
     2056                        'import_map'    => $this->get_import_map(),
     2057                );
     2058                $this->assertSame(
     2059                        array(
     2060                                'preload_links' => array(
     2061                                        'd' => array(
     2062                                                'url'           => '/d.js?ver=1.0.0',
     2063                                                'fetchpriority' => 'auto',
     2064                                        ),
     2065                                ),
     2066                                'script_tags'   => array(
     2067                                        'e' => array(
     2068                                                'url'           => '/e.js?ver=1.0.0',
     2069                                                'fetchpriority' => 'auto',
     2070                                                'in_footer'     => false,
     2071                                        ),
     2072                                ),
     2073                                'import_map'    => array(
     2074                                        'd' => '/d.js?ver=1.0.0',
     2075                                ),
     2076                        ),
     2077                        $actual,
     2078                        "Snapshot:\n" . var_export( $actual, true )
     2079                );
     2080
     2081                $deregister( array( 'd', 'e' ) );
     2082
     2083                // Test if one of the dependency is in footer, the dependant and other dependant dependencies are also placed in footer.
     2084                $register_and_enqueue( 'f', '/f.js', array(), '1.0.0' );
     2085                $register_and_enqueue( 'g', '/g.js', array( 'f' ), '1.0.0', array( 'in_footer' => true ) );
     2086                $register_and_enqueue( 'h', '/h.js', array( 'g' ), '1.0.0' );
     2087                $register_and_enqueue( 'i', '/i.js', array( 'h' ), '1.0.0' );
     2088
     2089                $actual = array(
     2090                        'preload_links' => $this->get_preloaded_script_modules(),
     2091                        'script_tags'   => $this->get_enqueued_script_modules(),
     2092                        'import_map'    => $this->get_import_map(),
     2093                );
     2094
     2095                $this->assertSame(
     2096                        array(
     2097                                'preload_links' => array(),
     2098                                'script_tags'   => array(
     2099                                        'f' => array(
     2100                                                'url'           => '/f.js?ver=1.0.0',
     2101                                                'fetchpriority' => 'auto',
     2102                                                'in_footer'     => false,
     2103                                        ),
     2104                                        'g' => array(
     2105                                                'url'           => '/g.js?ver=1.0.0',
     2106                                                'fetchpriority' => 'auto',
     2107                                                'in_footer'     => true,
     2108                                        ),
     2109                                        'h' => array(
     2110                                                'url'           => '/h.js?ver=1.0.0',
     2111                                                'fetchpriority' => 'auto',
     2112                                                'in_footer'     => true,
     2113                                        ),
     2114                                        'i' => array(
     2115                                                'url'           => '/i.js?ver=1.0.0',
     2116                                                'fetchpriority' => 'auto',
     2117                                                'in_footer'     => true,
     2118                                        ),
     2119                                ),
     2120                                'import_map'    => array(
     2121                                        'f' => '/f.js?ver=1.0.0',
     2122                                        'g' => '/g.js?ver=1.0.0',
     2123                                        'h' => '/h.js?ver=1.0.0',
     2124                                ),
     2125                        ),
     2126                        $actual,
     2127                        "Snapshot:\n" . var_export( $actual, true )
     2128                );
     2129
     2130                $deregister( array( 'f', 'g', 'h', 'i' ) );
     2131
     2132                // Test dependency ordering when all scripts modules are enqueued in head.
     2133                // Expected order: j, k, l, m.
     2134                $register_and_enqueue( 'm', '/m.js', array( 'j', 'l' ), '1.0.0' );
     2135                $register_and_enqueue( 'k', '/k.js', array( 'j' ), '1.0.0' );
     2136                $register_and_enqueue( 'l', '/l.js', array( 'k' ), '1.0.0' );
     2137                $register_and_enqueue( 'j', '/j.js', array(), '1.0.0' );
     2138
     2139                $actual = array(
     2140                        'preload_links' => $this->get_preloaded_script_modules(),
     2141                        'script_tags'   => $this->get_enqueued_script_modules(),
     2142                        'import_map'    => $this->get_import_map(),
     2143                );
     2144
     2145                $this->assertSame(
     2146                        array(
     2147                                'preload_links' => array(),
     2148                                'script_tags'   => array(
     2149                                        'j' => array(
     2150                                                'url'           => '/j.js?ver=1.0.0',
     2151                                                'fetchpriority' => 'auto',
     2152                                                'in_footer'     => false,
     2153                                        ),
     2154                                        'k' => array(
     2155                                                'url'           => '/k.js?ver=1.0.0',
     2156                                                'fetchpriority' => 'auto',
     2157                                                'in_footer'     => false,
     2158                                        ),
     2159                                        'l' => array(
     2160                                                'url'           => '/l.js?ver=1.0.0',
     2161                                                'fetchpriority' => 'auto',
     2162                                                'in_footer'     => false,
     2163                                        ),
     2164                                        'm' => array(
     2165                                                'url'           => '/m.js?ver=1.0.0',
     2166                                                'fetchpriority' => 'auto',
     2167                                                'in_footer'     => false,
     2168                                        ),
     2169                                ),
     2170                                'import_map'    => array(
     2171                                        'j' => '/j.js?ver=1.0.0',
     2172                                        'l' => '/l.js?ver=1.0.0',
     2173                                        'k' => '/k.js?ver=1.0.0',
     2174                                ),
     2175                        ),
     2176                        $actual,
     2177                        "Snapshot:\n" . var_export( $actual, true )
     2178                );
     2179
     2180                $deregister( array( 'j', 'k', 'l', 'm' ) );
     2181
     2182                // Test dependency ordering when scripts modules are enqueued in both head and footer.
     2183                // Expected order: q, n, o, p, r.
     2184                $register_and_enqueue( 'n', '/n.js', array( 'q' ), '1.0.0' );
     2185                $register_and_enqueue( 'q', '/q.js', array(), '1.0.0' );
     2186                $register_and_enqueue( 'o', '/o.js', array( 'n' ), '1.0.0', array( 'in_footer' => true ) );
     2187                $register_and_enqueue( 'r', '/r.js', array( 'q', 'o', 'p' ), '1.0.0' );
     2188                $register_and_enqueue( 'p', '/p.js', array(), '1.0.0', array( 'in_footer' => true ) );
     2189
     2190                $actual = array(
     2191                        'preload_links' => $this->get_preloaded_script_modules(),
     2192                        'script_tags'   => $this->get_enqueued_script_modules(),
     2193                        'import_map'    => $this->get_import_map(),
     2194                );
     2195
     2196                $this->assertSame(
     2197                        array(
     2198                                'preload_links' => array(),
     2199                                'script_tags'   => array(
     2200                                        'q' => array(
     2201                                                'url'           => '/q.js?ver=1.0.0',
     2202                                                'fetchpriority' => 'auto',
     2203                                                'in_footer'     => false,
     2204                                        ),
     2205                                        'n' => array(
     2206                                                'url'           => '/n.js?ver=1.0.0',
     2207                                                'fetchpriority' => 'auto',
     2208                                                'in_footer'     => false,
     2209                                        ),
     2210                                        'o' => array(
     2211                                                'url'           => '/o.js?ver=1.0.0',
     2212                                                'fetchpriority' => 'auto',
     2213                                                'in_footer'     => true,
     2214                                        ),
     2215                                        'p' => array(
     2216                                                'url'           => '/p.js?ver=1.0.0',
     2217                                                'fetchpriority' => 'auto',
     2218                                                'in_footer'     => true,
     2219                                        ),
     2220                                        'r' => array(
     2221                                                'url'           => '/r.js?ver=1.0.0',
     2222                                                'fetchpriority' => 'auto',
     2223                                                'in_footer'     => true,
     2224                                        ),
     2225                                ),
     2226                                'import_map'    => array(
     2227                                        'q' => '/q.js?ver=1.0.0',
     2228                                        'n' => '/n.js?ver=1.0.0',
     2229                                        'o' => '/o.js?ver=1.0.0',
     2230                                        'p' => '/p.js?ver=1.0.0',
     2231                                ),
     2232                        ),
     2233                        $actual,
     2234                        "Snapshot:\n" . var_export( $actual, true )
     2235                );
     2236        }
     2237
     2238        /**
     2239         * Tests various ways of printing and dependency ordering of script modules.
     2240         *
     2241         * @ticket 63486
     2242         *
     2243         * @dataProvider data_test_register_and_enqueue_script_module
     2244         *
     2245         * @covers ::wp_register_script_module()
     2246         * @covers WP_Script_Modules::register()
     2247         * @covers ::wp_enqueue_script_module()
     2248         * @covers WP_Script_Modules::enqueue()
     2249         * @covers ::wp_dequeue_script_module()
     2250         * @covers WP_Script_Modules::dequeue()
     2251         * @covers ::wp_deregister_script_module()
     2252         * @covers WP_Script_Modules::deregister()
     2253         * @covers WP_Script_Modules::set_fetchpriority()
     2254         * @covers WP_Script_Modules::print_head_enqueued_script_modules()
     2255         * @covers WP_Script_Modules::print_enqueued_script_modules()
     2256         * @covers WP_Script_Modules::print_import_map()
     2257         * @covers WP_Script_Modules::print_script_module_preloads()
     2258         */
     2259        public function test_static_import_dependency_with_dynamic_imports_depending_on_static_import_dependency() {
     2260                $get_dependency = function ( string $id, string $import ): array {
     2261                        return compact( 'id', 'import' );
     2262                };
     2263
     2264                wp_register_script_module( 'enqueued', '/enqueued.js', array( $get_dependency( 'static1', 'static' ) ), null );
     2265                wp_register_script_module( 'static1', '/static1.js', array( $get_dependency( 'dynamic1', 'dynamic' ) ), null );
     2266                wp_register_script_module( 'dynamic1', '/dynamic1.js', array( $get_dependency( 'static2', 'static' ) ), null );
     2267                wp_register_script_module( 'static2', '/static2.js', array(), null );
     2268
     2269                wp_enqueue_script_module( 'enqueued' );
     2270                $import_map     = $this->get_import_map();
     2271                $preload_links  = get_echo( array( wp_script_modules(), 'print_script_module_preloads' ) );
     2272                $script_modules = get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) );
     2273
     2274                $this->assertEquals(
     2275                        array(
     2276                                'static1'  => '/static1.js',
     2277                                'dynamic1' => '/dynamic1.js',
     2278                                'static2'  => '/static2.js',
     2279                        ),
     2280                        $import_map,
     2281                        "Expected import map to match snapshot:\n" . var_export( $import_map, true )
     2282                );
     2283                $this->assertEqualHTML(
     2284                        '
     2285                                <link rel="modulepreload" href="/static1.js" id="static1-js-modulepreload">
     2286                        ',
     2287                        $preload_links,
     2288                        '<body>',
     2289                        "Expected preload links to match snapshot:\n$preload_links"
     2290                );
     2291                $this->assertEqualHTML(
     2292                        '
     2293                                <script type="module" src="/enqueued.js" id="enqueued-js-module"></script>
     2294                        ',
     2295                        $script_modules,
     2296                        '<body>',
     2297                        "Expected script modules to match snapshot:\n$script_modules"
     2298                );
     2299        }
    17332300}
Note: See TracChangeset for help on using the changeset viewer.