diff --git a/.changeset/bright-feet-flash.md b/.changeset/bright-feet-flash.md new file mode 100644 index 000000000000..d88d2aecea32 --- /dev/null +++ b/.changeset/bright-feet-flash.md @@ -0,0 +1,5 @@ +--- +'astro': minor +--- + +Adds a `'load'` prefetch strategy to prefetch links on page load diff --git a/.changeset/two-hats-arrive.md b/.changeset/two-hats-arrive.md new file mode 100644 index 000000000000..6796462b0a0f --- /dev/null +++ b/.changeset/two-hats-arrive.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Ignores `3g` in slow connection detection. Only `2g` and `slow-2g` are considered slow connections. diff --git a/packages/astro/e2e/fixtures/prefetch/src/pages/index.astro b/packages/astro/e2e/fixtures/prefetch/src/pages/index.astro index 88ce196ae22f..63a30031ea8a 100644 --- a/packages/astro/e2e/fixtures/prefetch/src/pages/index.astro +++ b/packages/astro/e2e/fixtures/prefetch/src/pages/index.astro @@ -20,6 +20,8 @@ Scroll down to trigger viewport prefetch
+ load +
viewport - \ No newline at end of file + diff --git a/packages/astro/e2e/fixtures/prefetch/src/pages/prefetch-all.astro b/packages/astro/e2e/fixtures/prefetch/src/pages/prefetch-all.astro new file mode 100644 index 000000000000..8e2c799bbdfb --- /dev/null +++ b/packages/astro/e2e/fixtures/prefetch/src/pages/prefetch-all.astro @@ -0,0 +1 @@ +

Prefetch load

diff --git a/packages/astro/e2e/prefetch.test.js b/packages/astro/e2e/prefetch.test.js index a19c87680eca..12aa770029c2 100644 --- a/packages/astro/e2e/prefetch.test.js +++ b/packages/astro/e2e/prefetch.test.js @@ -95,6 +95,12 @@ test.describe('Prefetch (default)', () => { await page.locator('#prefetch-manual').click(); expect(reqUrls.filter((u) => u.includes('/prefetch-manual')).length).toEqual(1); }); + + test('data-astro-prefetch="load" should prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(reqUrls).toContainEqual('/prefetch-load'); + expect(page.locator('link[rel="prefetch"][href$="/prefetch-load"]')).toBeDefined(); + }); }); test.describe("Prefetch (prefetchAll: true, defaultStrategy: 'tap')", () => { @@ -182,4 +188,99 @@ test.describe("Prefetch (prefetchAll: true, defaultStrategy: 'tap')", () => { expect(reqUrls).toContainEqual('/prefetch-viewport'); expect(page.locator('link[rel="prefetch"][href$="/prefetch-viewport"]')).toBeDefined(); }); + + test('data-astro-prefetch="load" should prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(reqUrls).toContainEqual('/prefetch-load'); + expect(page.locator('link[rel="prefetch"][href$="/prefetch-load"]')).toBeDefined(); + }); +}); + +test.describe("Prefetch (prefetchAll: true, defaultStrategy: 'load')", () => { + let devServer; + /** @type {string[]} */ + const reqUrls = []; + + test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer({ + prefetch: { + prefetchAll: true, + defaultStrategy: 'load', + }, + }); + }); + + test.beforeEach(async ({ page }) => { + page.on('request', (req) => { + const urlObj = new URL(req.url()); + reqUrls.push(urlObj.pathname + urlObj.search); + }); + }); + + 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).toContainEqual('/prefetch-default'); + expect(page.locator('link[rel="prefetch"][href$="/prefetch-default"]')).toBeDefined(); + }); + + test('data-astro-prefetch="false" should not prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(reqUrls).not.toContainEqual('/prefetch-false'); + }); + + test('Link with search param should prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(reqUrls).not.toContainEqual('/?search-param=true'); + await Promise.all([ + page.waitForEvent('request'), // wait prefetch request + page.locator('#prefetch-search-param').hover(), + ]); + expect(reqUrls).toContainEqual('/?search-param=true'); + }); + + 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('data-astro-prefetch="load" should prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(reqUrls).toContainEqual('/prefetch-load'); + expect(page.locator('link[rel="prefetch"][href$="/prefetch-load"]')).toBeDefined(); + }); }); diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index dc47d3a63592..7c18af4602d4 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -949,7 +949,7 @@ export interface AstroUserConfig { /** * @docs * @name prefetch.defaultStrategy - * @type {'tap' | 'hover' | 'viewport'} + * @type {'tap' | 'hover' | 'viewport' | 'load'} * @default `'hover'` * @description * The default prefetch strategy to use when the `data-astro-prefetch` attribute is set on a link with no value. @@ -957,6 +957,7 @@ export interface AstroUserConfig { * - `'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. + * - `'load'`: Prefetch the link without any restrictions. * * You can override this default value and select a different strategy for any individual link by setting a value on the attribute. * @@ -964,7 +965,7 @@ export interface AstroUserConfig { * About * ``` */ - defaultStrategy?: 'tap' | 'hover' | 'viewport'; + defaultStrategy?: 'tap' | 'hover' | 'viewport' | 'load'; }; /** diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 0ea2d368a182..5d30d1b5305c 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -185,7 +185,7 @@ export const AstroConfigSchema = z.object({ z.boolean(), z.object({ prefetchAll: z.boolean().optional(), - defaultStrategy: z.enum(['tap', 'hover', 'viewport']).optional(), + defaultStrategy: z.enum(['tap', 'hover', 'viewport', 'load']).optional(), }), ]) .optional(), diff --git a/packages/astro/src/prefetch/index.ts b/packages/astro/src/prefetch/index.ts index 15f4ef0ccd95..99cf3627af8e 100644 --- a/packages/astro/src/prefetch/index.ts +++ b/packages/astro/src/prefetch/index.ts @@ -45,6 +45,7 @@ export function init(defaultOpts?: InitOptions) { initTapStrategy(); initHoverStrategy(); initViewportStrategy(); + initLoadStrategy(); } /** @@ -169,6 +170,20 @@ function createViewportIntersectionObserver() { }); } +/** + * Prefetch links with lower priority when page load + */ +function initLoadStrategy() { + onPageLoad(() => { + for (const anchor of document.getElementsByTagName('a')) { + if (elMatchesStrategy(anchor, 'load')) { + // Prefetch every link in this page + prefetch(anchor.href, { with: 'link' }); + } + } + }); +} + export interface PrefetchOptions { /** * How the prefetch should prioritize the URL. (default `'link'`) @@ -265,7 +280,7 @@ function isSlowConnection() { 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; - return conn.saveData || /(2|3)g/.test(conn.effectiveType); + return conn.saveData || /2g/.test(conn.effectiveType); } return false; }