Skip to content

Conversation

@sirreal
Copy link
Member

@sirreal sirreal commented Dec 19, 2024

This change allows classic scripts to declare dependencies on Script Modules.

Classic scripts can declare module dependencies by passing a module_dependencies key in the $args array of wp_register_script() or wp_enqueue_script().

In order for a classic script to have access to a script module, the script module must appear in the import map so that it can be accessed by its module identifier from the classic script in a dynamic import (e.g. import( '@example/module' )).

For a classic script to depend on a module, it must declare that dependency via the new module_dependencies argument. When the script modules import map is printed, it inspects all enqueued classic scripts (and their dependencies recursively) to search for these declared module dependencies and ensures those modules are included in the import map.

The implementation introduces a new _wp_scripts_add_args_data() helper function to consolidate argument validation and processing for wp_register_script() and wp_enqueue_script(), reducing code duplication. This function validates that the $args array only contains recognized keys (strategy, in_footer, fetchpriority, module_dependencies) and triggers a _doing_it_wrong() notice for any unrecognized keys. It also validates that module_dependencies is an array of strings.

When WP_Script_Modules::get_import_map() is called, it now efficiently traverses the dependency tree of all enqueued classic scripts to find any associated module dependencies. These modules are then added to the import map, making them available for dynamic imports within the classic scripts.

The usage looks like this:

wp_register_script_module( '@example/module', /*…*/ );

wp_enqueue_script(
	'example-classic',
	'/classic.js',
	array( 'classic-script-dependency' ), // Classic script dependencies.
	null,
	array(
		// Script module dependencies are declared here.
		'module_dependencies' => array( '@example/module' ),
	)
);
Old Description

This change proposes allowing classic scripts to add script module dependencies.

Classic scripts can add module dependencies like array( 'type' => 'module', 'id' => 'example-module-id' ), where classic script dependencies are simple strings, e.g. 'wp-i18n'.

In order for a classic script to have access to a script module, the script module must appear in the importmap so that it can be accessed by its module identifier from the classic script in a dynamic import.

For a classic script to depend on a module, it must declare that dependency and must be enqueued before the importmap is printed. When the script modules import map is printed, it inspects enqueued scripts to search for module dependencies and includes those dependencies in the import map. Import maps must be printed before any modulepreloads or any script module tags, and only one can be printed (although the specification has changed to allow multiple import maps).

The implementation overrides the WP_Dependencies::add method in WP_Scripts. add is is the main method that handles script registration. The subclassed implementation is used to partition the provided dependencies array into classic script dependencies and script module dependencies. The classic script dependencies are passed on to WP_Dependencies::add along with the rest of the arguments. The module dependencies are attached to the script as extra data module_deps.

When the script modules importmap is printed, it scans through the registered classic scripts for enqueued scripts with module_deps and adds those modules to the import map.

Script modules in the import map will be available for classic scripts to use via dynamic import().

The dependencies look like this:

wp_register_script_module( '@example/module', /*…*/ );
wp_enqueue_script(
	'example-classic',
	'/classic.js',

	// Dependencies array:
	array(
		// Classic script dependency, no changes.
		'classic-script-dependency',

		// Script module dependency, this form is new.
		array(
			'type' => 'module',
			'id' => '@example/module',
		),
	),
);

Builds on #8009. The implementation here is now independent.

Trac ticket: https://core.trac.wordpress.org/ticket/61500


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@sirreal sirreal changed the title Add module dependency to classic scripts Allow classic scripts to depend on modules Dec 19, 2024
@sirreal sirreal marked this pull request as ready for review December 19, 2024 17:24
@github-actions
Copy link

github-actions bot commented Dec 19, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props jonsurrell, westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@sirreal
Copy link
Member Author

sirreal commented Dec 19, 2024

@luisherranz @youknowriad @swissspidy @gziolo I'd love to have your review on this approach to allowing classic scripts to depend on script modules.

This allows for script modules to be exposed in the import map
regardless of being a dependency or enqueued.

