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

enhance(prefetch): Adds a 'load' prefetch strategy, and ignores 3g in slow connection detection #9513

Merged
merged 13 commits into from
Jan 3, 2024
Merged
5 changes: 5 additions & 0 deletions .changeset/bright-feet-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': minor
---

Adds a `'load'` prefetch strategy to prefetch links on page load
5 changes: 5 additions & 0 deletions .changeset/two-hats-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Ignores `3g` in slow connection detection. Only `2g` and `slow-2g` are considered slow connections.
4 changes: 3 additions & 1 deletion packages/astro/e2e/fixtures/prefetch/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<span>Scroll down to trigger viewport prefetch</span>
<!-- Large empty space to test viewport -->
<div style="height: 1000px;"></div>
<a id="prefetch-load" href="/prefetch-load" data-astro-prefetch="load">load</a>
<br>
<a id="prefetch-viewport" href="/prefetch-viewport" data-astro-prefetch="viewport">viewport</a>
<script>
// @ts-nocheck
Expand All @@ -29,4 +31,4 @@
})
</script>
</body>
</html>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Prefetch load</h1>
101 changes: 101 additions & 0 deletions packages/astro/e2e/prefetch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')", () => {
Expand Down Expand Up @@ -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();
});
});
5 changes: 3 additions & 2 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -947,22 +947,23 @@ 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.
*
* - `'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.
*
* ```html
* <a href="/about" data-astro-prefetch="viewport">About</a>
* ```
*/
defaultStrategy?: 'tap' | 'hover' | 'viewport';
defaultStrategy?: 'tap' | 'hover' | 'viewport' | 'load';
};

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,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(),
Expand Down
17 changes: 16 additions & 1 deletion packages/astro/src/prefetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function init(defaultOpts?: InitOptions) {
initTapStrategy();
initHoverStrategy();
initViewportStrategy();
initLoadStrategy();
}

/**
Expand Down Expand Up @@ -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'`)
Expand Down Expand Up @@ -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;
}
Expand Down
Loading