0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-24 23:21:57 -05:00

Support prefetch in core (#8951)

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Bjorn Lu 2023-11-08 22:07:18 +08:00 committed by GitHub
parent 23c9a30ad8
commit 38e21d1275
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 688 additions and 41 deletions

View file

@ -0,0 +1,21 @@
---
'astro': minor
---
Prefetching is now supported in core
You can enable prefetching for your site with the `prefetch: true` config. It is enabled by default when using [View Transitions](https://docs.astro.build/en/guides/view-transitions/) and can also be used to configure the `prefetch` behaviour used by View Transitions.
You can enable prefetching by setting `prefetch:true` in your Astro config:
```js
// astro.config.js
import { defineConfig } from 'astro/config';
export default defineConfig({
prefetch: true
})
```
This replaces the `@astrojs/prefetch` integration, which is now deprecated and will eventually be removed.
Visit the [Prefetch guide](https://docs.astro.build/en/guides/prefetch/) for more information.

View file

@ -121,6 +121,10 @@ declare module 'astro:transitions/client' {
export const navigate: TransitionRouterModule['navigate'];
}
declare module 'astro:prefetch' {
export { prefetch, PrefetchOptions } from 'astro/prefetch';
}
declare module 'astro:middleware' {
export * from 'astro/middleware/namespace';
}

View file

@ -25,11 +25,9 @@ const { fallback = 'animate' } = Astro.props;
<meta name="astro-view-transitions-enabled" content="true" />
<meta name="astro-view-transitions-fallback" content={fallback} />
<script>
import {
supportsViewTransitions,
transitionEnabledOnThisPage,
navigate,
} from 'astro:transitions/client';
import { supportsViewTransitions, navigate } from 'astro:transitions/client';
// NOTE: import from `astro/prefetch` as `astro:prefetch` requires the `prefetch` config to be enabled
import { init } from 'astro/prefetch';
export type Fallback = 'none' | 'animate' | 'swap';
function getFallback(): Fallback {
@ -40,21 +38,6 @@ const { fallback = 'animate' } = Astro.props;
return 'animate';
}
// Prefetching
function maybePrefetch(pathname: string) {
if (document.querySelector(`link[rel=prefetch][href="${pathname}"]`)) return;
// @ts-expect-error: connection might exist
if (navigator.connection) {
// @ts-expect-error: connection does exist
let conn = navigator.connection;
if (conn.saveData || /(2|3)g/.test(conn.effectiveType || '')) return;
}
let link = document.createElement('link');
link.setAttribute('rel', 'prefetch');
link.setAttribute('href', pathname);
document.head.append(link);
}
if (supportsViewTransitions || getFallback() !== 'none') {
document.addEventListener('click', (ev) => {
let link = ev.target;
@ -89,23 +72,9 @@ const { fallback = 'animate' } = Astro.props;
});
});
['mouseenter', 'touchstart', 'focus'].forEach((evName) => {
document.addEventListener(
evName,
(ev) => {
if (ev.target instanceof HTMLAnchorElement) {
let el = ev.target;
if (
el.origin === location.origin &&
el.pathname !== location.pathname &&
transitionEnabledOnThisPage()
) {
maybePrefetch(el.pathname);
}
}
},
{ passive: true, capture: true }
);
});
// @ts-expect-error injected by vite-plugin-transitions for treeshaking
if (!__PREFETCH_DISABLED__) {
init({ prefetchAll: true });
}
}
</script>

View file

@ -0,0 +1,6 @@
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
prefetch: true
});

View file

