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
prefetch page context #1617
base: main
Are you sure you want to change the base?
prefetch page context #1617
Conversation
@brillout Any advice on this would be greatly helpful. Thank you in advance for your assistance. |
@usk94 I will have a look and reply after I'm finished what I'm currently working on. ETA (beginning) of next week. |
@brillout |
@usk94 Alright, let's do this! Have a look at Open question: do you think viewport prefetching makes sense for |
Thanks! I'll try it.
Are you concerned that, for example, it might be too aggressive in terms of saturating the network with too many API requests? For instance, in frameworks like Astro, when viewport prefetching is enabled, the priority of these requests is managed to be lower to mitigate potential network congestion. But even with that consideration, I agree that it might be too aggressive. I think starting with hover prefetching would be sufficient. |
Yes, that's expected. It only fetches the I was thinking whether we should also call Let me know if you think
Yes, and also overloading the server (its database and logs).
👍 We could also, eventually, add support for viewport prefetching for individual links that opt in using the
Indeed Astro seems to be using clever heuristics. If you want we can improve Vike's prefetching in a follow up PR after this one. I also have a couple of ideas for improving static asset prefetching. |
@Boeing787 (sponsor who requested this feature) Do you have any special wishes? Your use case is that you want data to be prefetched when the user hovers a on link, correct? |
@brillout correct, to load the pageContext |
…ntext._pageId` are mismatched
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this is going in the right direction 🚀 I made a lot of comments and a couple of new thoughts occurred to me in the process. Let me know what you think. Looking forward to it 👀
@@ -1,5 +1,6 @@ | |||
export { prefetch } | |||
export { addLinkPrefetchHandlers } | |||
export { PrefetchedPageContext } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use export type { PrefetchedPageContext }
to make it a little bit clearer that it's a type.
@@ -48,6 +48,10 @@ const globalObject = getGlobalObject<{ | |||
previousPageContext?: { _pageId: string } & PageContextExports | |||
}>('renderPageClientSide.ts', { renderCounter: 0 }) | |||
|
|||
const globalObjectForPrefetchedPageContext = getGlobalObject<{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's re-use the globalObject
above.
@@ -48,6 +48,10 @@ const globalObject = getGlobalObject<{ | |||
previousPageContext?: { _pageId: string } & PageContextExports | |||
}>('renderPageClientSide.ts', { renderCounter: 0 }) | |||
|
|||
const globalObjectForPrefetchedPageContext = getGlobalObject<{ | |||
prefetchedPageContext?: PrefetchedPageContext | |||
}>('prefetch.ts', {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see, you basically use getGlobalObject(..., 'prefetch.ts')
to share information between modules. Interesting usage.
Or how about we export a new function getPrefetchedPageContext()
in prefetchts.ts
instead? I've a slight preference for this as it makes the code flow more explicit.
if ( | ||
prefetchedPageContext?.pageContextFromHooks && | ||
'_pageId' in prefetchedPageContext.pageContextFromHooks && | ||
prefetchedPageContext.pageContextFromHooks._pageId === pageContext._pageId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead, how about we make prefetchedPageContext
hold one pageContext
per pageId
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm not sure I understand your point. May I ask for more details?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can save multiple pageContext
objects, one per pageId
. In other words globalObject.prefetchedPageContext
would be a Record<string, PrefetchedPageContext>
where string
is the pageId
. The idea is that if the user hovers over multiple links, the pageContext
fetched for previous links can be used instead of being discarded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understood, thank you!
The idea is that if the user hovers over multiple links, the pageContext fetched for previous links can be used instead of being discarded.
Then, I have implemented this with { pageId: string; prefetchedPageContext: PrefetchedPageContext }[]
rather than Record<string, PrefetchedPageContext>
(because I struggled to fix type errors in getPageContextFromHooks.ts
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 We can polish such small details later.
@@ -203,9 +207,18 @@ async function renderPageClientSide(renderArgs: RenderArgs): Promise<void> { | |||
// Render page view | |||
await renderPageView(pageContext) | |||
} else { | |||
let res: Awaited<ReturnType<typeof getPageContextFromHooks_isNotHydration>> | |||
let res: Awaited<ReturnType<typeof getPageContextFromHooks_isNotHydration>> | PrefetchedPageContext | |||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thinks we can move the new code outside of the try-catch block?
@@ -109,3 +152,44 @@ function getPrefetchAttribute(linkTag: HTMLElement): PrefetchStaticAssets | null | |||
|
|||
assert(false) | |||
} | |||
|
|||
function getPrefetchPageContextAttribute(linkTag: HTMLElement): PrefetchPageContext | null { | |||
const whenAttr = linkTag.getAttribute('data-prefetch-page-context-when') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about data-prefetch-page-context
instead of data-prefetch-page-context-when
?
async function prefetchContextIfPossible(url: string, expire: number | undefined): Promise<void> { | ||
const now = Date.now() | ||
const lastPrefetch = globalObject?.lastPrefetchTime?.get(url) | ||
if (lastPrefetch && expire && now - lastPrefetch < expire) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- How about some default
expire
value? E.g. 5 seconds or maybe 30 seconds? I'm not sure. - I wonder whether
expire
should also apply toprefetch()
🤔 WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether expire should also apply to prefetch() 🤔 WDYT?
I think it would be reasonable to exclude the expire logic, as it is expected to be triggered less frequently than hover.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about some default expire value? E.g. 5 seconds or maybe 30 seconds? I'm not sure.
For now, I set it up in 5 seconds.
return { when: 'hover', expire: parseInt(expireAttr, 10) } | ||
} | ||
|
||
assertUsage(false, `data-prefetch-page-context-expire has value "${expireAttr}" but it should instead be number`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some code repetition here. See code above which is a duplicate. This is client-side code so it's important to keep it compact.
if ('prefetchPageContext' in pageContext.exports) { | ||
const { prefetchPageContext } = pageContext.exports | ||
|
||
const wrongUsageMsg = `prefetchPageContext should be an object with 'when' and 'expire' properties. 'when' should be false, 'hover', and 'expire' should be a number` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, thinking about it: how about we make the setting simply boolean | number
instead?
As for the failing CI, I think we can have a look at it later. |
@brillout |
👍 I can re-review after we resolved the two pending conversations, but let me know if you want me to re-review before that. Looking forward to it! |
@brillout |
@usk94 Neat. I will, tentatively today. |
I've started, I'll finish tomorrow. |
This reverts commit b0f2579.
b0f2579 fixes the CI. One (/ the only?) reason is that we need to use the URL instead of |
@brillout, Could the issue be that |
@phonzammi Maybe, I didn’t dig into that part yet 👀 |
Thanks! CI passes now. |
resolve #246
This PR introduces a feature to prefetch
pageContext
(specifically focusing ondata
) when users hover over a link.To ensure efficient network usage, the prefetch operation includes a cooldown period to prevent it from being re-triggered too soon after the initial action.
And if the user hovers over multiple links,
pageContext
fetched for previous links can be used instead of being discarded.Users can configure this feature through
+config.ts
or by settingdata-prefetch-page-context
attributes directly on anchor tags.