Make WordPress Core

Opened 4 weeks ago

Last modified 7 days ago

#62435 new defect (bug)

Preload script modules and enqueue script modules in wrong order

Reported by: yogeshbhutkar's profile yogeshbhutkar Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 6.5
Component: Script Loader Keywords: has-patch reporter-feedback close
Focuses: Cc:

Description

Issue:

In WP_Script_Modules, there is this order of output:

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' ) );

This is adding a preload after printing script modules. This is not always a problem, but can cause a race condition if assets are stored in the browser or served quickly. Dependencies may not load before dependent scripts.

Steps to reproduce the issue:

  1. Add a script module with dependencies marked as "static"
<?php
// instead, use a static loader that injects the script at runtime.
$static_assets = include trailingslashit( plugin_dir_path( __FILE__ ) ) . 'build/scripts/test/index.asset.php';
wp_register_script_module(
	'@my-plugin/test',
	trailingslashit( plugin_dir_url( __FILE__ ) ) . 'build/scripts/test/index.js',
	array(
		array(
			'id'     => '@wordpress/interactivity',
			'import' => 'static',
		),
	),
	$static_assets['version']
);
  1. View the markup on the page source:
<script type="module" src="https://mysite.com/wp-content/plugins/my-plugin/build/scripts/test/index.js?ver=0d40f7f88c7660867299" id="@my-plugin/test-js-module"></script>
<link rel="modulepreload" href="https://mysite.com/wp-includes/js/dist/script-modules/interactivity/debug.js?ver=d1c1c7faff86314c361a" id="@wordpress/interactivity-js-modulepreload">

The expected output should be the opposite, since the module requires @wordpress/interactivity.

<link rel="modulepreload" href="https://mysite.com/wp-includes/js/dist/script-modules/interactivity/debug.js?ver=d1c1c7faff86314c361a" id="@wordpress/interactivity-js-modulepreload">
<script type="module" src="https://mysite.com/wp-content/plugins/my-plugin/build/scripts/test/index.js?ver=0d40f7f88c7660867299" id="@my-plugin/test-js-module"></script>

@ajgagnon reported this issue earlier in the Gutenberg repo. We'd like to give him credit for sharing it with us.

Original Issue Link: https://github.com/WordPress/gutenberg/issues/66982

Thank You @ajgagnon.

Change History (8)

#1 @yogeshbhutkar
4 weeks ago

As per the issue, I was able to replicate the bug. The modulepreload is enlisted after loading module scripts. Whereas, I believe, the preload should happen before. Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/modulepreload

This ticket was mentioned in PR #7808 on WordPress/wordpress-develop by @yogeshbhutkar.


3 weeks ago
#2

  • Keywords has-patch added

…printed after printing preload scripts

This PR fixes the bug where the preload scripts are included in the head after the module imports which ideally should be reversed.

Trac ticket: https://core.trac.wordpress.org/ticket/62435#comment:1

This ticket was mentioned in Slack in #core by desrosj. View the logs.


3 weeks ago

#4 @desrosj
3 weeks ago

  • Version changed from trunk to 6.5

#5 @jonsurrell
3 weeks ago

Hello, and thanks for the report!

First, I'd like to clarify. Is there a bug that's been observed or is this describing a hypothetical race condition that could lead to a bug?

I don't believe there's an issue with the ordering, but if there's a reproducible bug I'd like to investigate further. Modules work very differently from "classic" scripts.

The modulepreload that are mentioned are a included for performance reasons, they are not strictly necessary. If they are removed the JavaScript modules should continue to work without issue. Module preloads do not cause JavaScript to be executed, they fetch the referenced modules and prepare them for use.

If the preloads are removed, the modules will still fetch their dependencies, but it may happen later because the browser will need to discover, fetch, and process the dependency modules inside the JavaScript modules themselves.

I believe ordering as they are (enqueued modules first, preloads second) are because the enqueued modules are higher priority. They're the main modules that are expected to be executed when the page is loaded. The preloads hint at the dependencies that will be statically required so that the browser can also fetch them and get them ready (before discovering them in JavaScript source).

Please let me know if I misunderstood this ticket or there are more details!

#6 @desrosj
3 weeks ago

  • Keywords reporter-feedback added

#7 @yogeshbhutkar
3 weeks ago

Thank you, @jonsurrell, for your detailed explanation.

The final points you mentioned were particularly helpful in addressing the query. Since this issue was originally raised by another contributor on the Gutenberg GitHub repository, I will incorporate my understanding of the problem from their perspective while presenting my response.

As you pointed out, the primary rationale for prioritizing module imports was their higher importance in the overall execution flow. This clarification significantly resolves the query.

The confusion likely stemmed from the general convention of importing preloads before modules, which is a widely recognized standard practice.

I'll be tagging the original author on GitHub and request him to provide his views for further clarity in case I missed something.

Thanks.

#8 @jonsurrell
7 days ago

  • Keywords close added
Note: See TracTickets for help on using tickets.