@ -0,0 +1,8 @@
{
"name": "@e2e/prefetch",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Prefetch</h1>
<a id="prefetch-default" href="/prefetch-default">default</a>
<br>
<a id="prefetch-false" href="/prefetch-false" data-astro-prefetch="false">false</a>
<br>
<a id="prefetch-tap" href="/prefetch-tap" data-astro-prefetch="tap">tap</a>
<br>
<a id="prefetch-hover" href="/prefetch-hover" data-astro-prefetch="hover">hover</a>
<br>
<button id="prefetch-manual">manual</button>
<br>
<span>Scroll down to trigger viewport prefetch</span>
<!-- Large empty space to test viewport -->
<div style="height: 1000px;"></div>
<a id="prefetch-viewport" href="/prefetch-viewport" data-astro-prefetch="viewport">viewport</a>
<script>
// @ts-nocheck
import { prefetch } from 'astro:prefetch'
document.getElementById('prefetch-manual').addEventListener('click', () => {
prefetch('/prefetch-manual', { with: 'link' })
})
</script>
</body>
</html>

View file

@ -0,0 +1 @@
<h1>Prefetch default</h1>

View file

@ -0,0 +1 @@
<h1>Prefetch false</h1>

View file

@ -0,0 +1 @@
<h1>Prefetch hover</h1>

View file

@ -0,0 +1 @@
<h1>Prefetch tap</h1>

View file

@ -0,0 +1 @@
<h1>Prefetch viewport</h1>

View file

@ -0,0 +1,6 @@
---
import Layout from '../components/Layout.astro';
---
<Layout>
<a id="prefetch-one" href="/one">Go to one with prefetch on hover</a>
</Layout>

View file

@ -0,0 +1,163 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
const test = testFactory({
root: './fixtures/prefetch/',
});
test.describe('Prefetch (default)', () => {
let devServer;
/** @type {string[]} */
const reqUrls = [];
test.beforeAll(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.beforeEach(async ({ page }) => {
page.on('request', (req) => {
reqUrls.push(new URL(req.url()).pathname);
});
});
test.afterEach(() => {
reqUrls.length = 0;
});
test.afterAll(async () => {
await devServer.stop();
});
test('Link without data-astro-prefetch should not prefetch', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-default');
});
test('data-astro-prefetch="false" should not prefetch', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-false');
});
test('data-astro-prefetch="tap" should prefetch on tap', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-tap');
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-tap').click(),
]);
expect(reqUrls).toContainEqual('/prefetch-tap');
});
test('data-astro-prefetch="hover" should prefetch on hover', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-hover');
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-hover').hover(),
]);
expect(reqUrls).toContainEqual('/prefetch-hover');
});
test('data-astro-prefetch="viewport" should prefetch on viewport', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-viewport');
// Scroll down to show the element
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-viewport').scrollIntoViewIfNeeded(),
]);
expect(reqUrls).toContainEqual('/prefetch-viewport');
expect(page.locator('link[rel="prefetch"][href$="/prefetch-viewport"]')).toBeDefined();
});
test('manual prefetch() works once', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-manual');
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-manual').click(),
]);
expect(reqUrls).toContainEqual('/prefetch-manual');
expect(page.locator('link[rel="prefetch"][href$="/prefetch-manual"]')).toBeDefined();
// prefetch again should have no effect
await page.locator('#prefetch-manual').click();
expect(reqUrls.filter((u) => u.includes('/prefetch-manual')).length).toEqual(1);
});
});
test.describe("Prefetch (prefetchAll: true, defaultStrategy: 'tap')", () => {
let devServer;
/** @type {string[]} */
const reqUrls = [];
test.beforeAll(async ({ astro }) => {
devServer = await astro.startDevServer({
prefetch: {
prefetchAll: true,
defaultStrategy: 'tap',
},
});
});
test.beforeEach(async ({ page }) => {
page.on('request', (req) => {
reqUrls.push(new URL(req.url()).pathname);
});
});
test.afterEach(() => {
reqUrls.length = 0;
});
test.afterAll(async () => {
await devServer.stop();
});
test('Link without data-astro-prefetch should prefetch', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-default');
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-default').click(),
]);
expect(reqUrls).toContainEqual('/prefetch-default');
});
test('data-astro-prefetch="false" should not prefetch', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-false');
});
test('data-astro-prefetch="tap" should prefetch on tap', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-tap');
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-tap').click(),
]);
expect(reqUrls).toContainEqual('/prefetch-tap');
});
test('data-astro-prefetch="hover" should prefetch on hover', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-hover');
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-hover').hover(),
]);
expect(reqUrls).toContainEqual('/prefetch-hover');
});
test('data-astro-prefetch="viewport" should prefetch on viewport', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(reqUrls).not.toContainEqual('/prefetch-viewport');
// Scroll down to show the element
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-viewport').scrollIntoViewIfNeeded(),
]);
expect(reqUrls).toContainEqual('/prefetch-viewport');
expect(page.locator('link[rel="prefetch"][href$="/prefetch-viewport"]')).toBeDefined();
});
});

