Skip to content

Commit 777c8ef

Browse files
cooolbrosbenmccanneltigerchinodummdidumm
authored
fix: tighten up preloadCode (#12217)
support absolute URLs with `data-sveltekit-preload-code="viewport"` and reroutes for preloadCode in general --------- Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> Co-authored-by: Chew Tee Ming <chew.tee.ming@nindatech.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
1 parent 0142dd8 commit 777c8ef

File tree

16 files changed

+163
-19
lines changed

16 files changed

+163
-19
lines changed

.changeset/fluffy-coats-laugh.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sveltejs/kit": patch
3+
---
4+
5+
fix: support absolute URLs and reroutes with `data-sveltekit-preload-code="viewport"`

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

+39-14
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,15 @@ async function _preload_data(intent) {
426426
return load_cache.promise;
427427
}
428428

429-
/** @param {URL} url */
429+
/**
430+
* @param {URL} url
431+
* @returns {Promise<void>}
432+
*/
430433
async function _preload_code(url) {
431-
const route = routes.find((route) => route.exec(get_url_path(url)));
434+
const rerouted = get_rerouted_url(url);
435+
if (!rerouted) return;
436+
437+
const route = routes.find((route) => route.exec(get_url_path(rerouted)));
432438

433439
if (route) {
434440
await Promise.all([...route.layouts, route.leaf].map((load) => load?.[1]()));
@@ -1189,16 +1195,11 @@ async function load_root_error_page({ status, error, url, route }) {
11891195
}
11901196

11911197
/**
1192-
* Resolve the full info (which route, params, etc.) for a client-side navigation from the URL,
1193-
* taking the reroute hook into account. If this isn't a client-side-navigation (or the URL is undefined),
1194-
* returns undefined.
1195-
* @param {URL | undefined} url
1196-
* @param {boolean} invalidating
1198+
* Resolve the relative rerouted URL for a client-side navigation
1199+
* @param {URL} url
1200+
* @returns {URL | undefined}
11971201
*/
1198-
function get_navigation_intent(url, invalidating) {
1199-
if (!url) return;
1200-
if (is_external_url(url, base, app.hash)) return;
1201-
1202+
function get_rerouted_url(url) {
12021203
// reroute could alter the given URL, so we pass a copy
12031204
let rerouted;
12041205
try {
@@ -1225,9 +1226,26 @@ function get_navigation_intent(url, invalidating) {
12251226
}
12261227

12271228
// fall back to native navigation
1228-
return undefined;
1229+
return;
12291230
}
12301231

1232+
return rerouted;
1233+
}
1234+
1235+
/**
1236+
* Resolve the full info (which route, params, etc.) for a client-side navigation from the URL,
1237+
* taking the reroute hook into account. If this isn't a client-side-navigation (or the URL is undefined),
1238+
* returns undefined.
1239+
* @param {URL | undefined} url
1240+
* @param {boolean} invalidating
1241+
*/
1242+
function get_navigation_intent(url, invalidating) {
1243+
if (!url) return;
1244+
if (is_external_url(url, base, app.hash)) return;
1245+
1246+
const rerouted = get_rerouted_url(url);
1247+
if (!rerouted) return;
1248+
12311249
const path = get_url_path(rerouted);
12321250

12331251
for (const route of routes) {
@@ -1942,13 +1960,20 @@ export function preloadCode(pathname) {
19421960
const url = new URL(pathname, current.url);
19431961

19441962
if (DEV) {
1963+
if (!pathname.startsWith('/')) {
1964+
throw new Error(
1965+
'argument passed to preloadCode must be a pathname (i.e. "/about" rather than "http://example.com/about"'
1966+
);
1967+
}
1968+
19451969
if (!pathname.startsWith(base)) {
19461970
throw new Error(
1947-
`pathnames passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
1971+
`pathname passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
19481972
);
19491973
}
19501974

1951-
if (!routes.find((route) => route.exec(get_url_path(url)))) {
1975+
const rerouted = get_rerouted_url(url);
1976+
if (!rerouted || !routes.find((route) => route.exec(get_url_path(rerouted)))) {
19521977
throw new Error(`'${pathname}' did not match any routes`);
19531978
}
19541979
}

packages/kit/test/ambient.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ declare global {
1616
const preloadData: (url: string) => Promise<void>;
1717
const beforeNavigate: (fn: (url: URL) => void | boolean) => void;
1818
const afterNavigate: (fn: () => void) => void;
19-
const preloadCode: (...urls: string[]) => Promise<void>;
19+
const preloadCode: (pathname: string) => Promise<void>;
2020
}
2121

2222
export {};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div style="height: 200vh"></div>
2+
3+
<a
4+
id="viewport"
5+
href="/data-sveltekit/preload-code/target/viewport"
6+
data-sveltekit-preload-code="viewport">viewport</a
7+
>
8+
<a id="hover" href="/data-sveltekit/preload-code/target/hover" data-sveltekit-preload-code="hover"
9+
>hover</a
10+
>
11+
<a id="tap" href="/data-sveltekit/preload-code/target/tap" data-sveltekit-preload-code="tap">tap</a>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function load() {
2+
return {
3+
name: 'eager'
4+
};
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let data;
3+
</script>
4+
5+
<h1>{data.name}</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function load() {
2+
return {
3+
name: 'hover'
4+
};
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let data;
3+
</script>
4+
5+
<h1>{data.name}</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function load() {
2+
return {
3+
name: 'tap'
4+
};
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let data;
3+
</script>
4+
5+
<h1>{data.name}</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function load() {
2+
return {
3+
name: 'hover'
4+
};
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let data;
3+
</script>
4+
5+
<h1>{data.name}</h1>

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

+30
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,36 @@ test.describe('Invalidation', () => {
824824
});
825825

826826
test.describe('data-sveltekit attributes', () => {
827+
test('data-sveltekit-preload-code', async ({ page }) => {
828+
/** @type {string[]} */
829+
const requests = [];
830+
page.on('request', (r) => {
831+
requests.push(r.url());
832+
});
833+
834+
// eager
835+
await page.goto('/data-sveltekit/preload-code');
836+
expect(requests.length).toBeGreaterThanOrEqual(1);
837+
838+
// viewport
839+
requests.length = 0;
840+
page.locator('#viewport').scrollIntoViewIfNeeded();
841+
await Promise.all([page.waitForTimeout(100), page.waitForLoadState('networkidle')]);
842+
expect(requests.length).toBeGreaterThanOrEqual(1);
843+
844+
// hover
845+
requests.length = 0;
846+
await page.locator('#hover').dispatchEvent('mousemove');
847+
await Promise.all([page.waitForTimeout(100), page.waitForLoadState('networkidle')]);
848+
expect(requests.length).toBeGreaterThanOrEqual(1);
849+
850+
// tap
851+
requests.length = 0;
852+
await page.locator('#tap').dispatchEvent('touchstart');
853+
await Promise.all([page.waitForTimeout(100), page.waitForLoadState('networkidle')]);
854+
expect(requests.length).toBeGreaterThanOrEqual(1);
855+
});
856+
827857
test('data-sveltekit-preload-data', async ({ page }) => {
828858
/** @type {string[]} */
829859
const requests = [];

packages/kit/test/apps/basics/test/cross-platform/client.test.js

+34-1
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,40 @@ test.describe.serial('Errors', () => {
601601
});
602602

603603
test.describe('Prefetching', () => {
604-
test('prefetches programmatically', async ({ baseURL, page, app }) => {
604+
test('prefetches code programmatically', async ({ page, app }) => {
605+
await page.goto('/routing/a');
606+
607+
/** @type {string[]} */
608+
const requests = [];
609+
page.on('request', (r) => {
610+
requests.push(r.url());
611+
});
612+
613+
await app.preloadCode('/routing/b');
614+
615+
// svelte request made is environment dependent
616+
if (process.env.DEV) {
617+
expect(requests.filter((req) => req.endsWith('routing/b/+page.js')).length).toBe(1);
618+
expect(requests.filter((req) => req.endsWith('routing/b/+page.svelte')).length).toBe(1);
619+
} else {
620+
expect(requests.filter((req) => /\/_app\/immutable\/nodes\/.*?.js$/.test(req)).length).toBe(
621+
1
622+
);
623+
}
624+
625+
if (process.env.DEV) {
626+
try {
627+
await app.preloadCode('https://example.com');
628+
throw new Error('Error was not thrown');
629+
} catch (/** @type {any} */ e) {
630+
expect(e.message).toMatch(
631+
'argument passed to preloadCode must be a pathname (i.e. "/about" rather than "http://example.com/about"'
632+
);
633+
}
634+
}
635+
});
636+
637+
test('prefetches data programmatically', async ({ baseURL, page, app }) => {
605638
await page.goto('/routing/a');
606639

607640
/** @type {string[]} */

packages/kit/test/utils.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const test: TestType<
1818
invalidate(url: string): Promise<void>;
1919
beforeNavigate(url: URL): void | boolean;
2020
afterNavigate(url: URL): void;
21-
preloadCode(...urls: string[]): Promise<void>;
21+
preloadCode(pathname: string): Promise<void>;
2222
preloadData(url: string): Promise<void>;
2323
};
2424
clicknav(selector: string, options?: Parameters<Page['waitForNavigation']>[0]): Promise<void>;

packages/kit/test/utils.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ export const test = base.extend({
4040
afterNavigate: () => page.evaluate(() => afterNavigate(() => {})),
4141

4242
/**
43-
* @param {string[]} urls
43+
* @param {string} pathname
4444
* @returns {Promise<void>}
4545
*/
46-
preloadCode: (...urls) => page.evaluate((urls) => preloadCode(...urls), urls),
46+
preloadCode: (pathname) => page.evaluate((pathname) => preloadCode(pathname), pathname),
4747

4848
/**
4949
* @param {string} url

0 commit comments

Comments
 (0)