Make WordPress Core

Opened 4 hours ago

Last modified 4 minutes ago

#66642 new enhancement

Command Palette: Make every admin screen faster by loading the palette only when it is opened

Reported by: ReneHermi Owned by:
Priority: normal Milestone: Awaiting Review
Component: Script Loader Version: 6.9
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses: javascript, performance

Description

This patch makes wp-admin pages ready about twice as fast for everyone who works in the dashboard, by loading the Command Palette only when someone actually opens it instead of on every page.

On a Settings screen, the page becomes usable in 57 ms instead of 127 ms with a warm cache, and in 299 ms instead of 910 ms on a simulated slower device, with less than half the scripts. The palette itself works exactly as before: Ctrl/Cmd+K and the toolbar button still open it, and the editors are unchanged.

Background

Since [61022] (#63845), wp_enqueue_command_palette_assets() runs on admin_enqueue_scripts for every admin screen. It enqueues wp-commands and wp-core-commands, which pull in most of the block editor's dependency tree: react, react-dom, wp-element, wp-components, wp-data, wp-core-data, wp-block-editor, wp-blocks, wp-rich-text, wp-compose, moment, wp-api-fetch, wp-router, wp-preferences, wp-keyboard-shortcuts and others, plus the render-blocking wp-commands and wp-components stylesheets.

Every classic admin screen (Dashboard, Posts, Plugins, Settings, any plugin's own page) downloads, parses and executes all of this before DOMContentLoaded, although the palette is only needed once someone presses Ctrl/Cmd+K or clicks its toolbar button (#64672).

Proposed change

The attached patch loads the palette on first use on screens that do not already load the commands package.

  • wp_enqueue_command_palette_loader() replaces wp_enqueue_command_palette_assets() on admin_enqueue_scripts. It builds the palette settings (menu commands included) exactly as before, attaches the wp.coreCommands.initializeCommandPalette() call to wp-core-commands, and enqueues only a new 1.4 KB script, wp-command-palette-loader.
  • wp_print_command_palette_assets_template() runs on admin_print_footer_scripts (priority 20). It prints the wp-commands style and the wp-core-commands script with WP_Styles::do_items() / WP_Scripts::do_items() into an inert <template id="wp-command-palette-assets">. Only what the page has not printed yet ends up there, and inline scripts, translations and localized data come along, since the regular printing code produces the markup.
  • On Ctrl/Cmd+K or a click on the toolbar button, the loader preloads every script in the template, inserts them in their printed order (waiting for each one, so inline before/after scripts keep their order), inserts the stylesheets, and then opens the palette with wp.data.dispatch( 'core/commands' ).open(). After that the palette's own keyboard shortcut handler takes over.
  • Screens that already load the commands package, such as the post editor and the site editor, keep loading the palette up front: if wp-commands is already enqueued, wp_enqueue_command_palette_loader() enqueues wp-core-commands and the wp-commands style as before, and no template is printed.
  • wp_enqueue_command_palette_assets() stays for direct callers such as the boot-based Font Library and Connectors pages, and still loads everything up front.
  • The toolbar button no longer has an inline onclick that calls wp.data, which would not exist yet on a classic screen; the loader handles the click on every screen.
  • wp_prefetch_admin_assets() no longer prefetches the wp-commands stylesheet from the login screen, since admin screens no longer load it up front. The editor prefetch still covers it through wp-edit-post.

I considered the smaller step of registering the palette handles with the defer strategy. It would stop them from blocking the parser, but deferred scripts still download and execute before DOMContentLoaded fires, on every screen. Loading on demand removes the cost from screens where the palette is never opened.

Measurements

Local wordpress-develop environment (trunk at 7.2-beta4-65231-src, Docker, PHP latest, nginx without gzip), single site, wp-admin/options-general.php, SCRIPT_DEBUG off. Headless Chromium (Playwright 1.64), 8 rounds each; each round is a fresh browser context (empty cache) followed by a reload (warm cache). Trunk was measured first, the patch afterwards on the same server. DOMContentLoaded is domContentLoadedEventEnd from Navigation Timing, given as median (min–max).

trunk patch
<script src> on the page 80 39
stylesheets on the page 29 26
bytes transferred, empty cache (uncompressed) 7.35 MB 1.57 MB
DOMContentLoaded, empty cache 251 ms (236–902) 97 ms (95–101)
DOMContentLoaded, warm cache 127 ms (120–144) 57 ms (55–108)
DOMContentLoaded, empty cache, 4× CPU slowdown 910 ms (902–928) 299 ms (292–303)
DOMContentLoaded, warm cache, 4× CPU slowdown 452 ms (448–463) 135 ms (128–141)

The first round of the unthrottled trunk run (902 ms) is a cold-server outlier.

The cost moves to the first time the palette is opened on a page: from the key press to the visible palette took 202–261 ms with an empty cache and 97–102 ms with a warm cache (localhost, no throttling). Opening it again on the same page is instant, as before.

Earlier, on a production WordPress 7.1.4 site (single site, a plugin admin page, headless Chromium, 8 alternating rounds with and without remove_action( 'admin_enqueue_scripts', 'wp_enqueue_command_palette_assets' )), the palette accounted for 42 of 59 scripts, +0.57 s DOMContentLoaded with an empty cache (1.65–1.79 s vs. 1.09–1.30 s) and about 2× with a warm cache (0.31–0.36 s vs. 0.17–0.21 s).

Testing

  • New PHPUnit tests in tests/phpunit/tests/dependencies/wpCommandPaletteLoader.php; wpPrefetchAdminAssets.php updated. The dependencies, scripts, admin-bar and script-modules groups pass on single site and multisite, and the full single-site suite passes (32,226 tests).
  • New Playwright spec tests/e2e/specs/command-palette.test.js: the palette is not loaded up front on a classic screen, opens with the shortcut and the toolbar button, runs a menu command, opens again after loading, and is still loaded up front in the block editor.
  • Checked by hand: Cmd+K and the toolbar button on Settings and Posts, searching and running "Settings > Permalinks", the post editor and the site editor, no console errors.

Attachments (1)

66642.diff​ (26.7 KB ) - added by ReneHermi 4 hours ago.
Load the Command Palette on demand, with PHPUnit and e2e tests.

Download all attachments as: .zip

Change History (4)

@ReneHermi
4 hours ago

Load the Command Palette on demand, with PHPUnit and e2e tests.

This ticket was mentioned in ​PR #15871 on ​WordPress/wordpress-develop by ​@ReneHermi.


4 hours ago
#1

This makes wp-admin pages ready about twice as fast for everyone who works in the dashboard, by loading the Command Palette only when someone actually opens it instead of on every page.

On a Settings screen, the page becomes usable in 57 ms instead of 127 ms with a warm cache, and in 299 ms instead of 910 ms on a simulated slower device, with less than half the scripts. The palette works exactly as before: Ctrl/Cmd+K and the toolbar button still open it, and the editors are unchanged.

Why

Since 6.9, every admin screen enqueues wp-commands and wp-core-commands, which pull in about 40 block-editor scripts (React, wp-components, wp-block-editor, wp-core-data, …) and the wp-components stylesheet, although the palette is only needed after Ctrl/Cmd+K or a click on its toolbar button.

How

  • wp_enqueue_command_palette_loader() replaces wp_enqueue_command_palette_assets() on admin_enqueue_scripts. It builds the same settings and menu commands, attaches the initializeCommandPalette() call to wp-core-commands, and enqueues only the new 1.4 KB wp-command-palette-loader script.
  • wp_print_command_palette_assets_template() prints the palette's not-yet-printed styles and scripts, with their inline scripts and translations, into an inert <template> in the footer, using WP_Styles::do_items() / WP_Scripts::do_items().
  • On the shortcut or a toolbar click, the loader preloads the template's scripts, inserts them in order, inserts the styles, and opens the palette. After that the palette's own shortcut handler takes over.
  • Screens that already load wp-commands (post editor, site editor) keep loading the palette up front. wp_enqueue_command_palette_assets() still loads everything up front for direct callers such as the Font Library and Connectors pages.
  • The toolbar button drops its inline onclick (which needs wp.data); the loader handles the click.
  • The login screen no longer prefetches the wp-commands stylesheet, since admin screens no longer load it up front.

Measured on the local env, wp-admin/options-general.php, SCRIPT_DEBUG off, headless Chromium, 8 rounds, median DOMContentLoaded:

trunk this PR
<script src> on the page 80 39
empty cache 251 ms 97 ms
warm cache 127 ms 57 ms
empty cache, 4× CPU slowdown 910 ms 299 ms
warm cache, 4× CPU slowdown 452 ms 135 ms

The first open of the palette on a page takes 200–260 ms with an empty cache and about 100 ms with a warm cache. Full numbers and method are in the ticket.

Tests: new tests/phpunit/tests/dependencies/wpCommandPaletteLoader.php, updated wpPrefetchAdminAssets.php, and a new Playwright spec tests/e2e/specs/command-palette.test.js.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Implementation, tests, measurements and the ticket text, under my direction; I reviewed the change and take responsibility for it.

This ticket was mentioned in ​Slack in #core-performance by mashshare. ​View the logs.


4 hours ago

#3 @alaasalama
4 minutes ago

Reviewed the approach. Using an inert template in the footer (wp_print_command_palette_assets_template) to queue unprinted scripts/styles and preloading them on first interaction (Ctrl/Cmd+K or the toolbar button) is clean and avoids breaking existing dependencies. Big win for admin performance.

Note: See TracTickets for help on using tickets.