View file

@ -901,4 +901,19 @@ test.describe('View Transitions', () => {
let announcer = page.locator('.astro-route-announcer');
await expect(announcer, 'should have content').toHaveCSS('width', '1px');
});
test('should prefetch on hover by default', async ({ page, astro }) => {
/** @type {string[]} */
const reqUrls = [];
page.on('request', (req) => {
reqUrls.push(new URL(req.url()).pathname);
});
await page.goto(astro.resolveUrl('/prefetch'));
expect(reqUrls).not.toContainEqual('/one');
await Promise.all([
page.waitForEvent('request'), // wait prefetch request
page.locator('#prefetch-one').hover(),
]);
expect(reqUrls).toContainEqual('/one');
});
});

View file

@ -78,7 +78,8 @@
"default": "./dist/core/middleware/namespace.js"
},
"./transitions": "./dist/transitions/index.js",
"./transitions/router": "./dist/transitions/router.js"
"./transitions/router": "./dist/transitions/router.js",
"./prefetch": "./dist/prefetch/index.js"
},
"imports": {
"#astro/*": "./dist/*.js"

View file

@ -536,6 +536,71 @@ export interface AstroUserConfig {
*/
redirects?: Record<string, RedirectConfig>;
/**
* @docs
* @name prefetch
* @type {boolean | object}
* @description
* Enable prefetching for links on your site to provide faster page transitions.
* (Enabled by default on pages using the `<ViewTransitions />` router. Set `prefetch: false` to opt out of this behaviour.)
*
* This configuration automatically adds a prefetch script to every page in the project
* giving you access to the `data-astro-prefetch` attribute.
* Add this attribute to any `<a />` link on your page to enable prefetching for that page.
*
* ```html
* <a href="/about" data-astro-prefetch>About</a>
* ```
* Further customize the default prefetching behavior using the [`prefetch.defaultStrategy`](#prefetchdefaultstrategy) and [`prefetch.prefetchAll`](#prefetchprefetchall) options.
*
* See the [Prefetch guide](https://docs.astro.build/en/guides/prefetch/) for more information.
*/
prefetch?:
| boolean
| {
/**
* @docs
* @name prefetch.prefetchAll
* @type {boolean}
* @description
* Enable prefetching for all links, including those without the `data-astro-prefetch` attribute.
* This value defaults to `true` when using the `<ViewTransitions />` router. Otherwise, the default value is `false`.
*
* ```js
* prefetch: {
* prefetchAll: true
* }
* ```
*
* When set to `true`, you can disable prefetching individually by setting `data-astro-prefetch="false"` on any individual links.
*
* ```html
* <a href="/about" data-astro-prefetch="false">About</a>
*```
*/
prefetchAll?: boolean;
/**
* @docs
* @name prefetch.defaultStrategy
* @type {'tap' | 'hover' | 'viewport'}
* @default `'hover'`
* @description
* The default prefetch strategy to use when the `data-astro-prefetch` attribute is set on a link with no value.
*
* - `'tap'`: Prefetch just before you click on the link.
* - `'hover'`: Prefetch when you hover over or focus on the link. (default)
* - `'viewport'`: Prefetch as the links enter the viewport.
*
* You can override this default value and select a different strategy for any individual link by setting a value on the attribute.
*
* ```html
* <a href="/about" data-astro-prefetch="viewport">About</a>
* ```
*/
defaultStrategy?: 'tap' | 'hover' | 'viewport';
};
/**
* @docs
* @name site

View file

@ -192,6 +192,15 @@ export const AstroConfigSchema = z.object({
])
)
.default(ASTRO_CONFIG_DEFAULTS.redirects),
prefetch: z
.union([
z.boolean(),
z.object({
prefetchAll: z.boolean().optional(),
defaultStrategy: z.enum(['tap', 'hover', 'viewport']).optional(),
}),
])
.optional(),
image: z
.object({
endpoint: z.string().optional(),

View file

@ -11,6 +11,7 @@ import {
astroContentImportPlugin,
astroContentVirtualModPlugin,
} from '../content/index.js';
import astroPrefetch from '../prefetch/vite-plugin-prefetch.js';
import astroTransitions from '../transitions/vite-plugin-transitions.js';
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
@ -134,7 +135,8 @@ export async function createVite(
astroContentAssetPropagationPlugin({ mode, settings }),
vitePluginSSRManifest(),
astroAssetsPlugin({ settings, logger, mode }),
astroTransitions(),
astroPrefetch({ settings }),
astroTransitions({ settings }),
astroDevOverlay({ settings, logger }),
],
publicDir: fileURLToPath(settings.config.publicDir),

View file

@ -0,0 +1,272 @@
/*
NOTE: Do not add any dependencies or imports in this file so that it can load quickly in dev.
*/
// eslint-disable-next-line no-console
const debug = import.meta.env.DEV ? console.debug : undefined;
const inBrowser = import.meta.env.SSR === false;
// Track prefetched URLs so we don't prefetch twice
const prefetchedUrls = new Set<string>();
// Track listened anchors so we don't attach duplicated listeners
const listenedAnchors = new WeakSet<HTMLAnchorElement>();
// User-defined config for prefetch. The values are injected by vite-plugin-prefetch
// and can be undefined if not configured. But it will be set a fallback value in `init()`.
// @ts-expect-error injected global
let prefetchAll: boolean = __PREFETCH_PREFETCH_ALL__;
// @ts-expect-error injected global
let defaultStrategy: string = __PREFETCH_DEFAULT_STRATEGY__;
interface InitOptions {
defaultStrategy?: string;
prefetchAll?: boolean;
}
let inited = false;
/**
* Initialize the prefetch script, only works once.
*
* @param defaultOpts Default options for prefetching if not already set by the user config.
*/
export function init(defaultOpts?: InitOptions) {
if (!inBrowser) return;
// Init only once
if (inited) return;
inited = true;
debug?.(`[astro] Initializing prefetch script`);
// Fallback default values if not set by user config
prefetchAll ??= defaultOpts?.prefetchAll ?? false;
defaultStrategy ??= defaultOpts?.defaultStrategy ?? 'hover';
// In the future, perhaps we can enable treeshaking specific unused strategies
initTapStrategy();
initHoverStrategy();
initViewportStrategy();
}
/**
* Prefetch links with higher priority when the user taps on them
*/
function initTapStrategy() {
for (const event of ['touchstart', 'mousedown']) {
document.body.addEventListener(
event,
(e) => {
if (elMatchesStrategy(e.target, 'tap')) {
prefetch(e.target.href, { with: 'fetch' });
}
},
{ passive: true }
);
}
}
/**
* Prefetch links with higher priority when the user hovers over them
*/
function initHoverStrategy() {
let timeout: number;
// Handle focus listeners
document.body.addEventListener(
'focusin',
(e) => {
if (elMatchesStrategy(e.target, 'hover')) {
handleHoverIn(e);
}
},
{ passive: true }
);
document.body.addEventListener('focusout', handleHoverOut, { passive: true });
// Handle hover listeners. Re-run each time on page load.
onPageLoad(() => {
for (const anchor of document.getElementsByTagName('a')) {
// Skip if already listening
if (listenedAnchors.has(anchor)) continue;
// Add listeners for anchors matching the strategy
if (elMatchesStrategy(anchor, 'hover')) {
listenedAnchors.add(anchor);
anchor.addEventListener('mouseenter', handleHoverIn, { passive: true });
anchor.addEventListener('mouseleave', handleHoverOut, { passive: true });
}
}
});
function handleHoverIn(e: Event) {
const href = (e.target as HTMLAnchorElement).href;
// Debounce hover prefetches by 80ms
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
prefetch(href, { with: 'fetch' });
}, 80) as unknown as number;
}
// Cancel prefetch if the user hovers away
function handleHoverOut() {
if (timeout) {
clearTimeout(timeout);
timeout = 0;
}
}
}
/**
* Prefetch links with lower priority as they enter the viewport
*/
function initViewportStrategy() {
let observer: IntersectionObserver;
onPageLoad(() => {
for (const anchor of document.getElementsByTagName('a')) {
// Skip if already listening
if (listenedAnchors.has(anchor)) continue;
// Observe for anchors matching the strategy
if (elMatchesStrategy(anchor, 'viewport')) {
listenedAnchors.add(anchor);
observer ??= createViewportIntersectionObserver();
observer.observe(anchor);
}
}
});
}
function createViewportIntersectionObserver() {
const timeouts = new WeakMap<HTMLAnchorElement, number>();
return new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
const anchor = entry.target as HTMLAnchorElement;
const timeout = timeouts.get(anchor);
// Prefetch if intersecting
if (entry.isIntersecting) {
// Debounce viewport prefetches by 300ms
if (timeout) {
clearTimeout(timeout);
}
timeouts.set(
anchor,
setTimeout(() => {
observer.unobserve(anchor);
timeouts.delete(anchor);
prefetch(anchor.href, { with: 'link' });
}, 300) as unknown as number
);
} else {
// If exited viewport but haven't prefetched, cancel it
if (timeout) {
clearTimeout(timeout);
timeouts.delete(anchor);
}
}
}
});
}
export interface PrefetchOptions {
/**
* How the prefetch should prioritize the URL. (default `'link'`)
* - `'link'`: use `<link rel="prefetch">`, has lower loading priority.
* - `'fetch'`: use `fetch()`, has higher loading priority.
*/
with?: 'link' | 'fetch';
}
/**
* Prefetch a URL so it's cached when the user navigates to it.
*
* @param url A full or partial URL string based on the current `location.href`. They are only fetched if:
* - The user is online
* - The user is not in data saver mode
* - The URL is within the same origin
* - The URL is not the current page
* - The URL has not already been prefetched
* @param opts Additional options for prefetching.
*/
export function prefetch(url: string, opts?: PrefetchOptions) {
if (!canPrefetchUrl(url)) return;
prefetchedUrls.add(url);
const priority = opts?.with ?? 'link';
debug?.(`[astro] Prefetching ${url} with ${priority}`);
if (priority === 'link') {
const link = document.createElement('link');
link.rel = 'prefetch';
link.setAttribute('href', url);
document.head.append(link);
} else {
fetch(url).catch((e) => {
// eslint-disable-next-line no-console
console.log(`[astro] Failed to prefetch ${url}`);
// eslint-disable-next-line no-console
console.error(e);
});
}
}
function canPrefetchUrl(url: string) {
// Skip prefetch if offline
if (!navigator.onLine) return false;
if ('connection' in navigator) {
// Untyped Chrome-only feature: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/connection
const conn = navigator.connection as any;
// Skip prefetch if using data saver mode or slow connection
if (conn.saveData || /(2|3)g/.test(conn.effectiveType)) return false;
}
// Else check if URL is within the same origin, not the current page, and not already prefetched
try {
const urlObj = new URL(url, location.href);
return (
location.origin === urlObj.origin &&
location.pathname !== urlObj.pathname &&
!prefetchedUrls.has(url)
);
} catch {}
return false;
}
function elMatchesStrategy(el: EventTarget | null, strategy: string): el is HTMLAnchorElement {
// @ts-expect-error access unknown property this way as it's more performant
if (el?.tagName !== 'A') return false;
const attrValue = (el as HTMLElement).dataset.astroPrefetch;
// Out-out if `prefetchAll` is enabled
if (attrValue === 'false') {
return false;
}
// If anchor has no dataset but we want to prefetch all, or has dataset but no value,
// check against fallback default strategy
if ((attrValue == null && prefetchAll) || attrValue === '') {
return strategy === defaultStrategy;
}
// Else if dataset is explicitly defined, check against it
if (attrValue === strategy) {
return true;
}
// Else, no match
return false;
}
/**
* Listen to page loads and handle Astro's View Transition specific events
*/
function onPageLoad(cb: () => void) {
cb();
// Ignore first call of `astro-page-load` as we already call `cb` above.
// We have to call `cb` eagerly as View Transitions may not be enabled.
let firstLoad = false;
document.addEventListener('astro:page-load', () => {
if (!firstLoad) {
firstLoad = true;
return;
}
cb();
});
}

