Skip to content

Reduce jQuery usage: vanilla leaf modules + opt-in Interactivity API front-end forms#452

Draft
cbravobernal wants to merge 4 commits into
perf/runtime-and-asset-optimizationsfrom
feat/jquery-reduction-interactivity
Draft

Reduce jQuery usage: vanilla leaf modules + opt-in Interactivity API front-end forms#452
cbravobernal wants to merge 4 commits into
perf/runtime-and-asset-optimizationsfrom
feat/jquery-reduction-interactivity

Conversation

@cbravobernal

Copy link
Copy Markdown
Contributor

Stacked on #451 (performance) which stacks on #450 (test battery) — merge those first; this branch contains both.

This PR delivers the first two executable phases of a jQuery-reduction roadmap, plus the roadmap itself as a maintained document. The guiding principle, established by a module-by-module audit: the Interactivity API realistically applies to SCF's one interactive front-end surface (acf_form()); admin code drops jQuery incrementally behind the stable acf.* API; library-bound modules (wp.media, TinyMCE, wpLink) stay as-is until core moves.

1. Migration plan (docs/contributing/jquery-migration-plan.md)

Five phases with rationale, risk, ecosystem-compat strategy, and test gates per phase: vanilla-internal leaf conversions (this PR) → vanilla event delegation inside acf.Model preserving the (e, $el) API → Interactivity API front-end forms (this PR) → SortableJS + native date/time inputs option → select2 → wp-components with a two-major deprecation of the acf/select2/* JS hooks.

2. Phase 1: nine leaf modules converted to vanilla DOM internals

_acf-unload and the url/range/radio/button-group/checkbox/true-false/oembed/link field models no longer use jQuery internally. The conversion is deliberately conservative:

  • events maps, $input()-style helper signatures, and method order are untouched — the public acf.Model API (which third-party add-ons consume) is unchanged; jQuery remains at the API boundary, vanilla inside.
  • jQuery .trigger('change') calls became native bubbled dispatchEvent only after verifying every listener (conditional logic, the fields event manager) catches native events; sites with jQuery-specific semantics (oembed's $.ajax abort contract, wpLink, beforeunload return-value handling) deliberately keep jQuery, each with an in-code comment.
  • Jest tests that asserted jQuery-mock internals were rewritten 1:1 (same names, same counts) to drive real jsdom DOM and assert observable state — which exposed one pre-existing false-positive test in url.test.js that could never fail.

3. Phase 3: opt-in Interactivity API bundle for front-end forms

New scf/form Interactivity store (3.6 KB module, @wordpress/interactivity externalized via the standard dependency-extraction module build):

  • Opt-in: frontend_interactivity_form setting (default false). With the flag off, behavior is byte-identical to today.
  • Eligibility guard (server-side, decided at render): the slim bundle serves only forms whose fields are all simple types (text, textarea, number, email, url, password, range, non-ui select, checkbox, radio, button group, true/false) on WP ≥ 6.5; any complex field (date picker, gallery, repeater, wysiwyg…) automatically falls back to the classic stack. Mixed pages are safe in both orders.
  • Validation contract preserved: native constraint validation as a fast path (rendered in the exact classic error markup/classes), then the same acf/validate_save_post admin-ajax endpoint (custom acf/validate_value filters stay authoritative — investigation showed form-front wp_die()s on server validation, so client pre-validation is required, exactly as the classic stack does), then a native POST through the untouched form-front.php save pipeline.
  • Proof: the new e2e spec asserts, in a logged-out context, script[src*="jquery"] count = 0 and no acf-input on the page, while the form renders via data-wp-interactive, blocks on required fields, clears errors on input, and persists values. A complex-field form on the same flag correctly falls back to classic and still works.

New code is SCF-owned (scf_ prefixed, includes/forms/form-front-view.php); shared-file changes total +21 flag-gated lines in form-front.php.

Verification

  • composer test:php: OK (2802 tests, 6435 assertions — includes 14 new)
  • npm run test:unit: 967/967 (21 new)
  • composer test:phpstan: clean
  • Full Playwright suite incl. the new front-end-form.spec.ts (5 tests): passing (see checks)
  • Conversion canaries: conditional-logic (9) + validation (15) e2e specs green — these exercise the event-bubbling paths the Phase-1 conversion touches
  • phpcs clean on changed lines; wp-scripts lint-js and lint:md clean on new files

Known limitations (documented in the plan)

The slim bundle intentionally omits the acf.* JS hook surface and jQuery change triggers — integrations that need them keep the flag off. The form-level error summary is non-dismissible. Phases 2, 4, and 5 are scoped in the plan document but not implemented here.

Closes

Use of AI Tools

This PR was authored by Claude Code (Claude Fable 5) under human direction. The migration strategy, module classifications, and scope decisions were derived from a measured audit; all changes were validated by the full test battery (PHPUnit, PHPStan, Jest, Playwright) including a purpose-built e2e spec proving the jQuery-free path.

…forms

Adds docs/contributing/jquery-migration-plan.md (five-phase roadmap with
ecosystem-compat strategy) and executes phases 1 and 3:

Phase 1 — nine leaf modules (unload + url/range/radio/button-group/
checkbox/true-false/oembed/link fields) converted to vanilla DOM
internals. acf.Model events maps, helper signatures, and the public
acf.* API are unchanged; jQuery stays at API boundaries and where
semantics require it (oembed $.ajax abort, wpLink, beforeunload).
jQuery-mock Jest tests rewritten 1:1 against real jsdom DOM.

Phase 3 — opt-in @wordpress/interactivity bundle for acf_form() pages
(frontend_interactivity_form setting, default off; flag-off behavior
byte-identical). Server-side eligibility: simple-field forms on WP 6.5+
get the 3.6KB scf/form module with zero jQuery; complex fields fall
back to the classic stack automatically. Client validation mirrors the
classic contract (native constraints + acf/validate_save_post ajax),
then a native POST through the untouched form-front save pipeline.
New e2e spec proves the jQuery-free path logged-out, including the
classic fallback.

Verified: 2802 PHPUnit / 967 Jest / 258 E2E green, PHPStan clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

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 props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props cbravobernal.

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

@cbravobernal cbravobernal marked this pull request as draft June 12, 2026 11:36
@cbravobernal

Copy link
Copy Markdown
Contributor Author

Stalled until 2026-06-15 (Mon) — do not merge before then. Branch updated: merged the current perf branch (and trunk) in; the esc_like option-meta security fix from #462 is preserved. Stacked on the perf PR (#466).

@cbravobernal cbravobernal added this to the 6.9.0 milestone Jun 12, 2026
@cbravobernal cbravobernal added [Type] Enhancement New feature or request javascript Pull requests that update javascript code labels Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update javascript code [Type] Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant