<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[57345] trunk: Script Loader: Load the modules to the footer in classic themes</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/57345">57345</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/57345","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>youknowriad</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-01-24 10:37:54 +0000 (Wed, 24 Jan 2024)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Script Loader: Load the modules to the footer in classic themes

Incremental import maps fail if the import map is printed after the module scripts.
This means, we should always render import maps first. This means that for classic themes, we need to move the import map and modules to the footer because we can't know before that which modules are needed.

Props luisherranz, cbravobernal.
Fixes <a href="https://core.trac.wordpress.org/ticket/60240">#60240</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpscriptmodulesphp">trunk/src/wp-includes/class-wp-script-modules.php</a></li>
<li><a href="#trunktestsphpunittestsscriptmoduleswpScriptModulesphp">trunk/tests/phpunit/tests/script-modules/wpScriptModules.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpscriptmodulesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-script-modules.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-script-modules.php 2024-01-24 07:55:53 UTC (rev 57344)
+++ trunk/src/wp-includes/class-wp-script-modules.php   2024-01-24 10:37:54 UTC (rev 57345)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -89,15 +89,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'version'      => $version,
</span><span class="cx" style="display: block; padding: 0 10px">                                'enqueue'      => isset( $this->enqueued_before_registered[ $id ] ),
</span><span class="cx" style="display: block; padding: 0 10px">                                'dependencies' => $dependencies,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'enqueued'     => false,
-                               'preloaded'    => false,
</del><span class="cx" style="display: block; padding: 0 10px">                         );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Marks the script module to be enqueued in the page the next time
-        * `print_enqueued_script_modules` is called.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Marks the script module to be enqueued in the page.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * If a src is provided and the script module has not been registered yet, it
</span><span class="cx" style="display: block; padding: 0 10px">         * will be registered.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -158,29 +155,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Adds the hooks to print the import map, enqueued script modules and script
</span><span class="cx" style="display: block; padding: 0 10px">         * module preloads.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * It adds the actions to print the enqueued script modules and script module
-        * preloads to both `wp_head` and `wp_footer` because in classic themes, the
-        * script modules used by the theme and plugins will likely be able to be
-        * printed in the `head`, but the ones used by the blocks will need to be
-        * enqueued in the `footer`.
-        *
-        * As all script modules are deferred and dependencies are handled by the
-        * browser, the order of the script modules is not important, but it's still
-        * better to print the ones that are available when the `wp_head` is rendered,
-        * so the browser starts downloading those as soon as possible.
-        *
-        * The import map is also printed in the footer to be able to include the
-        * dependencies of all the script modules, including the ones printed in the
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * In classic themes, the script modules used by the blocks are not yet known
+        * when the `wp_head` actions is fired, so it needs to print everything in the
</ins><span class="cx" style="display: block; padding: 0 10px">          * footer.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 6.5.0
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function add_hooks() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                add_action( 'wp_head', array( $this, 'print_enqueued_script_modules' ) );
-               add_action( 'wp_head', array( $this, 'print_script_module_preloads' ) );
-               add_action( 'wp_footer', array( $this, 'print_enqueued_script_modules' ) );
-               add_action( 'wp_footer', array( $this, 'print_script_module_preloads' ) );
-               add_action( 'wp_footer', array( $this, 'print_import_map' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $position = wp_is_block_theme() ? 'wp_head' : 'wp_footer';
+               add_action( $position, array( $this, 'print_import_map' ) );
+               add_action( $position, array( $this, 'print_enqueued_script_modules' ) );
+               add_action( $position, array( $this, 'print_script_module_preloads' ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -187,25 +172,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Prints the enqueued script modules using script tags with type="module"
</span><span class="cx" style="display: block; padding: 0 10px">         * attributes.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * If a enqueued script module has already been printed, it will not be
-        * printed again on subsequent calls to this function.
-        *
</del><span class="cx" style="display: block; padding: 0 10px">          * @since 6.5.0
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function print_enqueued_script_modules() {
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $this->get_marked_for_enqueue() as $id => $script_module ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( false === $script_module['enqueued'] ) {
-                               // Mark it as enqueued so it doesn't get enqueued again.
-                               $this->registered[ $id ]['enqueued'] = true;
-
-                               wp_print_script_tag(
-                                       array(
-                                               'type' => 'module',
-                                               'src'  => $this->get_versioned_src( $script_module ),
-                                               'id'   => $id . '-js-module',
-                                       )
-                               );
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 wp_print_script_tag(
+                               array(
+                                       'type' => 'module',
+                                       'src'  => $this->get_versioned_src( $script_module ),
+                                       'id'   => $id . '-js-module',
+                               )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -213,19 +190,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Prints the the static dependencies of the enqueued script modules using
</span><span class="cx" style="display: block; padding: 0 10px">         * link tags with rel="modulepreload" attributes.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * If a script module is marked for enqueue, it will not be preloaded. If a
-        * preloaded script module has already been printed, it will not be printed
-        * again on subsequent calls to this function.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * If a script module is marked for enqueue, it will not be preloaded.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 6.5.0
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function print_script_module_preloads() {
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ), array( 'static' ) ) as $id => $script_module ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Don't preload if it's marked for enqueue or has already been preloaded.
-                       if ( true !== $script_module['enqueue'] && false === $script_module['preloaded'] ) {
-                               // Mark it as preloaded so it doesn't get preloaded again.
-                               $this->registered[ $id ]['preloaded'] = true;
-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Don't preload if it's marked for enqueue.
+                       if ( true !== $script_module['enqueue'] ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 echo sprintf(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '<link rel="modulepreload" href="%s" id="%s">',
</span><span class="cx" style="display: block; padding: 0 10px">                                        esc_url( $this->get_versioned_src( $script_module ) ),
</span></span></pre></div>
<a id="trunktestsphpunittestsscriptmoduleswpScriptModulesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/script-modules/wpScriptModules.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/script-modules/wpScriptModules.php      2024-01-24 07:55:53 UTC (rev 57344)
+++ trunk/tests/phpunit/tests/script-modules/wpScriptModules.php        2024-01-24 10:37:54 UTC (rev 57345)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -517,68 +517,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Tests that it can print the enqueued script modules multiple times, and it
-        * will only print the script modules that have not been printed before.
-        *
-        * @ticket 56313
-        *
-        * @covers ::register()
-        * @covers ::enqueue()
-        * @covers ::print_enqueued_script_modules()
-        */
-       public function test_print_enqueued_script_modules_can_be_called_multiple_times() {
-               $this->script_modules->register( 'foo', '/foo.js' );
-               $this->script_modules->register( 'bar', '/bar.js' );
-               $this->script_modules->enqueue( 'foo' );
-
-               $enqueued_script_modules = $this->get_enqueued_script_modules();
-               $this->assertCount( 1, $enqueued_script_modules );
-               $this->assertTrue( isset( $enqueued_script_modules['foo'] ) );
-
-               $this->script_modules->enqueue( 'bar' );
-
-               $enqueued_script_modules = $this->get_enqueued_script_modules();
-               $this->assertCount( 1, $enqueued_script_modules );
-               $this->assertTrue( isset( $enqueued_script_modules['bar'] ) );
-
-               $enqueued_script_modules = $this->get_enqueued_script_modules();
-               $this->assertCount( 0, $enqueued_script_modules );
-       }
-
-       /**
-        * Tests that it can print the preloaded script modules multiple times, and it
-        * will only print the script modules that have not been printed before.
-        *
-        * @ticket 56313
-        *
-        * @covers ::register()
-        * @covers ::enqueue()
-        * @covers ::print_script_module_preloads()
-        */
-       public function test_print_preloaded_script_modules_can_be_called_multiple_times() {
-               $this->script_modules->register( 'foo', '/foo.js', array( 'static-dep-1', 'static-dep-2' ) );
-               $this->script_modules->register( 'bar', '/bar.js', array( 'static-dep-3' ) );
-               $this->script_modules->register( 'static-dep-1', '/static-dep-1.js' );
-               $this->script_modules->register( 'static-dep-3', '/static-dep-3.js' );
-               $this->script_modules->enqueue( 'foo' );
-
-               $preloaded_script_modules = $this->get_preloaded_script_modules();
-               $this->assertCount( 1, $preloaded_script_modules );
-               $this->assertTrue( isset( $preloaded_script_modules['static-dep-1'] ) );
-
-               $this->script_modules->register( 'static-dep-2', '/static-dep-2.js' );
-               $this->script_modules->enqueue( 'bar' );
-
-               $preloaded_script_modules = $this->get_preloaded_script_modules();
-               $this->assertCount( 2, $preloaded_script_modules );
-               $this->assertTrue( isset( $preloaded_script_modules['static-dep-2'] ) );
-               $this->assertTrue( isset( $preloaded_script_modules['static-dep-3'] ) );
-
-               $preloaded_script_modules = $this->get_preloaded_script_modules();
-               $this->assertCount( 0, $preloaded_script_modules );
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Tests that a script module is not registered when calling enqueue without a
</span><span class="cx" style="display: block; padding: 0 10px">         * valid src.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span></span></pre>
</div>
</div>

</body>
</html>