This will enable scripts to depend on script modules by requesting they
be exposed.
The + operator merges arrays with key overwriting.
array_merge renumbers numeric arrays, the desired behavior in this case.
This works, but it only works as long as scripts are printed before the importmap is printed.
That is undesireable sometimes. An alternate approach may be to invert this scheme and have
script modules inspect enqueued and printed scripts before printing the importmap.
Comment on lines +87 to +96
_doing_it_wrong(
$function_name,
sprintf(
/* translators: 1: $args, 2: List of unrecognized keys. */
__( 'Unrecognized keys in the %1$s array: %2$s.' ),
'$args',
implode( wp_get_list_item_separator(), $unknown_keys )
),
'7.0.0'
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importantly, this will catch cases where someone “mispells” the arg module_dependencies, like moduel_dependencys. I went with the full “dependencies” in the name as opposed to module_deps because there is also WP_Dependencies. See also Naming Conventions to not abbreviate names unnecessarily.

@westonruter
Copy link
Member

Gemini review:

Review of changes in scripts/allow-script-module-dependency

The changes introduce logic to include script module dependencies defined in classic scripts (via the module_dependencies key) into the import map. It also adds validation for the $args parameter in script registration functions and refactors them to reduce duplication.

General Observations

  • Standards & Compatibility: The code generally adheres to WordPress Coding Standards. The usage of typed properties in tests (PHP 7.4+) is acceptable for the current supported versions in the test suite.
  • Refactoring: The extraction of _wp_scripts_add_args_data() significantly cleans up wp_register_script() and wp_enqueue_script(), reducing duplication and centralizing validation.
  • Logic: The dependency traversal in WP_Script_Modules::get_import_map() uses an iterative approach with a stack (array_pop), which is efficient and avoids recursion depth issues. The use of $processed ensures circular dependencies don't cause infinite loops.

Conclusion

The changes are high quality, verified by tests, and improve the codebase's maintainability while adding the requested feature. Addressing the typo in the comment is the only immediate action recommended.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables classic (non-module) scripts to declare dependencies on registered script modules so that those modules (and their transitive dependencies) are added to the script modules import map, making them available to classic scripts via dynamic import().

Changes:

  • Adds support for declaring classic-script module dependencies via $args['module_dependencies'] in wp_register_script() / wp_enqueue_script().
  • Extends WP_Script_Modules import map generation to include module IDs referenced by enqueued classic scripts.
  • Adds validation and new PHPUnit coverage for module_dependencies and for unrecognized $args keys.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/wp-includes/functions.wp-scripts.php Introduces _wp_scripts_add_args_data() and documents/supports module_dependencies in script $args.
src/wp-includes/class-wp-scripts.php Adds module_dependencies validation/sanitization in WP_Scripts::add_data().
src/wp-includes/class-wp-script-modules.php Updates import map generation to discover module dependencies from enqueued classic scripts.
tests/phpunit/tests/script-modules/wpScriptModules.php Adds tests ensuring classic-script module deps are included in the import map and missing deps trigger a notice.
tests/phpunit/tests/dependencies/scripts.php Adds tests for unrecognized $args keys and module_dependencies validation behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@westonruter
Copy link
Member

Updated Gemini review (with nitpicks not needing actioning):

Review of changes in scripts/allow-script-module-dependency

The changes introduce functionality to allow classic scripts to declare dependencies on script modules, including validation, processing, and handling within the import map generation. The implementation also refactors script registration logic to reduce duplication.

Observations

  1. Refactoring (functions.wp-scripts.php):

    • The introduction of _wp_scripts_add_args_data() is a solid improvement, centralizing validation and data addition.
    • Validation logic correctly checks for unknown keys and ensures module_dependencies is well-formed.
    • The placeholder usage for $args in translation strings is correct.
  2. Script Module Integration (WP_Script_Modules::get_import_map):

    • The logic to traverse classic script dependencies ($wp_scripts->registered[$handle]->deps) to find associated module dependencies is implemented iteratively with a stack (array_pop). This avoids recursion limits and is efficient.
    • The _doing_it_wrong call correctly identifies missing module dependencies.
    • Usage of WP_Scripts::class . '::add' (or specifically WP_Scripts::add_data in the diff) as the function name in _doing_it_wrong is acceptable, though slightly unusual to see class constants used this way in legacy-style warnings.
  3. Tests (Tests_Script_Modules_WpScriptModules):

    • The tests are comprehensive, covering direct module dependencies, transitive dependencies, and error conditions.
    • The typo fix "dependnecy" -> "dependency" in test_wp_scripts_doing_it_wrong_for_missing_script_module_dependencies is correct.
    • Assertions are specific and verify the expected behavior (import map contents, error notices).

Nitpicks

  1. _doing_it_wrong context in WP_Script_Modules::get_import_map:
    In the diff:

    _doing_it_wrong(
        'WP_Scripts::add_data', // <--- This string
        // ...
    );

    While WP_Scripts::add_data is technically where the data was added, the error is being detected during import map generation (enqueue time). A user might find it confusing to see WP_Scripts::add_data if they didn't explicitly call it (e.g. they called wp_enqueue_script). However, since wp_enqueue_script calls add_data internally via the new helper, this is technically accurate enough.

  2. _wp_scripts_add_args_data Documentation:
    The docblock says:
    * Adds the data for the recognized args and warns for unrecognized args.
    This follows the third-person singular verb standard requested.

Conclusion

The code is robust, follows WordPress coding standards, and the test coverage is thorough. The refactoring improves maintainability.

Plan:
No changes are required. The current state is ready.

Copy link
Member

@westonruter westonruter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sirreal I'm excited about getting this capability into core! If you approve the changes, let's get this committed. You can do the honors if you'd like!

westonruter added a commit to westonruter/wordpress-develop that referenced this pull request Jan 31, 2026
… at 9acd7f0

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
westonruter added a commit to westonruter/wordpress-develop that referenced this pull request Jan 31, 2026
… at 9acd7f0

Co-authored-by: Jon Surrell <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@westonruter
Copy link
Member

I squash-merged this PR into #10806 to kick the tires. I added 34f5e4c to make use of it to add espree as a dependency of wp-codemirror. It works!

@westonruter westonruter requested a review from Copilot February 2, 2026 22:17
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2010 to +2012
array(
'id' => 'example2',
),
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case includes an array format array('id' => 'example2') for module dependencies, but there is no test coverage for validating that this array format is handled correctly. Consider adding a test case that specifically validates the array format with an 'id' key is properly processed.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not factual. There is a test case for it being handled properly, but it is in scripts.php. The bottom of this same test method in fact shows that the format is handled properly, as example2 appears in the array returned by get_import_map.

Copy link
Member Author

@sirreal sirreal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one final thing I'd like to consider, otherwise this seems ready to me. Thanks again for all your work on it @westonruter 🙂

…gs_data().

Automatically determine the calling function name using debug_backtrace() for context in _doing_it_wrong() calls.

Co-authored-by: Jon Surrell <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Copy link
Member Author

@sirreal sirreal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ This is ready!

@westonruter feel free to merge if you're ready. Otherwise, I'll get it landed this week. I don't mind either way 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants