From cb6ae8b9fe4f9d18b0f5835915a19a8c37e2a7c1 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Wed, 1 Nov 2023 23:13:42 +0800 Subject: [PATCH 1/5] Create 0043-prefetch.md --- proposals/0043-prefetch.md | 142 +++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 proposals/0043-prefetch.md diff --git a/proposals/0043-prefetch.md b/proposals/0043-prefetch.md new file mode 100644 index 00000000..4edf3ab9 --- /dev/null +++ b/proposals/0043-prefetch.md @@ -0,0 +1,142 @@ +- Start Date: 2023-11-01 +- Reference Issues: https://github.com/withastro/roadmap/issues/754 +- Implementation PR: https://github.com/withastro/astro/pull/8951 + +# Summary + +Deprecate `@astrojs/prefetch` and provide first-class prefetch support in Astro. + +# Example + +```html + +foo + + +foo + + +foo + + +foo + + +foo +``` +You can also configure options in `astro.config.js`: + +```js +prefetch: { + // Whether to prefetch all links by default. same as adding `data-astro-prefetch` to all links. + // Default false, but true if view transitions is used. User can explicitly set this to false + // to disable for view transitions. (I tried coming up a better name) + prefetchAll: true, + // Default prefetch strategy for `data-astro-prefetch` (no value specified). + defaultStrategy: 'hover' // accepts 'tap' and 'viewport' too +} +``` + +Different prefetch strategies have different behaviours and opinions: +- `tap`: Calls `fetch()` on `touchstart` or `mousedown` events (they are called before `click` event) +- `hover`: Calls `fetch()` on `focusin`+`focusout` or `mouseenter`+`mouseleave` events. Hover detection kicks in after 80ms delay. +- `viewport`: Creates a `` on enter viewport. It has lower priority than `fetch()` to not clog up the request. Intersection detection kicks in after 300ms. + +Notes: `hover` and `viewport` only works on `` tags on initial page load (and view transition page load) due to limitations of the events. Unless we watch the entire DOM with MutationObserver but it's not performant. + +--- + +For programmatic usage: + +```js +import { prefetch } from 'astro:prefetch' + +prefetch('http://...', { with: 'link' }) // second parameter optional, can specify link/fetch for prefetch priority +``` + +# Background & Motivation + +With the introduction of View Transitions, it includes partial prefetching code for snappy navigation between pages. We can take this opportunity to support prefetching in core, and share the prefetch behaviour with View Transitions. + +I've started an implementation before an RFC as the initial plan was to simply move `@astrojs/prefetch` to core. However, it would also be a good time to polish up and extend the API. + +# Goals + +- An option to enable prefetching +- Enable prefetching via an attribute/hint +- Enable prefetching for all links by default (required by View transitions) +- Disable prefetching if all links are enabled by default +- Different prefetching strategies (click, hover, viewport, etc) +- Only add JS if using prefetching + +# Non-Goals + +- Prefetch cache invalidation (Browser relies on cache control) +- Prefetch external links + +# Detailed Design + +A prefetch script for client-side is required. It should only be included if `prefetch` is truthy. The script can be injected through the `injectScript` integration API. + +### Config + +The prefetch configuration is a top-level Astro config: + +```js +// default value if `prefetch: true` (prefetch is not enabled by default) +prefetch: { + // Whether to prefetch all links by default + prefetchAll: false, + // Default prefetch strategy for `data-astro-prefetch` (no value specified). + defaultStrategy: 'hover' // accepts 'tap' and 'viewport' too +} +``` + +If View Transitions is used in Astro, the default value of `prefetch` (if user not configured) is `{ prefetchAll: true }`. The user can configure `false`, `{ prefetchAll: false }`, etc if they want to override this default. + +### Client script + +The script should attach listeners on initialization for different prefetch strategies: + +- `tap`: Calls `fetch()` on `touchstart` or `mousedown` events +- `hover`: Calls `fetch()` on `focusin`+`focusout` or `mouseenter`+`mouseleave` events. Hover detection kicks in after 80ms delay. +- `viewport`: Creates a `` on enter viewport. It has lower priority than `fetch()` to not clog up the request. Intersection detection kicks in after 300ms. + +Additional rules: + +- Prefetched links should not be fetched again (e.g. hovering on a link twice) +- The strategy should only apply when `data-astro-prefetch`'s value matches +- If `data-astro-prefetch` has no value, use the configured `defaultStrategy` +- If `prefetchAll` is enabled, apply `defaultStrategy` for all links + +### Programmatic API + +The client script would have an internal `prefetch` function that we can expose to the `astro:prefetch` module: + +```ts +export declare function prefetch(url: string, opts?: { with?: 'link' | 'fetch' }): void +``` + +# Testing Strategy + +End-to-end tests, make sure user tap/hover/viewport all workks. + +# Drawbacks + +- Users have limited prefetch features with `@astrojs/prefetch` +- Users have to use external prefetching solutions +- Double prefetching could happen as View Transitions prefetches too + +# Alternatives + +Continue supporting `@astrojs/prefetch` + +# Adoption strategy + +We should document a migration path for `@astrojs/prefetch` users to use the new `prefetch` option. + +Existing `@astrojs/prefetch` users could of course keep using it if needed, so an immediate switch isn't required. After the release of `prefetch` feature, we can deprecate the `@astrojs/prefetch` integration to nudge towards the new API. + +# Unresolved Questions + +n/a From 2fbb4bebdc01975fde84f0ae1abd573ab9230d01 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Wed, 1 Nov 2023 23:35:48 +0800 Subject: [PATCH 2/5] Clarify `prefetch` refers to the config option --- proposals/0043-prefetch.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/proposals/0043-prefetch.md b/proposals/0043-prefetch.md index 4edf3ab9..0aecab48 100644 --- a/proposals/0043-prefetch.md +++ b/proposals/0043-prefetch.md @@ -46,7 +46,7 @@ Notes: `hover` and `viewport` only works on `` tags on initial page load (a --- -For programmatic usage: +For programmatic usage (only if `prefetch` config is enabled): ```js import { prefetch } from 'astro:prefetch' @@ -76,7 +76,7 @@ I've started an implementation before an RFC as the initial plan was to simply m # Detailed Design -A prefetch script for client-side is required. It should only be included if `prefetch` is truthy. The script can be injected through the `injectScript` integration API. +A prefetch script for client-side is required. It should only be included if the `prefetch` config is truthy. The script can be injected through the `injectScript` integration API. ### Config @@ -92,7 +92,7 @@ prefetch: { } ``` -If View Transitions is used in Astro, the default value of `prefetch` (if user not configured) is `{ prefetchAll: true }`. The user can configure `false`, `{ prefetchAll: false }`, etc if they want to override this default. +If View Transitions is used in Astro, the default value of the `prefetch` config (if user not configured) is `{ prefetchAll: true }`. The user can configure `false`, `{ prefetchAll: false }`, etc if they want to override this default. ### Client script @@ -109,6 +109,9 @@ Additional rules: - If `data-astro-prefetch` has no value, use the configured `defaultStrategy` - If `prefetchAll` is enabled, apply `defaultStrategy` for all links +Notes: +- `fetch()` has higher priority than `` when prefetching + ### Programmatic API The client script would have an internal `prefetch` function that we can expose to the `astro:prefetch` module: @@ -117,6 +120,8 @@ The client script would have an internal `prefetch` function that we can expose export declare function prefetch(url: string, opts?: { with?: 'link' | 'fetch' }): void ``` +This module can only be imported if the `prefetch` config is enabled. + # Testing Strategy End-to-end tests, make sure user tap/hover/viewport all workks. From 8b90f24610e50156f15eee89a0e77857a34f9aad Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Thu, 2 Nov 2023 00:17:00 +0800 Subject: [PATCH 3/5] Fix typo Co-authored-by: Matthew Phillips --- proposals/0043-prefetch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/0043-prefetch.md b/proposals/0043-prefetch.md index 0aecab48..9f255226 100644 --- a/proposals/0043-prefetch.md +++ b/proposals/0043-prefetch.md @@ -124,7 +124,7 @@ This module can only be imported if the `prefetch` config is enabled. # Testing Strategy -End-to-end tests, make sure user tap/hover/viewport all workks. +End-to-end tests, make sure user tap/hover/viewport all works. And the `prefetch` programmatic API works. # Drawbacks From 65f845ef0cdda0dbffbcd1eaaa4a0798b86410a6 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Thu, 2 Nov 2023 17:28:07 +0800 Subject: [PATCH 4/5] Extend detailed design --- proposals/0043-prefetch.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/proposals/0043-prefetch.md b/proposals/0043-prefetch.md index 9f255226..b2756954 100644 --- a/proposals/0043-prefetch.md +++ b/proposals/0043-prefetch.md @@ -46,7 +46,7 @@ Notes: `hover` and `viewport` only works on `` tags on initial page load (a --- -For programmatic usage (only if `prefetch` config is enabled): +For programmatic usage (only if `prefetch` config is explicitly enabled regardless of View Transitions, otherwise report an error): ```js import { prefetch } from 'astro:prefetch' @@ -78,6 +78,8 @@ I've started an implementation before an RFC as the initial plan was to simply m A prefetch script for client-side is required. It should only be included if the `prefetch` config is truthy. The script can be injected through the `injectScript` integration API. +> NOTE: The details below works very differently to what `@astrojs/prefetch` has today. One feature `@astrojs/prefetch` has which I didn't implement is fetching HTMLs on viewport enter, parsing it for CSS links, and fetching them again. I think it's a little aggresive to support. + ### Config The prefetch configuration is a top-level Astro config: @@ -120,7 +122,13 @@ The client script would have an internal `prefetch` function that we can expose export declare function prefetch(url: string, opts?: { with?: 'link' | 'fetch' }): void ``` -This module can only be imported if the `prefetch` config is enabled. +This module can only be imported if the `prefetch` config is explicitly enabled, even with View Transitions enabled. + +- `url`: A URL string that can be a full `http://` path, or simply start with `/` or `./`. Internally it will construct as `new URL(url, window.location.href)`. + Prefetch will only run if the URL is not external and have not already been prefetched, otherwise it's a noop. +- `with`: The prefetch strategy used. (Not using the word `strategy` because it already refers to `tap/hover/viewport` etc). + - `'link'`: use ``, which has a lower loading priority. The browser will schedule when to prefetch it itself. + - `'fetch'`: use `fetch()`, has a higher loading priority. The browser will immediately fetch and cache it. # Testing Strategy From 18781cd63c0ea5f4a0ccddc694c8f4ff691586db Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Fri, 17 Nov 2023 23:40:34 +0800 Subject: [PATCH 5/5] Rename to 0044-prefetch.md --- proposals/{0043-prefetch.md => 0044-prefetch.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename proposals/{0043-prefetch.md => 0044-prefetch.md} (100%) diff --git a/proposals/0043-prefetch.md b/proposals/0044-prefetch.md similarity index 100% rename from proposals/0043-prefetch.md rename to proposals/0044-prefetch.md