Skip to content

Performance: 20x fewer field-lookup queries, screen-gated heavy assets, 4MB lighter zip#466

Closed
cbravobernal wants to merge 11 commits into
trunkfrom
perf/runtime-and-asset-optimizations
Closed

Performance: 20x fewer field-lookup queries, screen-gated heavy assets, 4MB lighter zip#466
cbravobernal wants to merge 11 commits into
trunkfrom
perf/runtime-and-asset-optimizations

Conversation

@cbravobernal

@cbravobernal cbravobernal commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Superseded by #471 (safe bundle) + #472 (sibling-field priming). Split per review so the lower-risk changes can merge without waiting on multilingual testing of the priming. This PR can be closed once the two successors are open for review.

Implements the highest-impact, lowest-risk items from a measured performance audit. All changes are behavior-identical: the full battery (PHPUnit / Jest / E2E / PHPStan) passes without modifying a test.

PHP runtime

  • Prime sibling field definitions when a field key resolves (includes/acf-field-functions.php): get_field() over a 50-field template drops from ~101 SQL queries / ~17ms to ~5 / ~2.8ms on cache-less hosts. The priming query runs with suppress_filters => false, matching the per-key lookup it replaces, so per-language/context query filters (WPML, Polylang, ACFML) still run — no hook is bypassed. Opt-out via acf/prime_field_group_fields.
  • Decode each local JSON file once per request instead of 4× (includes/local-json.php): ~44ms → ~16ms at 200 files.
  • Per-request cache for acf_get_option_meta() with invalidation via core option hooks. (Note: this PR keeps the esc_like() escaping that landed on trunk in Use esc_like() when building option-meta LIKE patterns #462 — the cache wraps that hardened query, it does not revert it.)
  • acf_decode_taxonomy_term(): raw SQL → get_term().
  • Blocks save-path early bail when no block types are registered.
  • Gate the escaped-html notice script to when the notice renders.
  • Memoize the WP version check in acf_is_using_datastore().

Assets / enqueue

  • scf-bindings moved to enqueue_block_editor_assets (was pulling the wp-editor stack onto classic admin + front-end acf_form pages).
  • Command-palette scripts gated on wp-commands being enqueued.
  • Drop wp-polyfill from all handles (webpack injectPolyfill). Compiled assets are gitignored and rebuilt at release (bin/prepare-release.php), so this takes effect on the next release build.
  • Fix pro CSS min suffix (production was shipping unminified CSS).
  • Gate acf-pro-ui-options-page with is_admin().
  • Fix acf-dark stylesheet 404.
  • Trim release zip (exclude assets/src + source maps): −4.2 MB.

Verification (on this branch, merged up to current trunk)

  • composer test:php: OK (2823 tests, 21565 assertions). npm run test:unit: 951. composer test:phpstan: clean. Re-verified independently after the hook-safe priming rework.
  • The esc_like regression test from Use esc_like() when building option-meta LIKE patterns #462 passes here (security fix preserved through the merge).
  • New code is stamped @since SCF 6.9.0 (the targeted milestone; 6.8.9 already shipped without it).

Use of AI Tools

Initial implementation authored by Claude Code (Claude Fable 5) under human direction; reviewed and revised (hook-safe sibling priming, @since correction) by Claude Code (Claude Opus 4.8).

cbravobernal and others added 9 commits June 12, 2026 01:40
PHPUnit 2261 -> 2788 tests (+527, +2074 assertions) covering previously
untested flows: public template API (get_field/update_field/have_rows row
API), upgrade migrations, revision save/restore, local JSON/fields/meta,
Meta storage backends, ACF_Data, validation, options pages, abilities
(incl. schema-robustness repros), blocks PHP (registration/render/
bindings), AJAX handlers, Site Health, and misc functions.

Jest 772 -> 946 tests (+174) for the hooks system, core utilities,
serialization, the legacy compatibility layer, and unload warnings.

E2E: +4 tests (options page UI lifecycle, field group duplication, user
profile fields), a REST purge utility plugin for suite isolation, and
fixes for two order/timing-dependent specs (abilities-fields, url
preview race).

Known-behavior findings are documented in-test with NOTE comments and
left unfixed to keep this change purely additive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… screens

PHP runtime (measured in wp-env, cache-less host):
- Prime sibling field definitions when a field key resolves: get_field()
  across a 50-field template drops from 101 SQL queries / ~17ms to
  5 queries / ~2.8ms. Opt-out via acf/prime_field_group_fields filter;
  per-field load_field filters unchanged.
- Decode each local JSON file once per request instead of 4x:
  44ms -> 16ms at 200 files.
- Per-request cache for acf_get_option_meta() LIKE queries, invalidated
  via core added/updated/deleted_option hooks.
- acf_decode_taxonomy_term(): raw term_taxonomy SQL -> get_term().
- Early-bail block save parsing when no block types are registered.
- Enqueue the escaped-html notice script only when the notice renders.
- Memoize the WP version check in acf_is_using_datastore().

Assets:
- Load scf-bindings only in block editor contexts; it previously pulled
  the full wp-editor dependency stack onto classic admin pages and
  public front-end acf_form() pages.
- Gate command-palette scripts on wp-commands being enqueued (stops
  React + wp-components loading on every classic admin page on WP <=6.8).
- Drop wp-polyfill dependency from all bundles (webpack injectPolyfill).
- Fix pro CSS min suffix: production shipped unminified acf-pro-input.css.
- Gate admin-only acf-pro-ui-options-page script with is_admin().
- Fix acf-dark stylesheet 404 (wrong path since the build move).
- Exclude assets/src and source maps from the release zip (-4.2MB).

Verified: 2788 PHPUnit / 946 Jest / 253 E2E tests green, PHPStan clean,
zero tests modified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PHP 7.4 raises E_NOTICE for undefined array keys (PHP 8 raises
E_WARNING) and the notice is not converted to a Throwable, so the
try/catch capture never fired. Capture via an explicit error handler
covering both error types instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review feedback: tests that assert possible-bug behavior as green
expectations now reference the GitHub issues tracking each finding
(#453-#461), so future fixes read as intentional, not regressions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…est.step structure

- scf-test-get-field-user.php reads ?scf_test_user_id (absint-validated),
  falling back to the queried author, removing the user_1 assumption;
  user-profile-fields.spec.ts derives the real admin ID via /users/me.
- Purge endpoint renamed to /purge-internal-posts to match what it
  deletes; single shared purgeScfInternalPosts() helper in
  field-helpers.js replaces four duplicated copies.
- Large lifecycle tests restructured with test.step() (duplication,
  options page, post-type integration flow) with small local helpers
  for repeated workflows; assertions unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seeded, CI-reproducible fuzz/property tests (SCF_FUZZ_ITERATIONS to crank
locally; failing seed + input embedded in assertion messages):

- PHP (tests/php/includes/fuzz/): post-id decoding, value round-trips,
  validation, the JSON schema validator, local JSON file ingestion,
  block.json registration, and escaping/sanitization helpers — including
  a security property that acf_maybe_unserialize() never instantiates
  real objects (verified: object-injection payloads decode only to
  __PHP_Incomplete_Class placeholders).
- JS (tests/js/fuzz/): acf.serialize, flexible-content normalization,
  and escaping utilities under a seeded PRNG.

Adds 27 PHPUnit + 5 Jest tests (~3s). Crashes surfaced on hostile input
are documented in-test with NOTE comments (generators constrained) and
reported for follow-up issues; no source was modified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…et-optimizations

# Conflicts:
#	tests/php/includes/fuzz/test-fuzz-schema-validator.php
cbravobernal and others added 2 commits June 16, 2026 10:51
The sibling-field priming loaded the group's fields via acf_get_raw_fields(),
which forces suppress_filters => true, so per-language/context query filters
(WPML, Polylang, ACFML) never ran for the primed siblings.

Replace it with a single get_posts() over the group's published fields using
suppress_filters => false, matching the per-key lookup in acf_get_field_post()
that it primes. Third-party query filters still run, and the per-group
single-query win is preserved. The acf/prime_field_group_fields filter remains
as a plain on/off opt-out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SCF 6.8.9 already shipped without these additions; the performance work
targets the 6.9.0 milestone. Bump the @SInCE tags on the new functions,
filters and methods from 6.8.9 to 6.9.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cbravobernal

Copy link
Copy Markdown
Contributor Author

Superseded by #471 (safe perf/asset bundle) and #472 (hook-safe sibling-field priming), which split this PR by risk so the lower-risk changes can land without waiting on multilingual testing of the priming. Closing in favor of those two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant