Skip to content

Cache the theme.json data merged from all origins - #81979

Merged
sirreal merged 21 commits into
trunkfrom
fix/cache-merged-theme-json-data
Oct 9, 2026
Merged

sirreal merged 21 commits into
trunkfrom
fix/cache-merged-theme-json-data

Conversation

@sirreal

@sirreal sirreal commented Aug 24, 2026 •

Copy link
Copy Markdown
Member

What?

Cache WP_Theme_JSON_Resolver_Gutenberg::get_merged_data() per origin for the request, so repeated calls reuse the merged data. Remove the request caches in gutenberg_get_global_settings() and gutenberg_get_global_styles(), which now read through to the resolver.

Supersedes #81883. Core backport: WordPress/wordpress-develop#14071.

Why?

The accessor cache added in #81889 avoids repeated merges for gutenberg_get_global_styles(), but direct resolver callers still merge on every call. These include block style variations, stylesheets, settings and duotone. Block style variations account for most of the remaining work: one merge per block with an is-style-* class.

Caching in the resolver serves all callers and applies the existing block-registration check to every read. Keeping the accessor cache as well produced no measurable request-time improvement.

How?

The resolver caches each origin separately in $merged. A merged entry in $blocks_cache reuses has_same_registered_blocks(), so a block registered after the first merge discards the cached data, as it already does for the per-origin caches. clean_cached_data() resets $merged and rebuilds $blocks_cache from its own keys. An unknown origin resolves to custom, which the docblock already documents as the fallback.

Each call returns a clone, so callers can modify the result without changing the cache. WP_Theme_JSON_Gutenberg::resolve_variables() relies on that.

The gallery block keeps its per-request static. The static is redundant in the plugin, where the build maps wp_get_global_styles() to the plugin accessor, but WordPress Core has no cache behind the function until the resolver change is backported. Only the comment changes.

Compatibility:

  • Theme supports added after the first merge in a request need _gutenberg_clean_theme_json_caches() before they appear in merged data. The wp_theme_json_data_* filters already behaved this way through the per-origin caches. Normally both are registered on after_setup_theme.
  • The registration check detects block additions, not removals, as before.
  • The removed accessor caches lived in the non-persistent theme_json group, so nothing changes across requests. Their WP_DEBUG bypass only affected the current request.
  • backport-changelog/7.2/14071.md lists Try caching gutenberg_get_global_styles #81889 as well, since this PR removes the cache it added and Core's layout support never had the static it removed. The entry for wordpress-develop#13214 is dropped; that PR has nothing left to port.

Performance

Measured on 2026-10-07 on an earlier revision of this branch with the same caching behavior, against trunk 4791f174f20. WordPress 7.2-alpha-64225, WP_DEBUG off, median of 20 requests after one warm-up. Values are total time in get_merged_data() per request, including the first merge.

Page Trunk This PR
Twenty Twenty-Three front page 3.59 ms 2.03 ms
Twenty Twenty-Four front page 6.61 ms 1.92 ms
Large post, Twenty Twenty-Four 3.64 ms 1.96 ms
Post with 52 block style variations, Twenty Twenty-Four 12.92 ms 1.92 ms

A separate perf-tool run on WordPress 7.1.3, with three interleaved rounds of 16 samples, reduced median wpTemplate time from 15.73 to 13.49 ms for Twenty Twenty-Three and from 12.04 to 10.86 ms for Twenty Twenty-One. Retaining the accessor cache alongside the resolver cache made no difference beyond run-to-run variation.

Testing Instructions

With built assets and the PHP test environment running:

npm run test:unit:php:base -- --filter 'WP_Theme_JSON_Resolver_Gutenberg_Test|WP_Theme_Json_Test'

Tests cover a separate instance per call, the cached data per origin, modification of the returned instance, late block registration, clean_cached_data(), theme switching, and the accessors reflecting a block registered or theme data changed after a previous call.

