Repository navigation
Lazy scripts: replay a bundle's declared dependencies on live activation - #825
Conversation
A bundle the shell fetches lazily never went through WordPress's own dependency resolution, and the payload resolver dropped any handle without a src. A plugin whose config rides a src-less alias handle declared as its bundle's dependency (AllTerrain Forms) therefore opened, after a live activation, with the config undefined. Every lazy payload builder now ships the handle's dependency closure; native windows carry it as `deps` inside the handle-keyed script-data map. The loader replays an alias's inline data in print order, once per document, and recognises Core's printed tag ids so a dependency the page already has is never run twice.
|
@claude please review |
|
Claude finished @epeicher's task in 3m 10s —— View job Review of #825 — Lazy scripts: replay a bundle's declared dependencies on live activation
Note on execution: my sandbox in this environment declined approval for any non- SummaryThis is a clean, well-scoped fix. The core insight — that What I checked and confirms is correct
Minor observations (non-blocking)
TestingI was not able to execute No bugs found in this review. This looks ready pending the actual test/lint run. |
|
On the "repeated work per request" observation: measured rather than estimated, and it is negligible. On a member site as admin, after
About a fifth of a millisecond per request, so no memo. A per-request cache would also need scoping to a single build: inline data can be attached to a handle between resolutions (the refresh probe replays |
What it does
A plugin activated from inside the shell now opens its windows configured, without an F5. Reported with AllTerrain Forms: the tile appeared live, but the builder opened with "AllTerrain Forms is not configured on this page" until the page was reloaded.
CleanShot.2026-09-15.at.17.11.15.mp4
Rationale
AllTerrain Forms ships its config on a src-less alias handle (
wp_register_script( 'allterrain-forms-config', false )pluswp_add_inline_script()settingwindow.allTerrainForms), declared as a dependency of every bundle so it always runs first. On a normal boot WordPress prints that dependency. On a live activation the shell fetches the bundle lazily, and two things were missing:openstation_resolve_script_payload()returned an empty payload for any handle without asrc, so the alias could not have been delivered even by a path that walked dependencies.A second layer showed up while verifying: the plugin registers the same bundle as a command script too, and
loadVendorScriptmemoizes by URL, so whichever path fetches a bundle first decides what ran before it. Fixing native windows alone left the command sync winning that race with no deps. That is why every lazy payload builder ships the closure now, not just native windows.Implementation
openstation_resolve_script_payload()keeps an alias'sbefore/after/l10nwith an emptyurl, mirroring whatWP_Scripts::do_item()prints for it (translations excluded, as Core only prints those for a handle it printed a tag for). The command-palette manifest's local copy of that harvest is gone.nativeWindowScriptDatacarriesdeps, an ordered handle list whose members land in the same map, so a shared package is serialized once.hydrateServerEntries()joins them intoscriptDepson the entry, its companions and its tabs;loadOnce()hands them to the loader.scriptDepsviaopenstation_resolve_script_dependencies(), and eachserver-syncpassesdepsthrough. Widgets already did; the type now lives once asLazyScriptDependency.loadVendorScript: a dependency with an emptyurlis replayed inline in print order, once per document (replayedAliases), and skipped when Core already printed it.isScriptInDocument()gains that third signal: a tag under Core's printed ids (<handle>-js,<handle>-js-beforeand siblings), the only trace an alias leaves.Docs:
docs/migration-wp-package-globals.md(the "no other lazy path does this" paragraph is now the opposite), thescriptnote indocs/hooks-reference.md,docs/architecture.md,docs/javascript-reference.md.Not fixed here: AllTerrain Photo Editor still opens blank after a live activation. Its
lienzobundle is only enqueued onopenstation_mode_initat boot and never appears in any payload, so the shell cannot know about it. That is plugin-side: declare it through theopenstation_app_window_argsfilter as a companionscriptshandle, and attachwindow.lienzoConfigat registration rather than at enqueue time. With this PR merged, both then ride the live payload. Follow-up on the plugin: AllTerrainDeveloper/allterrain-photo-editor#10.Cost of resolving the closure per request
Every sub-builder now calls
openstation_resolve_script_dependencies()for each handle it names, with no memo across builders (a handle named by two builders is resolved twice). Measured on a member site as admin, afteradmin_enqueue_scripts, with 271 registered handles and 827 dependency edges:openstation_build_menu_payload()call, cold / warmThe repeated work is about a fifth of a millisecond on a request that costs hundreds, so it stays as is. A per-request memo is not free either: inline data can be attached to a handle between resolutions (the refresh probe replays
admin_enqueue_scripts, and modules attach config at priority 5 while the payload builds at 10), so a memo that outlives that window would ship stale data, whichtest_probe_payload_carries_data_attached_on_admin_enqueue_scriptsexists to catch.Testing instructions
On an install with AllTerrain Forms, deactivate it, reload the shell, activate it from the Plugins window, then double-click the Forms desktop icon: the builder opens configured. Before this change the same steps reproduce the report.
npm run test:js -- vendor-loader script-presence native-windows-lazy-script commands-server-sync npm run test:php -- --filter='Tests_OpenStation_ResolveScriptDependencies|Tests_OpenStation_NativeWindowLazyScript|Tests_OpenStation_Commands'New tests pin: an alias dependency keeps its inline data and an empty one is dropped; a window ships its closure and a dependency later named as a script resolves its own; the command payload ships
scriptDeps; alias replay order, once-per-document, and skip-when-printed; id-based presence. Fulltest:js,test:php,lint,lint:phpandtypecheckare green.