Repository navigation
Interactivity: prototype data-wp-preserve directive - #80303
Closed
fabiankaegy wants to merge 1 commit into
Closed
fabiankaegy wants to merge 1 commit into
fabiankaegy wants to merge 1 commit into
Conversation
Adds an opt-in `data-wp-preserve` directive that keeps a live element (matched by its `id`) intact across client-side navigations instead of letting the router diff or replace it. It's meant for wrapper elements a site owner controls around DOM that third-party scripts inject at runtime (consent managers, chat widgets, embeds), which the router's vDOM diffing can't see and otherwise clobbers or removes on navigation. This is a discussion prototype for #80298, the proposed successor to the deprecated data-wp-ignore directive. Unlike data-wp-ignore, which freezes the initial server HTML string and can lose injected DOM after navigating away and back, this keeps a registry of the actual live DOM nodes so the real subtree (including anything injected into it) survives even when the component instance backing it isn't reused across a navigation. Part of #80298 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Size Change: +99 B (0%) Total Size: 7.72 MB 📦 View Changed
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
This is a prototype PR, meant to make the discussion in #80298 concrete rather than to be merged as is. It adds an opt-in
data-wp-preservedirective to the Interactivity API. When an element in the newly fetched page hasdata-wp-preserveand anid, and an element with the same directive andidalready exists in the current live DOM, the router keeps that live element (and its whole subtree) as is during client-side navigation, instead of diffing it or replacing it with the server-rendered version.Part of #80298
Why?
data-wp-ignorewas deprecated with no replacement (#70881), and nobody following up on that issue got a working alternative. The underlying problem is bigger thandata-wp-ignoreitself: third-party scripts that aren't built on the Interactivity API (consent managers like OneTrust, chat widgets, various embeds) inject DOM into the page at runtime. The router's vDOM diffing doesn't know about that DOM, so it either removes it or clobbers it on the next navigation. The docs already warn about this with no real solution offered.Other client-side routers already solve this exact problem: Turbo has
data-turbo-permanent, htmx hashx-preserve. Both are for the same category of third-party DOM the Interactivity API router struggles with.How?
data-wp-preservegoes on a wrapper element that the site owner controls (a theme or block renders it), since a third-party script will never add a WordPress-specific attribute to the markup it injects itself.The implementation deliberately avoids the bugs that got
data-wp-ignoredeprecated (context inheritance breakage, incorrect behavior across navigation). Instead of freezing the initial server HTML string in auseMemo, it keeps a module-level registry mapping each preserved element'sidto its actual live DOM node. On every render:data-wp-ignoreavoids touching the DOM when nothing changed).refcallback (deliberately a fresh function every render, so Preact invokes it on every render rather than only on mount/unmount) checks whether a different DOM node was already preserved for thisid. If so, it means this component instance couldn't be reused across the navigation (e.g. the surrounding markup changed enough that Preact recreated it), so it moves the previously preserved live children over onto the freshly created node, overriding whatever server HTML was just applied to it.This means the actual DOM nodes (and anything injected into them after the page loaded) survive navigation, rather than a stringified snapshot of
innerHTMLthat would lose any live state inside the preserved subtree (video playback position, focus, third-party JS instances, etc).An element without an
idcan't be matched across navigations, so the directive is ignored with aSCRIPT_DEBUGwarning in that case, falling back to normal processing.Known limitations (documented in the docs update)
data-wp-router-regionplaced inside adata-wp-preserveelement won't update during navigation. This is called out in the docs as a limitation rather than solved here.data-wp-preserveare not processed, the same restrictiondata-wp-ignorehas today. Put them on a parent element instead.Testing Instructions
wp-env.test/directive-preserveblock, which renders adata-wp-preservewrapper inside a router region.id.divand the original content are both still there, untouched by the navigation.Alternatively, run the new e2e spec:
npm run test:e2e -- test/e2e/specs/interactivity/directive-preserve.spec.ts(requireswp-env-test; not run in this environment, CI will run it).Testing Instructions for Keyboard
Not applicable, this PR has no user-facing UI.
Use of AI Tools
This PR was implemented by Claude Code (Claude Fable 5), based on the proposal filed in #80298 by @fabiankaegy. The implementation approach (module-level live-DOM registry instead of freezing an HTML string, informed by the known bugs in
data-wp-ignore) was researched and written by the AI agent, and reviewed before opening this PR.🤖 Generated with Claude Code