View file

@ -0,0 +1,56 @@
import * as vite from 'vite';
import type { AstroSettings } from '../@types/astro.js';
const virtualModuleId = 'astro:prefetch';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
const prefetchInternalModuleFsSubpath = 'astro/dist/prefetch/index.js';
const prefetchCode = `import { init } from 'astro/prefetch';init()`;
export default function astroPrefetch({ settings }: { settings: AstroSettings }): vite.Plugin {
const prefetchOption = settings.config.prefetch;
const prefetch = prefetchOption
? typeof prefetchOption === 'object'
? prefetchOption
: {}
: undefined;
// Check against existing scripts as this plugin could be called multiple times
if (prefetch && settings.scripts.every((s) => s.content !== prefetchCode)) {
// Inject prefetch script to all pages
settings.scripts.push({
stage: 'page',
content: `import { init } from 'astro/prefetch';init()`,
});
}
// Throw a normal error instead of an AstroError as Vite captures this in the plugin lifecycle
// and would generate a different stack trace itself through esbuild.
const throwPrefetchNotEnabledError = () => {
throw new Error('You need to enable the `prefetch` Astro config to import `astro:prefetch`');
};
return {
name: 'astro:prefetch',
async resolveId(id) {
if (id === virtualModuleId) {
if (!prefetch) throwPrefetchNotEnabledError();
return resolvedVirtualModuleId;
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
if (!prefetch) throwPrefetchNotEnabledError();
return `export { prefetch } from "astro/prefetch";`;
}
},
transform(code, id) {
// NOTE: Handle replacing the specifiers even if prefetch is disabled so View Transitions
// can import the interal module as not hit runtime issues.
if (id.includes(prefetchInternalModuleFsSubpath)) {
return code
.replace('__PREFETCH_PREFETCH_ALL__', JSON.stringify(prefetch?.prefetchAll))
.replace('__PREFETCH_DEFAULT_STRATEGY__', JSON.stringify(prefetch?.defaultStrategy));
}
},
};
}

