Index: /trunk/src/js/_enqueues/lib/codemirror/javascript-lint.js
===================================================================
--- /trunk/src/js/_enqueues/lib/codemirror/javascript-lint.js	(revision 62367)
+++ /trunk/src/js/_enqueues/lib/codemirror/javascript-lint.js	(revision 62368)
@@ -31,4 +31,5 @@
  * @property {boolean} [module] - "This option informs JSHint that the input code describes an ECMAScript 6 module. All module code is interpreted as strict mode code."
  * @property {'implied'} [strict] - "This option requires the code to run in ECMAScript 5's strict mode."
+ * @property {string} [espreeModuleUrl] - The URL to the espree script module.
  */
 
@@ -43,7 +44,11 @@
  */
 async function validator( text, options ) {
+	if ( ! options.espreeModuleUrl ) {
+		return [];
+	}
+
 	const errors = /** @type {CodeMirrorLintError[]} */ [];
 	try {
-		const espree = await import( /* webpackIgnore: true */ 'espree' );
+		const espree = await import( /* webpackIgnore: true */ options.espreeModuleUrl );
 		espree.parse( text, {
 			...getEspreeOptions( options ),
Index: /trunk/src/js/_enqueues/wp/code-editor.js
===================================================================
--- /trunk/src/js/_enqueues/wp/code-editor.js	(revision 62367)
+++ /trunk/src/js/_enqueues/wp/code-editor.js	(revision 62368)
@@ -2,4 +2,6 @@
  * @output wp-admin/js/code-editor.js
  */
+
+/* global console */
 
 /* eslint-env es2020 */
@@ -413,4 +415,8 @@
 	 */
 	wp.codeEditor.initialize = function initialize( textarea, settings ) {
+		if ( document.readyState === 'loading' ) {
+			console.warn( 'wp.codeEditor.initialize() ran too early. Invoke this function in a `DOMContentLoaded` event listener.' );
+		}
+
 		let $textarea;
 		if ( 'string' === typeof textarea ) {
Index: /trunk/src/wp-includes/class-wp-scripts.php
===================================================================
--- /trunk/src/wp-includes/class-wp-scripts.php	(revision 62367)
+++ /trunk/src/wp-includes/class-wp-scripts.php	(revision 62368)
@@ -886,5 +886,5 @@
 						/* translators: 1: $strategy, 2: $handle */
 						__( 'Invalid strategy `%1$s` defined for `%2$s` during script registration.' ),
-						$value,
+						is_string( $value ) ? $value : gettype( $value ),
 						$handle
 					),
@@ -898,5 +898,5 @@
 						/* translators: 1: $strategy, 2: $handle */
 						__( 'Cannot supply a strategy `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
-						$value,
+						is_string( $value ) ? $value : gettype( $value ),
 						$handle
 					),
Index: /trunk/src/wp-includes/functions.wp-scripts.php
===================================================================
--- /trunk/src/wp-includes/functions.wp-scripts.php	(revision 62367)
+++ /trunk/src/wp-includes/functions.wp-scripts.php	(revision 62368)
@@ -72,4 +72,7 @@
  * Adds the data for the recognized args and warns for unrecognized args.
  *
+ * @see wp_enqueue_script()
+ * @see wp_register_script()
+ *
  * @ignore
  * @since 7.0.0
@@ -78,11 +81,19 @@
  * @param string     $handle     Script handle.
  * @param array      $args       Array of extra args for the script.
- */
-function _wp_scripts_add_args_data( WP_Scripts $wp_scripts, string $handle, array $args ) {
+ *
+ * @phpstan-param non-empty-string $handle
+ * @phpstan-param array{
+ *     in_footer?: bool,
+ *     strategy?: 'async'|'defer',
+ *     fetchpriority?: 'low'|'auto'|'high',
+ *     module_dependencies?: array<non-empty-string|array{ id: non-empty-string, ... }>,
+ * } $args
+ */
+function _wp_scripts_add_args_data( WP_Scripts $wp_scripts, string $handle, array $args ): void {
 	$allowed_keys = array( 'strategy', 'in_footer', 'fetchpriority', 'module_dependencies' );
 	$unknown_keys = array_diff( array_keys( $args ), $allowed_keys );
 	if ( ! empty( $unknown_keys ) ) {
 		$trace         = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 );
-		$function_name = ( $trace[1]['class'] ?? '' ) . ( $trace[1]['type'] ?? '' ) . $trace[1]['function'];
+		$function_name = ( $trace[1]['class'] ?? '' ) . ( $trace[1]['type'] ?? '' ) . ( $trace[1]['function'] ?? __FUNCTION__ );
 		_doing_it_wrong(
 			$function_name,
@@ -98,5 +109,6 @@
 	}
 
-	if ( ! empty( $args['in_footer'] ) ) {
+	$in_footer = ! empty( $args['in_footer'] );
+	if ( $in_footer ) {
 		$wp_scripts->add_data( $handle, 'group', 1 );
 	}
@@ -109,4 +121,29 @@
 	if ( ! empty( $args['module_dependencies'] ) ) {
 		$wp_scripts->add_data( $handle, 'module_dependencies', $args['module_dependencies'] );
+
+		/*
+		 * A classic script with module dependencies must either be printed in the
+		 * footer or use the 'defer' loading strategy. Otherwise, the script may be
+		 * evaluated before the script modules import map is printed, causing
+		 * dynamic imports to fail with a "Failed to resolve module specifier" error.
+		 */
+		$is_deferred = 'defer' === ( $args['strategy'] ?? null );
+		if ( ! $in_footer && ! $is_deferred ) {
+			$trace         = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 );
+			$function_name = ( $trace[1]['class'] ?? '' ) . ( $trace[1]['type'] ?? '' ) . ( $trace[1]['function'] ?? __FUNCTION__ );
+			_doing_it_wrong(
+				$function_name,
+				sprintf(
+					/* translators: 1: 'module_dependencies', 2: Script handle, 3: 'in_footer', 4: 'strategy', 5: 'defer'. */
+					__( 'When the %1$s arg is provided, the "%2$s" script must either be printed in the footer (%3$s set to true) or use a deferred loading %4$s (%5$s) so that the import map is printed before the script is evaluated.' ),
+					'<code>module_dependencies</code>',
+					$handle,
+					'<code>in_footer</code>',
+					'<code>strategy</code>',
+					'<code>defer</code>'
+				),
+				'7.0.0'
+			);
+		}
 	}
 }
@@ -205,23 +242,37 @@
  * @since 7.0.0 The $module_dependencies parameter of type string[] was added to the $args parameter of type array.
  *
- * @param string                                                              $handle Name of the script. Should be unique.
- * @param string|false                                                        $src    Full URL of the script, or path of the script relative to the WordPress root directory.
- *                                                                                    If source is set to false, script is an alias of other scripts it depends on.
- * @param string[]                                                            $deps   Optional. An array of registered script handles this script depends on. Default empty array.
- * @param string|bool|null                                                    $ver    Optional. String specifying script version number, if it has one, which is added to the URL
- *                                                                                    as a query string for cache busting purposes. If version is set to false, a version
- *                                                                                    number is automatically added equal to current installed WordPress version.
- *                                                                                    If set to null, no version is added.
- * @param array<string, string|bool|array<string|array<string, string>>>|bool $args   {
+ * @param string           $handle Name of the script. Should be unique.
+ * @param string|false     $src    Full URL of the script, or path of the script relative to the WordPress root directory.
+ *                                 If source is set to false, script is an alias of other scripts it depends on.
+ * @param string[]         $deps   Optional. An array of registered script handles this script depends on. Default empty array.
+ * @param string|bool|null $ver    Optional. String specifying script version number, if it has one, which is added to the URL
+ *                                 as a query string for cache busting purposes. If version is set to false, a version
+ *                                 number is automatically added equal to current installed WordPress version.
+ *                                 If set to null, no version is added.
+ * @param array|bool       $args   {
  *     Optional. An array of extra args for the script. Default empty array.
  *     Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
  *
- *     @type string                              $strategy            Optional. If provided, may be either 'defer' or 'async'.
- *     @type bool                                $in_footer           Optional. Whether to print the script in the footer. Default 'false'.
- *     @type string                              $fetchpriority       Optional. The fetch priority for the script. Default 'auto'.
- *     @type array<string|array<string, string>> $module_dependencies Optional. IDs for module dependencies loaded via dynamic import. Default empty array.
+ *     @type string $strategy            Optional. If provided, may be either 'defer' or 'async'.
+ *     @type bool   $in_footer           Optional. Whether to print the script in the footer. Default 'false'.
+ *     @type string $fetchpriority       Optional. The fetch priority for the script. Default 'auto'.
+ *     @type array  $module_dependencies Optional. IDs for module dependencies loaded via dynamic import. Default empty array.
  *                                                                    For the full data format, see the `$deps` param of {@see wp_register_script_module()}.
+ *                                                                    When provided, the script must either be printed in the footer (with
+ *                                                                    `in_footer` set to true) or use a deferred loading `strategy` (`defer`),
+ *                                                                    so that the script modules import map is printed before the script
+ *                                                                    is evaluated. Otherwise dynamic imports may fail to resolve.
  * }
  * @return bool Whether the script has been registered. True on success, false on failure.
+ *
+ * @phpstan-param non-empty-string $handle
+ * @phpstan-param non-empty-string|false $src
+ * @phpstan-param non-empty-string[] $deps
+ * @phpstan-param array{
+ *     in_footer?: bool,
+ *     strategy?: 'async'|'defer',
+ *     fetchpriority?: 'low'|'auto'|'high',
+ *     module_dependencies?: array<non-empty-string|array{ id: non-empty-string, ... }>,
+ * }|bool $args
  */
 function wp_register_script( $handle, $src, $deps = array(), $ver = false, $args = array() ) {
@@ -387,22 +438,36 @@
  * @since 7.0.0 The $module_dependencies parameter of type string[] was added to the $args parameter of type array.
  *
- * @param string                                                              $handle Name of the script. Should be unique.
- * @param string                                                              $src    Full URL of the script, or path of the script relative to the WordPress root directory.
- *                                                                                    Default empty.
- * @param string[]                                                            $deps   Optional. An array of registered script handles this script depends on. Default empty array.
- * @param string|bool|null                                                    $ver    Optional. String specifying script version number, if it has one, which is added to the URL
- *                                                                                    as a query string for cache busting purposes. If version is set to false, a version
- *                                                                                    number is automatically added equal to current installed WordPress version.
- *                                                                                    If set to null, no version is added.
- * @param array<string, string|bool|array<string|array<string, string>>>|bool $args {
+ * @param string           $handle Name of the script. Should be unique.
+ * @param string           $src    Full URL of the script, or path of the script relative to the WordPress root directory.
+ *                                 Default empty.
+ * @param string[]         $deps   Optional. An array of registered script handles this script depends on. Default empty array.
+ * @param string|bool|null $ver    Optional. String specifying script version number, if it has one, which is added to the URL
+ *                                 as a query string for cache busting purposes. If version is set to false, a version
+ *                                 number is automatically added equal to current installed WordPress version.
+ *                                 If set to null, no version is added.
+ * @param array|bool $args {
  *     Optional. An array of extra args for the script. Default empty array.
  *     Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
  *
- *     @type string                              $strategy            Optional. If provided, may be either 'defer' or 'async'.
- *     @type bool                                $in_footer           Optional. Whether to print the script in the footer. Default 'false'.
- *     @type string                              $fetchpriority       Optional. The fetch priority for the script. Default 'auto'.
- *     @type array<string|array<string, string>> $module_dependencies Optional. IDs for module dependencies loaded via dynamic import. Default empty array.
- *                                                                    For the full data format, see the `$deps` param of {@see wp_register_script_module()}.
+ *     @type string $strategy            Optional. If provided, may be either 'defer' or 'async'.
+ *     @type bool   $in_footer           Optional. Whether to print the script in the footer. Default 'false'.
+ *     @type string $fetchpriority       Optional. The fetch priority for the script. Default 'auto'.
+ *     @type array  $module_dependencies Optional. IDs for module dependencies loaded via dynamic import. Default empty array.
+ *                                       For the full data format, see the `$deps` param of {@see wp_register_script_module()}.
+ *                                       When provided, the script must either be printed in the footer (with
+ *                                       `in_footer` set to true) or use a deferred loading `strategy` (`defer`),
+ *                                       so that the script modules import map is printed before the script
+ *                                       is evaluated. Otherwise dynamic imports may fail to resolve.
  * }
+ *
+ * @phpstan-param non-empty-string $handle
+ * @phpstan-param string $src
+ * @phpstan-param non-empty-string[] $deps
+ * @phpstan-param array{
+ *     in_footer?: bool,
+ *     strategy?: 'async'|'defer',
+ *     fetchpriority?: 'low'|'auto'|'high',
+ *     module_dependencies?: array<non-empty-string|array{ id: non-empty-string, ... }>,
+ * }|bool $args
  */
 function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $args = array() ) {
@@ -412,4 +477,5 @@
 
 	if ( $src || ! empty( $args ) ) {
+		/** @var array{ 0: non-empty-string, 1?: string } $_handle */
 		$_handle = explode( '?', $handle );
 		if ( ! is_array( $args ) ) {
Index: /trunk/src/wp-includes/general-template.php
===================================================================
--- /trunk/src/wp-includes/general-template.php	(revision 62367)
+++ /trunk/src/wp-includes/general-template.php	(revision 62368)
@@ -4156,6 +4156,10 @@
 		),
 		'jshint'     => array(
-			'esversion' => 11,
-			'module'    => str_ends_with( $args['file'] ?? '', '.mjs' ),
+			'esversion'       => 11,
+			'module'          => str_ends_with( $args['file'] ?? '', '.mjs' ),
+
+			// This script module URL is intentionally referenced here instead of registering an espree script module
+			// in wp_default_script_modules(). This is a first stab at a core-only private module.
+			'espreeModuleUrl' => add_query_arg( 'ver', '9.6.1', includes_url( 'js/codemirror/espree.min.js' ) ),
 
 			// The following JSHint *linting rule* options are copied from
@@ -4164,17 +4168,17 @@
 			// are honored by the Espree-based integration, but these linting-rule options are not interpreted by Espree
 			// and are kept only for compatibility/documentation with the original JSHint configuration.
-			'boss'      => true,
-			'curly'     => true,
-			'eqeqeq'    => true,
-			'eqnull'    => true,
-			'expr'      => true,
-			'immed'     => true,
-			'noarg'     => true,
-			'nonbsp'    => true,
-			'quotmark'  => 'single',
-			'undef'     => true,
-			'unused'    => true,
-			'browser'   => true,
-			'globals'   => array(
+			'boss'            => true,
+			'curly'           => true,
+			'eqeqeq'          => true,
+			'eqnull'          => true,
+			'expr'            => true,
+			'immed'           => true,
+			'noarg'           => true,
+			'nonbsp'          => true,
+			'quotmark'        => 'single',
+			'undef'           => true,
+			'unused'          => true,
+			'browser'         => true,
+			'globals'         => array(
 				'_'                 => false,
 				'Backbone'          => false,
Index: /trunk/src/wp-includes/script-loader.php
===================================================================
--- /trunk/src/wp-includes/script-loader.php	(revision 62367)
+++ /trunk/src/wp-includes/script-loader.php	(revision 62368)
@@ -1201,7 +1201,6 @@
 
 	$scripts->add( 'wp-codemirror', '/wp-includes/js/codemirror/codemirror.min.js', array(), '5.65.20' );
-	did_action( 'init' ) && $scripts->add_data( 'wp-codemirror', 'module_dependencies', array( 'espree' ) );
 	$scripts->add( 'csslint', '/wp-includes/js/codemirror/csslint.js', array(), '1.0.5' );
-	$scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' ); // Deprecated. Use 'espree' script module.
+	$scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' ); // Deprecated.
 	$scripts->add( 'jshint', '/wp-includes/js/codemirror/fakejshint.js', array( 'esprima' ), '2.9.5' ); // Deprecated.
 	$scripts->add( 'jsonlint', '/wp-includes/js/codemirror/jsonlint.js', array(), '1.6.3' );
Index: /trunk/src/wp-includes/script-modules.php
===================================================================
--- /trunk/src/wp-includes/script-modules.php	(revision 62367)
+++ /trunk/src/wp-includes/script-modules.php	(revision 62368)
@@ -220,11 +220,4 @@
 		wp_register_script_module( $script_module_id, $path, $module_deps, $script_module_data['version'], $args );
 	}
-
-	wp_register_script_module(
-		'espree',
-		includes_url( 'js/codemirror/espree.min.js' ),
-		array(),
-		'9.6.1'
-	);
 }
 
Index: /trunk/tests/phpunit/includes/abstract-testcase.php
===================================================================
--- /trunk/tests/phpunit/includes/abstract-testcase.php	(revision 62367)
+++ /trunk/tests/phpunit/includes/abstract-testcase.php	(revision 62368)
@@ -19,5 +19,7 @@
 	protected $caught_deprecated       = array();
 	protected $expected_doing_it_wrong = array();
-	protected $caught_doing_it_wrong   = array();
+
+	/** @var non-empty-string[] */
+	protected $caught_doing_it_wrong = array();
 
 	protected static $hooks_saved = array();
Index: /trunk/tests/phpunit/tests/dependencies/scripts.php
===================================================================
--- /trunk/tests/phpunit/tests/dependencies/scripts.php	(revision 62367)
+++ /trunk/tests/phpunit/tests/dependencies/scripts.php	(revision 62368)
@@ -9,4 +9,18 @@
  * @covers ::wp_add_inline_script
  * @covers ::wp_set_script_translations
+ *
+ * @phpstan-type ScriptArgs array{
+ *     in_footer?: bool,
+ *     strategy?: 'async'|'defer',
+ *     fetchpriority?: 'low'|'auto'|'high',
+ *     module_dependencies?: array<non-empty-string|array{ id: non-empty-string, ... }>,
+ * }
+ * @phpstan-type WpEnqueueScriptArgs array{
+ *     0: non-empty-string, // $handle
+ *     1?: non-empty-string, // $src
+ *     2?: non-empty-string[], // $deps
+ *     3?: null|bool|string, // $version
+ *     4?: ScriptArgs,
+ * }
  */
 class Tests_Dependencies_Scripts extends WP_UnitTestCase {
@@ -1393,4 +1407,221 @@
 				'expected' => true,
 				'stored'   => array( 'valid', array( 'id' => 'valid2' ) ),
+			),
+		);
+	}
+
+	/**
+	 * Tests that registering a script with `module_dependencies` triggers `_doing_it_wrong`
+	 * when the script is not printed in the footer and does not use the `defer` strategy.
+	 *
+	 * @ticket 65165
+	 *
+	 * @covers ::wp_register_script
+	 * @covers ::wp_enqueue_script
+	 * @covers ::_wp_scripts_add_args_data
+	 *
+	 * @dataProvider data_module_dependencies_require_footer_or_defer
+	 *
+	 * @param callable-string $function_name Function name to call.
+	 * @param array           $args          Arguments to pass to the function.
+	 * @param bool            $should_warn   Whether the call is expected to trigger a `_doing_it_wrong` warning.
+	 *
+	 * @phpstan-param WpEnqueueScriptArgs $args
+	 */
+	public function test_module_dependencies_require_footer_or_defer( string $function_name, array $args, bool $should_warn ): void {
+		if ( $should_warn ) {
+			$this->setExpectedIncorrectUsage( $function_name );
+		}
+
+		call_user_func_array( $function_name, $args );
+
+		if ( $should_warn ) {
+			$this->assertStringContainsString(
+				'module_dependencies',
+				$this->caught_doing_it_wrong[ $function_name ],
+				'The _doing_it_wrong message should reference module_dependencies.'
+			);
+			$this->assertStringContainsString(
+				'in_footer',
+				$this->caught_doing_it_wrong[ $function_name ],
+				'The _doing_it_wrong message should reference the in_footer requirement.'
+			);
+			$this->assertStringContainsString(
+				'defer',
+				$this->caught_doing_it_wrong[ $function_name ],
+				'The _doing_it_wrong message should reference the defer strategy.'
+			);
+		} else {
+			$this->assertArrayNotHasKey(
+				$function_name,
+				$this->caught_doing_it_wrong,
+				'No _doing_it_wrong warning should be triggered when in_footer is true or strategy is defer.'
+			);
+		}
+	}
+
+	/**
+	 * Data provider for {@see self::test_module_dependencies_require_footer_or_defer()}.
+	 *
+	 * @phpstan-return array<string, array{
+	 *     function_name: callable-string,
+	 *     args: WpEnqueueScriptArgs,
+	 *     should_warn: bool,
+	 * }>
+	 */
+	public function data_module_dependencies_require_footer_or_defer(): array {
+		$base_args = array(
+			'/script.js',
+			array(),
+			null,
+		);
+
+		return array(
+			'register_blocking_warns'            => array(
+				'function_name' => 'wp_register_script',
+				'args'          => array_merge(
+					array( 'module-deps-blocking-register' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+						),
+					)
+				),
+				'should_warn'   => true,
+			),
+			'enqueue_blocking_warns'             => array(
+				'function_name' => 'wp_enqueue_script',
+				'args'          => array_merge(
+					array( 'module-deps-blocking-enqueue' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+						),
+					)
+				),
+				'should_warn'   => true,
+			),
+			'register_async_warns'               => array(
+				'function_name' => 'wp_register_script',
+				'args'          => array_merge(
+					array( 'module-deps-async-register' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+							'strategy'            => 'async',
+						),
+					)
+				),
+				'should_warn'   => true,
+			),
+			'enqueue_async_warns'                => array(
+				'function_name' => 'wp_enqueue_script',
+				'args'          => array_merge(
+					array( 'module-deps-async-enqueue' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+							'strategy'            => 'async',
+						),
+					)
+				),
+				'should_warn'   => true,
+			),
+			'register_in_footer_does_not_warn'   => array(
+				'function_name' => 'wp_register_script',
+				'args'          => array_merge(
+					array( 'module-deps-footer-register' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+							'in_footer'           => true,
+						),
+					)
+				),
+				'should_warn'   => false,
+			),
+			'enqueue_in_footer_does_not_warn'    => array(
+				'function_name' => 'wp_enqueue_script',
+				'args'          => array_merge(
+					array( 'module-deps-footer-enqueue' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+							'in_footer'           => true,
+						),
+					)
+				),
+				'should_warn'   => false,
+			),
+			'register_defer_does_not_warn'       => array(
+				'function_name' => 'wp_register_script',
+				'args'          => array_merge(
+					array( 'module-deps-defer-register' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+							'strategy'            => 'defer',
+						),
+					)
+				),
+				'should_warn'   => false,
+			),
+			'enqueue_defer_does_not_warn'        => array(
+				'function_name' => 'wp_enqueue_script',
+				'args'          => array_merge(
+					array( 'module-deps-defer-enqueue' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+							'strategy'            => 'defer',
+						),
+					)
+				),
+				'should_warn'   => false,
+			),
+			'register_footer_and_defer_no_warn'  => array(
+				'function_name' => 'wp_register_script',
+				'args'          => array_merge(
+					array( 'module-deps-footer-defer-register' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array( 'foo' ),
+							'in_footer'           => true,
+							'strategy'            => 'defer',
+						),
+					)
+				),
+				'should_warn'   => false,
+			),
+			'register_no_module_deps_no_warn'    => array(
+				'function_name' => 'wp_register_script',
+				'args'          => array_merge(
+					array( 'no-module-deps-register' ),
+					$base_args,
+					array( array() )
+				),
+				'should_warn'   => false,
+			),
+			'register_empty_module_deps_no_warn' => array(
+				'function_name' => 'wp_register_script',
+				'args'          => array_merge(
+					array( 'empty-module-deps-register' ),
+					$base_args,
+					array(
+						array(
+							'module_dependencies' => array(),
+						),
+					)
+				),
+				'should_warn'   => false,
 			),
 		);
@@ -3170,4 +3401,5 @@
 				'browser',
 				'globals',
+				'espreeModuleUrl',
 			),
 			array_keys( $wp_enqueue_code_editor['jshint'] )