Local run on 2026-10-07 at the current head, including Tests_Blocks_Render_Gallery: 77 tests, 253 assertions, passing.

Use of AI Tools

Claude generated the implementation, tests, review fixes and this description under @sirreal's direction, including work with Claude Fable 5.1. Codex edited an earlier description. Human review of the generated code is pending.

@github-actions github-actions Bot added the [Package] Block library /packages/block-library label Aug 24, 2026
@sirreal
sirreal force-pushed the fix/cache-merged-theme-json-data branch from 18a323d to aa55294 Compare August 24, 2026 09:17
@sirreal sirreal added the [Type] Bug An existing feature does not function as intended label Aug 24, 2026
@sirreal
sirreal force-pushed the fix/cache-merged-theme-json-data branch from aa55294 to fafc2b8 Compare August 26, 2026 09:53
@sirreal sirreal added [Type] Performance Related to performance efforts and removed [Type] Bug An existing feature does not function as intended labels Sep 3, 2026
@sirreal
sirreal force-pushed the fix/cache-merged-theme-json-data branch from fafc2b8 to 3e4e20a Compare September 3, 2026 09:24
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

🎉 Props

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: sirreal <jonsurrell@git.wordpress.org>
Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric a50c228 trunk % Change
timeToFirstByte 57.05 ms +6.22% -2.8% 58.3 ms +15.35% -4.37% -2.14%
largestContentfulPaint 96 ms +8.33% -4.17% 100 ms +6% -12% -4%
lcpMinusTtfb 37.45 ms +19.49% -5.61% 36.65 ms +13.23% -9.55% 2.18%
wpBeforeTemplate 29.86 ms +13.33% -2.55% 29.57 ms +9.5% -4.97% 0.98%
wpTemplate 22.79 ms +5.4% -3.6% 25.54 ms +7.44% -6.26% -10.77%
wpTotal 53.17 ms +6.86% -2.93% 54.85 ms +13.97% -5.07% -3.06%
wpMemoryUsage 7.61 MB +0% -0% 7.65 MB +0% -0% -0.57%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric a50c228 trunk % Change
timeToFirstByte 48.6 ms +1.95% -3.4% 48.45 ms +4.13% -2.37% 0.31%
largestContentfulPaint 104 ms +3.85% -0% 104 ms +3.85% -0% 0%
lcpMinusTtfb 56.4 ms +3.9% -2.39% 56.1 ms +3.65% -2.94% 0.53%
wpBeforeTemplate 27.73 ms +4.8% -3.75% 26.74 ms +1.68% -3.03% 3.7%
wpTemplate 17.21 ms +3.49% -3.89% 18.31 ms +4.1% -1.04% -6.01%
wpTotal 45.26 ms +2.01% -3.12% 45.11 ms +4.06% -1.75% 0.33%
wpMemoryUsage 6.24 MB +0% -0% 6.26 MB +0% -0% -0.36%
wpDbQueries 14 +0% -0% 14 +0% -0% 0%

media-processing

Metric a50c228 trunk % Change
mediaProcessingJpeg 411.45 ms +1.57% -1.1% 404.94 ms +0.95% -1.1% 1.61%
mediaProcessingAvif 6110.9 ms +0.43% -0.31% 6077.48 ms +0.12% -0.17% 0.55%
mediaProcessingJpegToAvif 4227.58 ms +0.33% -0.5% 4203.07 ms +0.2% -0.12% 0.58%

media-upload

Metric a50c228 trunk % Change
jpegUploadProcessing 1451.97 ms +36.22% -1.39% 1429.3 ms +0.62% -1.3% 1.59%
pngUploadProcessing 210.69 ms +9.53% -13.34% 202.8 ms +10.56% -10.44% 3.89%
largeJpegUploadProcessing 1437.42 ms +0.4% -0.76% 1422.6 ms +0.64% -1.27% 1.04%
multipleImageUploadProcessing 1616.53 ms +10.11% -4.59% 1587 ms +10.57% -1.42% 1.86%

