Skip to content

Commit 484acd5

Browse files
committed
Script Loader: Defer single-page admin init until DOMContentLoaded.
The Connectors and Font Library admin screens register a `-prerequisites` classic-script handle with an empty `src` and attach an inline script that does `import("@wordpress/boot").then(mod => mod.init(...))`. Because the handle has no `src`, only the inline is printed, and it runs as a classic script the moment the parser reaches it. On fast CDN-fronted hosts (WordPress VIP, CloudFront, etc.) in Chrome, `@wordpress/boot` is already `<link rel="modulepreload">`-ed, so the dynamic import resolves and the module evaluates before the parser has reached the classic deps it relies on (`wp-private-apis`, `wp-components`, `wp-theme`). At its top-level `@wordpress/boot` reads `window.wp.theme.privateApis`, which is still undefined at that point, causing `unlock(undefined)` to throw "Cannot unlock an undefined object". The mount element stays empty and `initSinglePage` / `init` never runs. Wrap the dynamic import in `DOMContentLoaded`. The HTML spec guarantees `DOMContentLoaded` fires only after every parser-blocking classic `<script>` has executed, so all required globals are populated before `@wordpress/boot` evaluates. A `document.readyState` guard preserves the existing behavior when the inline is evaluated after DOM ready (e.g. injected late). Applied to all four auto-generated files that share the pattern: * src/wp-includes/build/pages/options-connectors/page.php * src/wp-includes/build/pages/options-connectors/page-wp-admin.php * src/wp-includes/build/pages/font-library/page.php * src/wp-includes/build/pages/font-library/page-wp-admin.php Fixes #65103. Made-with: Cursor
1 parent 67094ee commit 484acd5

File tree

4 files changed

+48
-8
lines changed

4 files changed

+48
-8
lines changed

