Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guard [data-turbo-preload] with conditionals #1033

Merged
merged 1 commit into from
Dec 8, 2023

Conversation

seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented Oct 12, 2023

Closes #857

Prior to this change, any <a> element with [data-turbo-preload] would be preloaded. With that behavior comes some risk:

  • if an element is nested within a [data-turbo="false"] element, the preloading would occur unnecessarily
  • if an element has [data-turbo-stream], the preloaded request won't be the same as the ensuing request due to differences in the Accept: header
  • if an element has [data-turbo-method], the preloaded request won't be the same as the ensuing request due to differences in the method
  • if an element is within a <turbo-frame> or driving a frame via [data-turbo-frame], the preloaded request won't be the same as the ensuing request due to differences in the Turbo-Frame: header

This commit extends the Preloader delegate interface to include a shouldPreloadLink(link: HTMLAnchorElement) predicate method to give delegates an opportunity to opt-out of preloading. The Session implementation of the delegate class adds rejects <a> elements that match any of those cases.

@seanpdoyle seanpdoyle force-pushed the preloader-guards branch 2 times, most recently from 7c9cb74 to 279bbfc Compare October 12, 2023 16:16
seanpdoyle added a commit to seanpdoyle/turbo-site that referenced this pull request Oct 12, 2023
Related to [@hotwired/turbo#1033][]

Document the cases when `[data-turbo-preload]` has no effect.

[@hotwired/turbo#1033]: hotwired/turbo#1033
@seanpdoyle
Copy link
Contributor Author

I've opened hotwired/turbo-site#150 to make these edge cases more obvious.

Prior to this change, _any_ `<a>` element with `[data-turbo-preload]`
would be preloaded. With that behavior comes some risk:

* if an element is nested within a `[data-turbo="false"]` element, the
  preloading would occur unnecessarily
* if an element has `[data-turbo-stream]`, the preloaded request won't
  be the same as the ensuing request due to differences in the `Accept:`
  header
* if an element has `[data-turbo-method]`, the preloaded request won't
  be the same as the ensuing request due to differences in the method
* if an element is within a `<turbo-frame>` or driving a frame via
  `[data-turbo-frame]`, the preloaded request won't be the same as the
  ensuing request due to differences in the `Turbo-Frame:` header

This commit extends the `Preloader` delegate interface to include a
`shouldPreloadLink(link: HTMLAnchorElement)` predicate method to give
delegates an opportunity to opt-out of preloading. The `Session`
implementation of the delegate class adds rejects `<a>` elements that
match any of those cases.

Co-authored-by: Julian Rubisch <[email protected]>
@seanpdoyle
Copy link
Contributor Author

@afcapel @jorgemanrubia this is ready for review, if either of you are available.

@afcapel afcapel merged commit 098aafc into hotwired:main Dec 8, 2023
1 check passed
@afcapel
Copy link
Collaborator

afcapel commented Dec 8, 2023

Thanks @seanpdoyle, nice one!

@adambutler
Copy link

adambutler commented Jan 8, 2024

@seanpdoyle I'm trying to achieve some preloading behaviour that appears to be impossible with the changes made here, specifically:

const frame = frameTarget == "_top" ?
  null :
  document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");

My understanding is:

  1. If the frameTarget is "_top" then frame is set to an explicit null
  2. Otherwise; Attempt to find the element by the data-turbo-frame attribute.
  3. Otherwise; Get the closest <turbo-frame>

Then if it's present and a kind of FrameElement a prefetch will be prevented.

In my example, I have tabbed content within a page containing other content. I wish for users to be able to click the tabs and for the content to be shown instantly since it has been prefetched. See my diagram for more information:

CleanShot 2024-01-08 at 14 16 04

If I set my data-turbo-frame="_top" this causes it to work as expected but does mean that I'm loading and replacing the whole document rather than just the frame I'd like.

In Turbo v7.3.0:

  • The fetch for the prefetch pages performs as expected
  • However, upon clicking the link the cache is not used.
  • Next time the same link is used within in the same page visit the cache is used.

However in Turbo v8.0.0-beta.2 I believe the issue of the prefrech populating the cache to be used upon click has been solved by some other PR all that remains is fixing an issue that this PR has seemed to introduce where the prefetch is not invoked due to the guard introduced.

My suggestion might be nieve as I'm not completely clear on the why this condition was added. I was thinking though that:

  1. If the frameTarget is "_top:" then it continues to work
  2. If the frameTarget is not present it allows it to perform the prefetch (a change in behaviour)
  3. If the frameTarget is present
    a. If it matches the closest turbo frame it allows it to perform the prefetch (a change in behaviour)
    b. Otherwise the prefetch is prevented

The code change in src/core/session.js required would be as such:

    const frame = frameTarget == "_top" ?
      null :
-      document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
+      document.getElementById(frameTarget);
+    
+    if (frame && frame !== findClosestRecursively(element, "turbo-frame:not([disabled])")) {
+      return false;
+    }

I'd appreciate if you could let me know what you think and I'd he happy to submit a PR if you think this change makes sense or if there is a different way of solving my problem.

seanpdoyle added a commit to seanpdoyle/turbo-site that referenced this pull request Feb 13, 2024
Related to [@hotwired/turbo#1033][]

Document the cases when `[data-turbo-preload]` has no effect.

[@hotwired/turbo#1033]: hotwired/turbo#1033

Co-authored-by: Matheus Richard <[email protected]>
domchristie pushed a commit to domchristie/turbo that referenced this pull request Jul 20, 2024
Prior to this change, _any_ `<a>` element with `[data-turbo-preload]`
would be preloaded. With that behavior comes some risk:

* if an element is nested within a `[data-turbo="false"]` element, the
  preloading would occur unnecessarily
* if an element has `[data-turbo-stream]`, the preloaded request won't
  be the same as the ensuing request due to differences in the `Accept:`
  header
* if an element has `[data-turbo-method]`, the preloaded request won't
  be the same as the ensuing request due to differences in the method
* if an element is within a `<turbo-frame>` or driving a frame via
  `[data-turbo-frame]`, the preloaded request won't be the same as the
  ensuing request due to differences in the `Turbo-Frame:` header

This commit extends the `Preloader` delegate interface to include a
`shouldPreloadLink(link: HTMLAnchorElement)` predicate method to give
delegates an opportunity to opt-out of preloading. The `Session`
implementation of the delegate class adds rejects `<a>` elements that
match any of those cases.

Co-authored-by: Julian Rubisch <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Turbo-frame caching doesn't seem to work when preloading frame links.
3 participants