post-editor

Metric a50c228 trunk % Change
serverResponse 422.73 ms +4.73% -5.58% 418 ms +3.76% -6.9% 1.13%
firstPaint 204.57 ms +7.96% -16.85% 207.56 ms +11.99% -7.31% -1.44%
domContentLoaded 1025.84 ms +2.29% -0.62% 1050.11 ms +1.16% -0.59% -2.31%
loaded 1027.08 ms +2.29% -0.61% 1051.32 ms +1.17% -0.57% -2.31%
firstContentfulPaint 445.11 ms +1.34% -2.7% 460.53 ms +3.6% -0.89% -3.35%
firstBlock 2979.96 ms +1.37% -1.07% 2971.41 ms +1.37% -0.57% 0.29%
type 18.41 ms +2.23% -5.98% 18.33 ms +6% -1.96% 0.44%
typeWithoutInspector 17.41 ms +1.55% -0.63% 18.5 ms +1.35% -4.05% -5.89%
typeWithTopToolbar 22.96 ms +5.84% -2.66% 24.26 ms +5.73% -5.36% -5.36%
typeContainer 8.75 ms +2.74% -3.54% 8.65 ms +0.92% -5.55% 1.16%
focus 113.91 ms +6.79% -7.22% 108.28 ms +2.84% -4.93% 5.2%
firstFocus 209.68 ms +0% -0% 200.11 ms +0% -0% 4.78%
selectAll 451.75 ms +1.42% -0.62% 423.31 ms +6.42% -0.88% 6.72%
listViewOpen 73.12 ms +8.59% -3.23% 70.38 ms +2.53% -8% 3.89%
inserterOpen 39.33 ms +5.92% -5.9% 40.84 ms +10.68% -13.32% -3.7%
inserterHover 13.3 ms +3.68% -5.94% 11.78 ms +6.54% -5.26% 12.9%
inserterSearch 9.26 ms +4.54% -7.67% 9.14 ms +2.95% -12.04% 1.31%
loadPatterns 677.55 ms +2.05% -1.74% 649.48 ms +0.53% -2.47% 4.32%
wpTotal 412.81 ms +4.37% -5.85% 407.56 ms +3.85% -7.03% 1.29%
wpMemoryUsage 13.30 MB +0% -0% 13.21 MB +0% -0% 0.69%
wpDbQueries 54 +0% -1.85% 53.5 +0.93% -0.93% 0.93%

site-editor

Metric a50c228 trunk % Change
serverResponse 404.11 ms +3.88% -3.36% 412.48 ms +1.75% -4.36% -2.03%
firstPaint 245.54 ms +6.84% -6.12% 230.09 ms +41.27% -3.63% 6.71%
domContentLoaded 1071.72 ms +0.91% -0.96% 1075.06 ms +1.24% -1.59% -0.31%
loaded 1072.72 ms +0.92% -0.96% 1076.36 ms +1.21% -1.6% -0.34%
firstContentfulPaint 441.74 ms +4.52% -2.97% 451.55 ms +1.27% -2.84% -2.17%
firstBlock 4002.56 ms +0.96% -1.08% 3973 ms +1.33% -1.08% 0.74%
type 18.12 ms +2.21% -4.36% 17.38 ms +9.09% -0.86% 4.26%
navigate 119.45 ms +15.06% -0.59% 112.25 ms +6.94% -2.81% 6.41%
loadPatterns 1252.93 ms +3.71% -7.79% 1176.03 ms +2.35% -2.47% 6.54%
loadPages 1002.46 ms +2.32% -1.79% 1009.12 ms +5.89% -3.73% -0.66%
wpTotal 394.26 ms +3.99% -3.58% 402.17 ms +1.95% -4.28% -1.97%
wpMemoryUsage 12.33 MB +0% -0% 12.25 MB +0% -0% 0.58%
wpDbQueries 43 +2.33% -0% 43 +2.33% -0% 0%

