From 28a04eacda36e06fd9d3f24898dca45310856b4b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 10 May 2026 17:46:39 -0700 Subject: [PATCH 1/3] Build: Defer single-page admin init until DOMContentLoaded Wrap the dynamic import("@wordpress/boot") emitted by page-wp-admin.php.template in a DOMContentLoaded guard so the boot module evaluates only after its classic-script dependencies (wp-private-apis, wp-components, wp-theme, etc.) have parsed and executed. Otherwise, a modulepreloaded @wordpress/boot can win the race against the classic-script-printing pass on fast CDN-fronted hosts in Chrome, evaluating before wp.theme.privateApis is defined and throwing "Cannot unlock an undefined object". Mirrors the corresponding fix made to the generated page-wp-admin.php files in wordpress-develop. See Trac #65103 and https://github.com/WordPress/wordpress-develop/pull/11611. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: Khokan Sardar --- .../templates/page-wp-admin.php.template | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/wp-build/templates/page-wp-admin.php.template b/packages/wp-build/templates/page-wp-admin.php.template index 23241a3921fd6f..d84546dcea69a3 100644 --- a/packages/wp-build/templates/page-wp-admin.php.template +++ b/packages/wp-build/templates/page-wp-admin.php.template @@ -153,12 +153,35 @@ function {{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_enqueue_scripts( $hook_suf // 2. It initializes the boot module as an inline script. wp_register_script( '{{PAGE_SLUG}}-wp-admin-prerequisites', '', $asset['dependencies'], $asset['version'], true ); - // Add inline script to initialize the app using initSinglePage (no menuItems) + /* + * Add inline script to initialize the app using initSinglePage (no menuItems). + * The dynamic import is deferred until DOMContentLoaded so that all classic + * script dependencies of @wordpress/boot (wp-private-apis, wp-components, + * wp-theme, etc.) have finished parsing and executing before the boot module + * evaluates. Otherwise, a modulepreloaded @wordpress/boot can win the race + * against the classic-script-printing pass on fast CDN-fronted hosts in + * Chrome, evaluating before wp.theme.privateApis is defined and throwing + * "Cannot unlock an undefined object". See #65103. + */ + $init_js_function = <<<'JS' +( mountId, routes ) => { + const run = async () => { + const mod = await import( "@wordpress/boot" ); + mod.initSinglePage( { mountId, routes } ); + }; + if ( document.readyState === "loading" ) { + document.addEventListener( "DOMContentLoaded", run ); + } else { + run(); + } +} +JS; wp_add_inline_script( '{{PAGE_SLUG}}-wp-admin-prerequisites', sprintf( - 'import("@wordpress/boot").then(mod => mod.initSinglePage({mountId: "%s", routes: %s}));', - '{{PAGE_SLUG}}-wp-admin-app', + '( %s )( %s, %s );', + $init_js_function, + wp_json_encode( '{{PAGE_SLUG}}-wp-admin-app', JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ), wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) ) ); From 674e0d1bd911790ea6d19c31abb8a323c6433889 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 10 May 2026 17:48:00 -0700 Subject: [PATCH 2/3] Use indentation with flexible nowdoc syntax --- .../templates/page-wp-admin.php.template | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/wp-build/templates/page-wp-admin.php.template b/packages/wp-build/templates/page-wp-admin.php.template index d84546dcea69a3..d40f2a4e652ba0 100644 --- a/packages/wp-build/templates/page-wp-admin.php.template +++ b/packages/wp-build/templates/page-wp-admin.php.template @@ -164,18 +164,18 @@ function {{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_enqueue_scripts( $hook_suf * "Cannot unlock an undefined object". See #65103. */ $init_js_function = <<<'JS' -( mountId, routes ) => { - const run = async () => { - const mod = await import( "@wordpress/boot" ); - mod.initSinglePage( { mountId, routes } ); - }; - if ( document.readyState === "loading" ) { - document.addEventListener( "DOMContentLoaded", run ); - } else { - run(); - } -} -JS; + ( mountId, routes ) => { + const run = async () => { + const mod = await import( "@wordpress/boot" ); + mod.initSinglePage( { mountId, routes } ); + }; + if ( document.readyState === "loading" ) { + document.addEventListener( "DOMContentLoaded", run ); + } else { + run(); + } + } + JS; wp_add_inline_script( '{{PAGE_SLUG}}-wp-admin-prerequisites', sprintf( From ae1f0b885b52ab620be8d087e138e4bcbc2fad7d Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 12 May 2026 13:28:48 -0700 Subject: [PATCH 3/3] Replace bare ticket reference with ticket URL --- packages/wp-build/templates/page-wp-admin.php.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wp-build/templates/page-wp-admin.php.template b/packages/wp-build/templates/page-wp-admin.php.template index d40f2a4e652ba0..7f9caebc4175e2 100644 --- a/packages/wp-build/templates/page-wp-admin.php.template +++ b/packages/wp-build/templates/page-wp-admin.php.template @@ -161,7 +161,7 @@ function {{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_enqueue_scripts( $hook_suf * evaluates. Otherwise, a modulepreloaded @wordpress/boot can win the race * against the classic-script-printing pass on fast CDN-fronted hosts in * Chrome, evaluating before wp.theme.privateApis is defined and throwing - * "Cannot unlock an undefined object". See #65103. + * "Cannot unlock an undefined object". See . */ $init_js_function = <<<'JS' ( mountId, routes ) => {