View file

@ -1,4 +1,5 @@
import * as vite from 'vite';
import type { AstroSettings } from '../@types/astro.js';
const virtualModuleId = 'astro:transitions';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
@ -6,7 +7,7 @@ const virtualClientModuleId = 'astro:transitions/client';
const resolvedVirtualClientModuleId = '\0' + virtualClientModuleId;
// The virtual module for the astro:transitions namespace
export default function astroTransitions(): vite.Plugin {
export default function astroTransitions({ settings }: { settings: AstroSettings }): vite.Plugin {
return {
name: 'astro:transitions',
async resolveId(id) {
@ -30,5 +31,11 @@ export default function astroTransitions(): vite.Plugin {
`;
}
},
transform(code, id) {
if (id.includes('ViewTransitions.astro') && id.endsWith('.ts')) {
const prefetchDisabled = settings.config.prefetch === false;
return code.replace('__PREFETCH_DISABLED__', JSON.stringify(prefetchDisabled));
}
},
};
}

View file

@ -1,5 +1,7 @@
# @astrojs/prefetch 🔗
> NOTE: `@astrojs/prefetch` is deprecated. Use the `prefetch` feature in Astro 3.5 instead. Check out the [migration guide](https://docs.astro.build/en/guides/prefetch/#migrating-from-astrojsprefetch).
- <strong>[Why Prefetch?](#why-prefetch)</strong>
- <strong>[Installation](#installation)</strong>
- <strong>[Usage](#usage)</strong>

6
pnpm-lock.yaml generated
View file

@ -1396,6 +1396,12 @@ importers:
specifier: ^10.17.1
version: 10.18.1
packages/astro/e2e/fixtures/prefetch:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/e2e/fixtures/react-component:
dependencies:
'@astrojs/mdx':