48923db Run

🏁 Flaky tests

Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

refuses the drop and uploads nothing in /test/e2e/specs/editor/various/single-file-placeholder-drop.spec.js, passed after 1 failed attempt.
Error: expect(received).toHaveLength(expected)

Expected length: 0
Received length: 2
Received array:  [{"_links": {"about": [{"href": "http://localhost:8889/wp-json/wp/v2/types/attachment"}], "author": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/users/1"}], "collection": [{"href": "http://localhost:8889/wp-json/wp/v2/media"}], "curies": [{"href": "https://api.w.org/{rel}", "name": "wp", "templated": true}], "replies": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/comments?post=264"}], "self": [{"href": "http://localhost:8889/wp-json/wp/v2/media/264", "targetHints": {"allow": ["GET", "POST", "PUT", "PATCH", "DELETE"]}}], "wp:attached-to": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/posts/262", "id": 262, "post_type": "post"}]}, "alt_text": "", "author": 1, "caption": {"rendered": ""}, "class_list": ["post-264", "attachment", "type-attachment", "status-inherit", "hentry", "entry"], "comment_status": "open", "date": "2026-10-09T11:20:16", "date_gmt": "2026-10-09T11:20:16", "description": {"rendered": "<p class=\"attachment\"><a href='http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK-1.png'><img loading=\"lazy\" decoding=\"async\" width=\"10\" height=\"10\" src=\"http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK-1.png\" class=\"attachment-medium size-medium\" alt=\"\" style=\"width:100%;height:100%;max-width:10px;\" /></a></p>
"}, "featured_media": 0, "filename": "10x10_e2e_test_image_z9T8jK-1.png", "filesize": 80, "guid": {"rendered": "http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK-1.png"}, "id": 264, "link": "http://localhost:8889/?attachment_id=264", "media_details": {"file": "2026/10/10x10_e2e_test_image_z9T8jK-1.png", "filesize": 80, "height": 10, "image_meta": {"alt": "", "aperture": "0", "camera": "", "caption": "", "copyright": "", "created_timestamp": "0", "credit": "", "focal_length": "0", "iso": "0", "keywords": [], "orientation": "0", "shutter_speed": "0", "title": ""}, "sizes": {}, "width": 10}, "media_type": "image", "meta": [], "mime_type": "image/png", "modified": "2026-10-09T11:20:16", "modified_gmt": "2026-10-09T11:20:16", "ping_status": "closed", "post": 262, "slug": "10x10_e2e_test_image_z9t8jk-2", "source_url": "http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK-1.png", "status": "inherit", "template": "", "title": {"rendered": "10x10_e2e_test_image_z9T8jK"}, "type": "attachment"}, {"_links": {"about": [{"href": "http://localhost:8889/wp-json/wp/v2/types/attachment"}], "author": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/users/1"}], "collection": [{"href": "http://localhost:8889/wp-json/wp/v2/media"}], "curies": [{"href": "https://api.w.org/{rel}", "name": "wp", "templated": true}], "replies": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/comments?post=263"}], "self": [{"href": "http://localhost:8889/wp-json/wp/v2/media/263", "targetHints": {"allow": ["GET", "POST", "PUT", "PATCH", "DELETE"]}}], "wp:attached-to": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/posts/262", "id": 262, "post_type": "post"}]}, "alt_text": "", "author": 1, "caption": {"rendered": ""}, "class_list": ["post-263", "attachment", "type-attachment", "status-inherit", "hentry", "entry"], "comment_status": "open", "date": "2026-10-09T11:20:16", "date_gmt": "2026-10-09T11:20:16", "description": {"rendered": "<p class=\"attachment\"><a href='http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK.png'><img loading=\"lazy\" decoding=\"async\" width=\"10\" height=\"10\" src=\"http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK.png\" class=\"attachment-medium size-medium\" alt=\"\" style=\"width:100%;height:100%;max-width:10px;\" /></a></p>
"}, "featured_media": 0, "filename": "10x10_e2e_test_image_z9T8jK.png", "filesize": 80, "guid": {"rendered": "http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK.png"}, "id": 263, "link": "http://localhost:8889/?attachment_id=263", "media_details": {"file": "2026/10/10x10_e2e_test_image_z9T8jK.png", "filesize": 80, "height": 10, "image_meta": {"alt": "", "aperture": "0", "camera": "", "caption": "", "copyright": "", "created_timestamp": "0", "credit": "", "focal_length": "0", "iso": "0", "keywords": [], "orientation": "0", "shutter_speed": "0", "title": ""}, "sizes": {}, "width": 10}, "media_type": "image", "meta": [], "mime_type": "image/png", "modified": "2026-10-09T11:20:16", "modified_gmt": "2026-10-09T11:20:16", "ping_status": "closed", "post": 262, "slug": "10x10_e2e_test_image_z9t8jk", "source_url": "http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK.png", "status": "inherit", "template": "", "title": {"rendered": "10x10_e2e_test_image_z9T8jK"}, "type": "attachment"}]
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/single-file-placeholder-drop.spec.js:71:45

48923db Run

@sirreal
sirreal force-pushed the fix/cache-merged-theme-json-data branch 2 times, most recently from 320381f to 39122cd Compare October 6, 2026 06:52
@tellthemachines

Copy link
Copy Markdown
Contributor

@sirreal I assume the draft status is because it hasn't been human reviewed yet; are you planning to do so? Happy to give it a review and test run once it's marked ready.

@sirreal

sirreal commented Oct 7, 2026 •

Copy link
Copy Markdown
Member Author

@tellthemachines Yep, this was waiting on my self-review and some numbers. I did some testing and here are the results.

In summary, This seems to outperform trunk. Keeping the accessor cache adds a very small improvement that doesn't seem worth it.

I'll perform self-review and mark as ready, then I'd love feedback or land if/when you're happy with it.

Full LLM-driven perf measurements

Method. Base 4791f174f20, WP_DEBUG off (it bypasses the accessor cache), get_merged_data() wrapped with a timer and call counter. Table 1: curl, 21 requests per page, first dropped, median, WordPress trunk. Table 2: npm run perf, 3 rounds of 16 samples, WordPress 7.1.3. Compare columns within a table only.

Time in full merges per request.

Page trunk: merges trunk this PR PR + accessor cache
twentytwentythree front page 7 3.59 ms 2.03 ms 1.97 ms
twentytwentyfour front page 24 6.61 ms 1.92 ms 1.88 ms
single post, large-post.html fixture 8 3.64 ms 1.96 ms 1.85 ms
post with 52 blocks carrying an is-style-* class 58 12.92 ms 1.92 ms 1.90 ms

About 1.9 ms is the first merge per origin and remains in every column.

Callers that still merge on trunk. The accessor cache serves only gutenberg_get_global_styles(). The rest: gutenberg_render_block_style_variation_support_styles() once per is-style-* block (1, 18, 2 and 52 above); gutenberg_get_global_stylesheet() twice; gutenberg_add_global_styles_for_blocks(), WP_Duotone_Gutenberg::get_all_global_style_block_names(), gutenberg_get_global_settings() and the first layout call once each.

REST posts?context=edit. Content is rendered three times per post, so each is-style-* block costs three merges on trunk. Three posts listed, one the 52-block post:

merges merge time wpTotal
trunk 159 31.7 ms 148 ms
this PR 471 (memoized) 2.3 ms 119 ms
PR + accessor cache 159 2.1 ms 119 ms

The editor perf suites miss this: their fixtures have no is-style-* blocks. A blog index listing that post behaves the same (56 merges, 11.9 ms on trunk, 2.0 ms here).

Trunk with #81889 reverted measures the same as trunk on every page: equal merge counts, merge time within 0.2 ms. The layout static served the same calls the accessor cache does.

Request level.

Suite Metric trunk this PR PR + accessor cache
front-end-block-theme wpTemplate 15.73 ms 13.49 ms 13.62 ms
front-end-block-theme wpTotal 54.45 ms 52.31 ms 53.17 ms
front-end-classic-theme wpTemplate 12.04 ms 10.86 ms 10.79 ms
front-end-classic-theme wpTotal 50.53 ms 49.62 ms 50.22 ms

Round spread is at most 0.6 ms on wpTemplate and 1.9 ms on wpTotal; the classic-suite wpTotal difference is inside it.

Accessor cache on top of the memo: no measurable difference. A hit saves one memoized call, 0.34 µs.

Memory. wpMemoryUsage fell 81 KB (block) and 69 KB (classic). Not investigated.

Not measured. global-styles/<id>, templates and template-parts make no plugin resolver calls; global-styles/themes/<stylesheet> makes one. The editor's single-post load, posts/<id>?context=edit, was not run on its own. Scripts and raw results on request.

@sirreal
sirreal force-pushed the fix/cache-merged-theme-json-data branch from c964129 to 6440fcc Compare October 7, 2026 12:00
Comment thread lib/class-wp-theme-json-resolver-gutenberg.php Outdated
@sirreal
sirreal marked this pull request as ready for review October 7, 2026 12:46
sirreal added a commit to sirreal/wordpress-develop that referenced this pull request Oct 7, 2026
… old cache keys.

Match the Gutenberg test from WordPress/gutenberg#81979: the key assertions forbid one past implementation by name and fail only if those exact keys return. Keep the assertion that the accessor reflects resolver data refreshed after a previous call.

@tellthemachines tellthemachines left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! The code LGTM and I couldn't find any issues in manual testing. I'm not an expert in this area though, so feel free to get a second opinion 😄

Looking at the perf numbers it makes a lot of sense to replace #81889 with this.

Just curious, any reason the tests in the first table were run on TT3 and TT4 but not TT5?

@sirreal

sirreal commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

any reason the tests in the first table were run on TT3 and TT4 but not TT5?

No conscious decision on my part to do that. I'm happy to run more numbers if we'd like.

@tellthemachines

Copy link
Copy Markdown
Contributor

I'm happy to run more numbers if we'd like.

I don't think it's necessary, TT4 has a ton of blocks on its front page so if it works there it should be good for the other themes 😄

@sirreal
sirreal requested a review from ramonjd October 8, 2026 05:48
@sirreal

sirreal commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

I'm not an expert in this area

Me neither unfortunately 😅

I'll leave this open for another day or so in case other folks can have a look.

sirreal added 20 commits October 9, 2026 15:01
get_merged_data() stored the merged data on the first call but nothing
recorded the registered blocks for it, so the guard on the second call
found an empty record, filled it, and rebuilt. The first hit was the
third call.

Run has_same_registered_blocks() before the isset() so the first call
records the blocks while it builds, and the second call hits.

The guard tracks block registration only. get_theme_data() reads the
theme supports on every call, so a support added after the first merge
stays out of the merged data until the blocks change or the cache is
cleaned. Say so in the comment.

Add a test that the second call returns the memoized data, through
reflection on the merged property; it fails when the merged_* keys are
missing from the block record. Warm the late-registration test with two
calls so the guard runs against a seeded record.
test_clean_cached_data_refreshes_merged_data never called
clean_cached_data(); it tested the switch_theme hook. Keep that as
test_switching_theme_refreshes_merged_data, and have the clean test
plant a sentinel, clean, and assert that the next call rebuilds.

The mutation test switched to twentytwentyfour, which only the wp-env
test config provides, from a zip. Use the in-repo fonts-block-theme
fixture: its root font family references a font family preset, which
resolve_variables() resolves. The duotone reference in block-theme does
not resolve, because duotone presets emit no CSS variables.
#81889 added an object cache in front of the resolver. Nothing reads its
resolve-variables keys: no caller in the plugin or in core passes that
transform. A plain hit saves one clone and one array read over the
memoized resolver. The cache sits in front of
has_same_registered_blocks(), so a block registered after the first
call stays invisible to this accessor for the rest of the request while
every other consumer sees it, and WP_DEBUG bypasses it.

Keep the layout static removal from #81889.

Add a test that the accessor reflects a block registered after a
previous call. The test suite runs with WP_DEBUG on, which bypassed the
removed cache, so the test only fails against it with WP_DEBUG off.
block_core_gallery_render() held the global styles in a function static
and read them through core's wp_get_global_styles(), which uses core's
resolver, not the plugin's. A gap set in global styles after the first
gallery rendered stayed invisible for the rest of the request, and the
gallery saw different theme.json data than the layout support.

Prefer gutenberg_get_global_styles() when it exists, the way the file
already prefers gutenberg_resolve_style_state_aliases(). In the plugin
that reads the memoized plugin resolver. In core the first branch is
dead and wp_get_global_styles() runs, which becomes cheap once the
resolver change syncs.

The static came from #80030 as a workaround for the uncached merge.
The suite defines WP_DEBUG true in phpunit/bootstrap.php, and the cache
this PR removes recomputed under WP_DEBUG, so the late-registration
assertion passed against that cache. The test guarded nothing in CI.

The cache was written on every call whatever WP_DEBUG said. Assert its
four keys stay empty after the accessor runs, so putting the cache back
fails the test under the settings CI uses. Renamed for what it now
checks: the accessor reads through to the resolver.
The mutation isolation test switched to the fonts-block-theme fixture,
which #82813 removed from phpunit/data/themedir1. Add a font family
preset and a root font family that references it through the
wp_theme_json_data_theme filter instead, so the test depends on no
bundled theme. It still fails when the clone is removed from
get_merged_data().
The block-library build lists wp_get_global_styles() in prefixFunctions,
so the plugin build already rewrites the call to
gutenberg_get_global_styles(). The IS_GUTENBERG_PLUGIN branch added in
1ece376 selected the same function in the plugin and was dead in
core. Remove it and keep the per-render read.
Shorten the docblocks above the cache lookup, the clone helper and the
accessor test to the reason each exists, list the merged_* keys that
has_same_registered_blocks() accepts, and drop the "asserts nothing"
commentary from the precondition assertions.

Run the reuse and clearing tests over all four origins; removing one
merged_* key from the registered-blocks record now fails the theme
case. Assert a block-theme palette slug across the theme switch instead
of comparing whole trees.
WordPress Core has no cache behind wp_get_global_styles() or the
resolver, so removing the static would run a full theme.json merge per
gallery render in Core once the package syncs. Keep it, explain why, and
drop the changelog entry and the test that asserted the removal.
All merged origins depend on block registration identically, so one
'merged' entry in $blocks_cache suffices. Clear the whole merged array
when new blocks appear, and reset $blocks_cache from its own keys in
clean_cached_data() so the two lists cannot drift.
The resolver now caches the merged data, so this cache saved one array
read per call and its WP_DEBUG bypass re-read the same cached object.
Read through to the resolver on every call, as the styles accessor does,
and cover the removal with the same read-through test.
The cache_merged_data() helper wrapped each early return to store the
result and return a clone. Merge each origin behind a condition instead,
so the method stores the result once and clones once, and the helper,
its docblock and its visibility question go away. The helper was added
in this branch and never shipped.
The file syncs into Core, where a comment naming WordPress 7.2 and
promising removal after it would describe Core to itself. State the
fact: the static avoids a merge per gallery in Core until the resolver
cache is backported.
Match the docblocks in wordpress-develop#14071 so the port stays
mechanical: the merged entry in $blocks_cache, the $merged property,
the 'merged' origin of has_same_registered_blocks(), the cache and
returned copy in get_merged_data(), and the reset in clean_cached_data().
The two accessor tests asserted that six removed wp_cache keys stay
absent. That forbids one past implementation by key name and fails only
if those exact keys return. Keep the assertions that the accessors
reflect a block registered, or theme data changed, after a previous
call, and name the tests after that behavior.
This PR removes the accessor cache #81889 added, and Core's layout
support never had the static it removed, so wordpress-develop#13214 has
nothing left to port. List #81889 under #14071, which supersedes it,
and drop the 13214 entry.
The test suite defines WP_DEBUG, which bypassed the removed accessor
cache, so the test passes with or without that cache and cannot show
its absence. Name it, like the styles test, for what it checks.
@sirreal
sirreal force-pushed the fix/cache-merged-theme-json-data branch from 637f599 to 48923db Compare October 9, 2026 11:01
@sirreal
sirreal enabled auto-merge (squash) October 9, 2026 11:03
sirreal added a commit to sirreal/wordpress-develop that referenced this pull request Oct 9, 2026
Match the test name and assertion message from WordPress/gutenberg#81979.
The Gutenberg suite defines WP_DEBUG, which bypassed the plugin's removed
cache, so the test there cannot show that cache's absence. Core's removed
cache was bypassed only in theme development mode, so this test still
fails against it.
@sirreal
sirreal merged commit d5683a8 into trunk Oct 9, 2026
73 checks passed
@sirreal
sirreal deleted the fix/cache-merged-theme-json-data branch October 9, 2026 11:28
@github-actions github-actions Bot added this to the Gutenberg 24.3 milestone Oct 9, 2026
sirreal added a commit to sirreal/wordpress-develop that referenced this pull request Oct 14, 2026
…tion.

Port the styles accessor test from WordPress/gutenberg#81979. The accessor reads the resolver's current data, so a block registered after a previous call appears in the next result.
wporg-sync pushed a commit to WordPress/wordpress-develop that referenced this pull request Oct 15, 2026
`WP_Theme_JSON_Resolver::get_merged_data()` merged all origins on every call, including once for each rendered block with an `is-style-*` class. Cache the result until a block is registered or `wp_clean_theme_json_cache()` is called, and remove the redundant cache in `wp_get_global_settings()`. Theme support changes made after the first merge require `wp_clean_theme_json_cache()`.

Backports WordPress/gutenberg#81979.

Developed in: #14071

Props jonsurrell, peterwilsoncc, isabel_brison, ramonopoly.
Fixes #66280. See #62249.


git-svn-id: https://develop.svn.wordpress.org/trunk@64354 602fd350-edb4-49c9-b593-d223f7449a82
wporg-sync pushed a commit to WordPress/WordPress that referenced this pull request Oct 15, 2026
`WP_Theme_JSON_Resolver::get_merged_data()` merged all origins on every call, including once for each rendered block with an `is-style-*` class. Cache the result until a block is registered or `wp_clean_theme_json_cache()` is called, and remove the redundant cache in `wp_get_global_settings()`. Theme support changes made after the first merge require `wp_clean_theme_json_cache()`.

Backports WordPress/gutenberg#81979.

Developed in: WordPress/wordpress-develop#14071

Props jonsurrell, peterwilsoncc, isabel_brison, ramonopoly.
Fixes #66280. See #62249.

Built from https://develop.svn.wordpress.org/trunk@64354


git-svn-id: http://core.svn.wordpress.org/trunk@63504 1a063a9b-81f0-0310-95a4-ce76da25c4cd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Block library /packages/block-library [Type] Performance Related to performance efforts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants