Widget Types: REST endpoint and core-data entity#77987
Conversation
internal singleton hydrated from the build manifest at init
Surface packages handle widget-to-page wiring.
core-data widgetModule entity reads it, custom store removed
extension point so surfaces can append script-module deps
via dashboard-wp-admin_boot_dependencies filter
default 'id' is missing in REST records, core-data drops them from cache
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
drops the static entry from entities.js so the entity is only registered when the dashboard widgets package is loaded
# Conflicts: # lib/experimental/dashboard-widgets/widget-types.php # packages/wp-build/templates/page-wp-admin.php.template # packages/wp-build/templates/page.php.template
|
Size Change: 0 B Total Size: 7.94 MB ℹ️ View Unchanged
|
|
Flaky tests detected in bb3b3bb. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/25406152102
|
TL;DR
A widget type is server-side metadata: a namespaced name, a script-module handle for its render entry point, and a script-module handle for its metadata module. The server knows them; the client renders them.
The transport between the two used to be an inline
window.__registeredWidgetTypesglobal stamped by PHP onadmin_print_scripts. It was tagged "stopgap" from day one, sat outside the data layer, and required a manualdispatch.registerWidgetTypestep to land in a customcore/widget-typesstore.This PR replaces that bridge with a proper REST endpoint backed by
core-data. Widget types now flow through the same data layer pattern as post types, taxonomies, and every other server-owned collection:getEntityRecordsfor the list, lazyimport()for each entry's metadata.The custom
core/widget-typesdata store is deleted. There is no client-side registration step.Part of #77616 #77625
How a widget reaches the dashboard stage
End-to-end: from "the build pipeline discovers a widget folder" to "a React component renders on screen".
1. Build discovery.
@wordpress/buildwalkswidgets/, parses eachwidget.jsonwith itsrender.tsxandwidget.tsfiles, and emits both a static manifest (build/widgets/registry.php) and per-widget script modules (build/widgets/<name>/render.min.js,widget.min.js).2. Server-side script-module registration. At
init,gutenberg_register_widget_modules()callswp_register_script_module()once per discovered widget. The modules are known to WordPress but not yet loaded by any page.3. Server-side type registry. Also at
init,gutenberg_register_widget_types()populatesWP_Widget_Type_Registry(singleton) from the manifest. The registry holdsWP_Widget_Typeinstances keyed by namespaced name.4. REST endpoint.
WP_REST_Widget_Modules_Controllermounts onrest_api_initand exposes the registry at/wp/v2/widget-modules. Each record returns{ name, render_module, widget_module }: the catalog the client needs to know what to import, nothing more.5. Per-page import map. Each page emitted by
@wordpress/builddeclares its boot script-module dependencies. The dashboard page hooks the new genericdashboard-wp-admin_boot_dependenciesfilter (introduced in this PR) and appends every registered widget's render and widget modules as'dynamic'entries. They appear in the page's import map without eager execution.6. core-data entity registration. When the dashboard JS first runs,
use-widget-types.tscallsdispatch( coreStore ).addEntities( [ widgetModuleEntity ] )at module load. The entity is scoped to this experimental feature: WP installs that never load the dashboard widgets package never see it.7. Hook fetches.
useWidgetTypes()readsgetEntityRecords( 'root', 'widgetModule' ). core-data resolves the selector byapiFetch-ing/wp/v2/widget-modulesonce, caches the response, and returns the array on subsequent reads.8. Lazy metadata. For each record the hook runs
await import( record.widget_module ). The bare specifier resolves through the page's import map (step 5) to the actual JS module, whose default export carries the rich metadata (title,description,category,attributes, ...).9. Stage renders. The dashboard stage reads
useWidgetTypes()and renders eachWidgetType. TherenderModulehandle on each type is a separate dynamic import the surface uses when mounting the widget on screen.End-to-end, the path is the same shape as every other entity in core-data: REST + selector + lazy resolution. No client-side registration, no
windowglobal, no custom data store.Why
widgetModule(and/wp/v2/widget-modules)The entity name
widgetTypeand the path/wp/v2/widget-typesare taken by WordPress core's legacy widgets API (the sidebar widgets registered viaregister_widget()). Reusing them would collide with every WP install.The new entity uses
widgetModuleas its core-data name and/wp/v2/widget-modulesas its path. The PHP class is stillWP_Widget_Typebecause the legacy stack usesWP_WidgetandWP_Widget_Factory; there is no collision on the PHP side.The asymmetry is contained: the PHP class names what the thing is (a widget type); the data layer names how it ships (as a script module).
Why a generic boot dependencies filter
Step 5 needs a way for the dashboard surface to declare which widget modules it wants in its import map.
Hardcoding a foreach over the widget catalog inside
@wordpress/build's page templates would violate the package's contract as a generic build tool (AGENTS.md:75). It would also mean every emitted page paid the cost of a complete widget catalog, regardless of whether it renders widgets.This PR adds a generic
apply_filters( '{page-id}_boot_dependencies', $boot_dependencies )(and the-wp-adminvariant) just before each boot module registration. Surfaces hook in to add their own deps. The dashboard adds widget modules; another surface could add something else; pages without a hook are unaffected.Network
wp-json/wp/v2/widget-modulesendpointTesting
28 tests / 65 assertions cover the registry and the new controller (routes, collection, item, 404, permissions, schema).
Manual:
gutenberg-dashboard-widgetsexperiment.GET /wp/v2/widget-modulesreturns the registered widgets.?page=dashboard-wp-admin. The dashboard renders, the page's import map containswp/widgets/<name>/renderandwp/widgets/<name>/widgetentries, and the resolved widget types appear in the dashboard stage.Follow-ups
register_widget_type()PHP API for runtime authoring.