@@ -3253,4 +3485,5 @@
 				'browser',
 				'globals',
+				'espreeModuleUrl',
 			),
 			array_keys( $wp_enqueue_code_editor['jshint'] )
@@ -3350,4 +3583,5 @@
 				'browser',
 				'globals',
+				'espreeModuleUrl',
 			),
 			array_keys( $wp_enqueue_code_editor['jshint'] )
@@ -3444,4 +3678,5 @@
 				'browser',
 				'globals',
+				'espreeModuleUrl',
 			),
 			array_keys( $wp_enqueue_code_editor['jshint'] )
Index: /trunk/tests/phpunit/tests/script-modules/wpScriptModules.php
===================================================================
--- /trunk/tests/phpunit/tests/script-modules/wpScriptModules.php	(revision 62367)
+++ /trunk/tests/phpunit/tests/script-modules/wpScriptModules.php	(revision 62368)
@@ -2042,4 +2042,5 @@
 			false,
 			array(
+				'strategy'            => 'defer',
 				'module_dependencies' => array(
 					'example',
@@ -2110,4 +2111,5 @@
 			false,
 			array(
+				'in_footer'           => true,
 				'module_dependencies' => array( 'classic-transitive-dependency' ),
 			)
@@ -2119,4 +2121,5 @@
 			false,
 			array(
+				'in_footer'           => true,
 				'module_dependencies' => array( 'not-enqueued' ),
 			)
@@ -2154,4 +2157,5 @@
 			null,
 			array(
+				'strategy'            => 'defer',
 				'module_dependencies' => array( 'does-not-exist' ),
 			)
