Skip to content

Commit

Permalink
enhance(prefetch): Adds a 'load' prefetch strategy, and ignores `3g…
Browse files Browse the repository at this point in the history
…` in slow connection detection (#9513)

* [enhance:prefetch] add global ignoreSlowConnection and add none to defaultStrategy enum

* changeset

* change defaultStrategy enum 'none' to 'all', and fix e2e test

* test:e2e prefetch

* update changeset

* rename defaultStrategy.all to defaultStrategy.load

* fix: remove global ignoreSlowConnection config and ignore 3g in slow connection detection

* fix: Revert variable name changes

* Split changeset

---------

Co-authored-by: bluwy <bjornlu.dev@gmail.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
  • Loading branch information
4 people authored Jan 3, 2024
1 parent 2a8b9c5 commit e44f6ac
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 5 deletions.
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 @@ -949,22 +949,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 @@ -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(),
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

0 comments on commit e44f6ac

Please sign in to comment.