Skip to content

Commit 6e97d5f

Browse files
authored
fix: preload data on tap after code was preloaded on hover (#13530)
fixes #13466 (for real this time) #13486 previously fixed the preload on tap not working by ensuring we do not skip checking the link until a preload has triggered. However, there's also the case where we have both preload link options: preload code on hover and preload data on tap. This PR ensures that we still preload data on tap even after a successful preload on hover if the preload on hover was only preloading code.
1 parent 993fa25 commit 6e97d5f

File tree

5 files changed

+108
-33
lines changed

5 files changed

+108
-33
lines changed

.changeset/silver-pigs-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: correctly preload data on `mousedown`/`touchstart` if code was preloaded on hover

packages/kit/src/runtime/client/client.js

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,25 +1636,29 @@ if (import.meta.hot) {
16361636
});
16371637
}
16381638

1639+
/** @typedef {(typeof PRELOAD_PRIORITIES)['hover'] | (typeof PRELOAD_PRIORITIES)['tap']} PreloadDataPriority */
1640+
16391641
function setup_preload() {
16401642
/** @type {NodeJS.Timeout} */
16411643
let mousemove_timeout;
16421644
/** @type {Element} */
16431645
let current_a;
1646+
/** @type {PreloadDataPriority} */
1647+
let current_priority;
16441648

16451649
container.addEventListener('mousemove', (event) => {
16461650
const target = /** @type {Element} */ (event.target);
16471651

16481652
clearTimeout(mousemove_timeout);
16491653
mousemove_timeout = setTimeout(() => {
1650-
void preload(target, 2);
1654+
void preload(target, PRELOAD_PRIORITIES.hover);
16511655
}, 20);
16521656
});
16531657

16541658
/** @param {Event} event */
16551659
function tap(event) {
16561660
if (event.defaultPrevented) return;
1657-
void preload(/** @type {Element} */ (event.composedPath()[0]), 1);
1661+
void preload(/** @type {Element} */ (event.composedPath()[0]), PRELOAD_PRIORITIES.tap);
16581662
}
16591663

16601664
container.addEventListener('mousedown', tap);
@@ -1674,11 +1678,14 @@ function setup_preload() {
16741678

16751679
/**
16761680
* @param {Element} element
1677-
* @param {number} priority
1681+
* @param {PreloadDataPriority} priority
16781682
*/
16791683
async function preload(element, priority) {
16801684
const a = find_anchor(element, container);
1681-
if (!a || a === current_a) return;
1685+
1686+
// we don't want to preload data again if the user has already hovered/tapped
1687+
const interacted = a === current_a && priority >= current_priority;
1688+
if (!a || interacted) return;
16821689

16831690
const { url, external, download } = get_link_info(a, base, app.hash);
16841691
if (external || download) return;
@@ -1687,31 +1694,34 @@ function setup_preload() {
16871694

16881695
// we don't want to preload data for a page we're already on
16891696
const same_url = url && get_page_key(current.url) === get_page_key(url);
1690-
1691-
if (!options.reload && !same_url) {
1692-
if (priority <= options.preload_data) {
1693-
current_a = a;
1694-
const intent = await get_navigation_intent(url, false);
1695-
if (intent) {
1696-
if (DEV) {
1697-
void _preload_data(intent).then((result) => {
1698-
if (result.type === 'loaded' && result.state.error) {
1699-
console.warn(
1700-
`Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
1701-
'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
1702-
'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
1703-
'See https://svelte.dev/docs/kit/link-options for more info'
1704-
);
1705-
}
1706-
});
1707-
} else {
1708-
void _preload_data(intent);
1697+
if (options.reload || same_url) return;
1698+
1699+
if (priority <= options.preload_data) {
1700+
current_a = a;
1701+
// we don't want to preload data again on tap if we've already preloaded it on hover
1702+
current_priority = PRELOAD_PRIORITIES.tap;
1703+
1704+
const intent = await get_navigation_intent(url, false);
1705+
if (!intent) return;
1706+
1707+
if (DEV) {
1708+
void _preload_data(intent).then((result) => {
1709+
if (result.type === 'loaded' && result.state.error) {
1710+
console.warn(
1711+
`Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
1712+
'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
1713+
'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
1714+
'See https://svelte.dev/docs/kit/link-options for more info'
1715+
);
17091716
}
1710-
}
1711-
} else if (priority <= options.preload_code) {
1712-
current_a = a;
1713-
void _preload_code(/** @type {URL} */ (url));
1717+
});
1718+
} else {
1719+
void _preload_data(intent);
17141720
}
1721+
} else if (priority <= options.preload_code) {
1722+
current_a = a;
1723+
current_priority = priority;
1724+
void _preload_code(/** @type {URL} */ (url));
17151725
}
17161726
}
17171727

packages/kit/test/apps/basics/src/routes/data-sveltekit/preload-data/+page.svelte

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@
88
</div>
99

1010
<a id="tap" href="/data-sveltekit/preload-data/target" data-sveltekit-preload-data="tap">tap</a>
11+
12+
<a
13+
id="hover-then-tap"
14+
href="/data-sveltekit/preload-data/target"
15+
data-sveltekit-preload-code="hover"
16+
data-sveltekit-preload-data="tap">hover for code then tap for data</a
17+
>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function load() {
2+
return {
3+
a: 1
4+
};
5+
}

packages/kit/test/apps/basics/test/client.test.js

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -887,37 +887,44 @@ test.describe('data-sveltekit attributes', () => {
887887
req
888888
.response()
889889
.then(
890-
(res) => res.text(),
890+
(res) => res?.text(),
891891
() => ''
892892
)
893-
.then((response) => {
894-
if (response.includes('this string should only appear in this preloaded file')) {
893+
.then((text) => {
894+
if (text?.includes('this string should only appear in this preloaded file')) {
895895
requests.push(req.url());
896896
}
897897
});
898898
}
899+
900+
if (req.url().includes('__data.json')) {
901+
requests.push(req.url());
902+
}
899903
});
900904

901905
await page.goto('/data-sveltekit/preload-data');
902906
await page.locator('#one').hover();
907+
await page.locator('#one').dispatchEvent('touchstart');
903908
await Promise.all([
904909
page.waitForTimeout(100), // wait for preloading to start
905910
page.waitForLoadState('networkidle') // wait for preloading to finish
906911
]);
907-
expect(requests.length).toBe(1);
912+
expect(requests.length).toBe(2);
908913

909914
requests.length = 0;
910915
await page.goto('/data-sveltekit/preload-data');
911916
await page.locator('#two').hover();
917+
await page.locator('#two').dispatchEvent('touchstart');
912918
await Promise.all([
913919
page.waitForTimeout(100), // wait for preloading to start
914920
page.waitForLoadState('networkidle') // wait for preloading to finish
915921
]);
916-
expect(requests.length).toBe(1);
922+
expect(requests.length).toBe(2);
917923

918924
requests.length = 0;
919925
await page.goto('/data-sveltekit/preload-data');
920926
await page.locator('#three').hover();
927+
await page.locator('#three').dispatchEvent('touchstart');
921928
await Promise.all([
922929
page.waitForTimeout(100), // wait for preloading to start
923930
page.waitForLoadState('networkidle') // wait for preloading to finish
@@ -932,7 +939,7 @@ test.describe('data-sveltekit attributes', () => {
932939
page.waitForTimeout(100), // wait for preloading to start
933940
page.waitForLoadState('networkidle') // wait for preloading to finish
934941
]);
935-
expect(requests.length).toBe(1);
942+
expect(requests.length).toBe(2);
936943
});
937944

938945
test('data-sveltekit-preload-data network failure does not trigger navigation', async ({
@@ -1001,6 +1008,47 @@ test.describe('data-sveltekit attributes', () => {
10011008
expect(page).toHaveURL('/data-sveltekit/preload-data/offline/slow-navigation');
10021009
});
10031010

1011+
test('data-sveltekit-preload-data tap works after data-sveltekit-preload-code hover', async ({
1012+
page
1013+
}) => {
1014+
/** @type {string[]} */
1015+
const requests = [];
1016+
page.on('request', (req) => {
1017+
if (req.resourceType() === 'script') {
1018+
req
1019+
.response()
1020+
.then(
1021+
(res) => res?.text(),
1022+
() => ''
1023+
)
1024+
.then((text) => {
1025+
if (text?.includes('this string should only appear in this preloaded file')) {
1026+
requests.push(req.url());
1027+
}
1028+
});
1029+
}
1030+
1031+
if (req.url().includes('__data.json')) {
1032+
requests.push(req.url());
1033+
}
1034+
});
1035+
1036+
await page.goto('/data-sveltekit/preload-data');
1037+
await page.locator('#hover-then-tap').hover();
1038+
await Promise.all([
1039+
page.waitForTimeout(100), // wait for preloading to start
1040+
page.waitForLoadState('networkidle') // wait for preloading to finish
1041+
]);
1042+
expect(requests.length).toBe(1);
1043+
1044+
await page.locator('#hover-then-tap').dispatchEvent('touchstart');
1045+
await Promise.all([
1046+
page.waitForTimeout(100), // wait for preloading to start
1047+
page.waitForLoadState('networkidle') // wait for preloading to finish
1048+
]);
1049+
expect(requests.length).toBe(2);
1050+
});
1051+
10041052
test('data-sveltekit-reload', async ({ baseURL, page, clicknav }) => {
10051053
/** @type {string[]} */
10061054
const requests = [];

0 commit comments

Comments
 (0)