src/wp-includes/build/pages/font-library/page-wp-admin.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,21 @@ function wp_font_library_wp_admin_enqueue_scripts( $hook_suffix ) {
153153
// 2. It initializes the boot module as an inline script.
154154
wp_register_script( 'font-library-wp-admin-prerequisites', '', $asset['dependencies'], $asset['version'], true );
155155

156-
// Add inline script to initialize the app using initSinglePage (no menuItems)
156+
/*
157+
* Add inline script to initialize the app using initSinglePage (no menuItems).
158+
*
159+
* The call is deferred until DOMContentLoaded so that all classic script
160+
* dependencies of @wordpress/boot (wp-private-apis, wp-components, wp-theme,
161+
* etc.) have finished parsing and executing before the dynamic module import
162+
* resolves. Without this, a modulepreloaded @wordpress/boot can win the race
163+
* against the classic-script-printing pass on fast CDN-fronted hosts in
164+
* Chrome, evaluating before wp.theme.privateApis is defined and throwing
165+
* "Cannot unlock an undefined object". See Trac #65103.
166+
*/
157167
wp_add_inline_script(
158168
'font-library-wp-admin-prerequisites',
159169
sprintf(
160-
'import("@wordpress/boot").then(mod => mod.initSinglePage({mountId: "%s", routes: %s}));',
170+
'(function(){var run=function(){import("@wordpress/boot").then(function(mod){mod.initSinglePage({mountId: "%s", routes: %s});});};if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",run);}else{run();}})();',
161171
'font-library-wp-admin-app',
162172
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
163173
)

src/wp-includes/build/pages/font-library/page.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,22 @@ function wp_font_library_render_page() {
159159
// 2. It initializes the boot module as an inline script.
160160
wp_register_script( 'font-library-prerequisites', '', $asset['dependencies'], $asset['version'], true );
161161

162-
// Add inline script to initialize the app
162+
/*
163+
* Add inline script to initialize the app.
164+
*
165+
* The call is deferred until DOMContentLoaded so that all classic script
166+
* dependencies of @wordpress/boot (wp-private-apis, wp-components, wp-theme,
167+
* etc.) have finished parsing and executing before the dynamic module import
168+
* resolves. Without this, a modulepreloaded @wordpress/boot can win the race
169+
* against the classic-script-printing pass on fast CDN-fronted hosts in
170+
* Chrome, evaluating before wp.theme.privateApis is defined and throwing
171+
* "Cannot unlock an undefined object". See Trac #65103.
172+
*/
163173
$init_modules = [];
164174
wp_add_inline_script(
165175
'font-library-prerequisites',
166176
sprintf(
167-
'import("@wordpress/boot").then(mod => mod.init({mountId: "%s", menuItems: %s, routes: %s, initModules: %s, dashboardLink: "%s"}));',
177+
'(function(){var run=function(){import("@wordpress/boot").then(function(mod){mod.init({mountId: "%s", menuItems: %s, routes: %s, initModules: %s, dashboardLink: "%s"});});};if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",run);}else{run();}})();',
168178
'font-library-app',
169179
wp_json_encode( $menu_items, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
170180
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),

src/wp-includes/build/pages/options-connectors/page-wp-admin.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,21 @@ function wp_options_connectors_wp_admin_enqueue_scripts( $hook_suffix ) {
153153
// 2. It initializes the boot module as an inline script.
154154
wp_register_script( 'options-connectors-wp-admin-prerequisites', '', $asset['dependencies'], $asset['version'], true );
155155

156-
// Add inline script to initialize the app using initSinglePage (no menuItems)
156+
/*
157+
* Add inline script to initialize the app using initSinglePage (no menuItems).
158+
*
159+
* The call is deferred until DOMContentLoaded so that all classic script
160+
* dependencies of @wordpress/boot (wp-private-apis, wp-components, wp-theme,
161+
* etc.) have finished parsing and executing before the dynamic module import
162+
* resolves. Without this, a modulepreloaded @wordpress/boot can win the race
163+
* against the classic-script-printing pass on fast CDN-fronted hosts in
164+
* Chrome, evaluating before wp.theme.privateApis is defined and throwing
165+
* "Cannot unlock an undefined object". See Trac #65103.
166+
*/
157167
wp_add_inline_script(
158168
'options-connectors-wp-admin-prerequisites',
159169
sprintf(
160-
'import("@wordpress/boot").then(mod => mod.initSinglePage({mountId: "%s", routes: %s}));',
170+
'(function(){var run=function(){import("@wordpress/boot").then(function(mod){mod.initSinglePage({mountId: "%s", routes: %s});});};if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",run);}else{run();}})();',
161171
'options-connectors-wp-admin-app',
162172
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
163173
)

src/wp-includes/build/pages/options-connectors/page.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,22 @@ function wp_options_connectors_render_page() {
159159
// 2. It initializes the boot module as an inline script.
160160
wp_register_script( 'options-connectors-prerequisites', '', $asset['dependencies'], $asset['version'], true );
161161

162-
// Add inline script to initialize the app
162+
/*
163+
* Add inline script to initialize the app.
164+
*
165+
* The call is deferred until DOMContentLoaded so that all classic script
166+
* dependencies of @wordpress/boot (wp-private-apis, wp-components, wp-theme,
167+
* etc.) have finished parsing and executing before the dynamic module import
168+
* resolves. Without this, a modulepreloaded @wordpress/boot can win the race
169+
* against the classic-script-printing pass on fast CDN-fronted hosts in
170+
* Chrome, evaluating before wp.theme.privateApis is defined and throwing
171+
* "Cannot unlock an undefined object". See Trac #65103.
172+
*/
163173
$init_modules = [];
164174
wp_add_inline_script(
165175
'options-connectors-prerequisites',
166176
sprintf(
167-
'import("@wordpress/boot").then(mod => mod.init({mountId: "%s", menuItems: %s, routes: %s, initModules: %s, dashboardLink: "%s"}));',
177+
'(function(){var run=function(){import("@wordpress/boot").then(function(mod){mod.init({mountId: "%s", menuItems: %s, routes: %s, initModules: %s, dashboardLink: "%s"});});};if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",run);}else{run();}})();',
168178
'options-connectors-app',
169179
wp_json_encode( $menu_items, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
170180
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),

0 commit comments

Comments
 (0)