Skip to content

Commit d86f1a9

Browse files
committed
feat: make handleFetch a shared hook
1 parent b183244 commit d86f1a9

File tree

6 files changed

+110
-55
lines changed

6 files changed

+110
-55
lines changed

packages/kit/src/core/sync/write_client_manifest.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
162162
export const dictionary = ${dictionary};
163163
164164
export const hooks = {
165+
handleFetch: ${
166+
client_hooks_file ? 'client_hooks.handleFetch || ' : ''
167+
}(({ request, fetch }) => fetch(request)),
165168
handleError: ${
166169
client_hooks_file ? 'client_hooks.handleError || ' : ''
167170
}(({ error }) => { console.error(error) }),

packages/kit/src/exports/public.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,14 +790,22 @@ export type HandleClientError = (input: {
790790
}) => MaybePromise<void | App.Error>;
791791

792792
/**
793-
* The [`handleFetch`](https://svelte.dev/docs/kit/hooks#Server-hooks-handleFetch) hook allows you to modify (or replace) a `fetch` request that happens inside a `load` function that runs on the server (or during pre-rendering)
793+
* The [`handleFetch`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleFetch) hook allows you to modify (or replace) a `fetch` request that happens inside a `load` function that runs on the server (or during pre-rendering)
794794
*/
795795
export type HandleFetch = (input: {
796796
event: RequestEvent;
797797
request: Request;
798798
fetch: typeof fetch;
799799
}) => MaybePromise<Response>;
800800

801+
/**
802+
* The [`handleFetch`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleFetch) hook allows you to modify (or replace) a `fetch` request that happens inside a `load` function that runs on the client
803+
*/
804+
export type HandleClientFetch = (input: {
805+
request: Request;
806+
fetch: typeof fetch;
807+
}) => MaybePromise<Response>;
808+
801809
/**
802810
* The [`init`](https://svelte.dev/docs/kit/hooks#Shared-hooks-init) will be invoked before the server responds to its first request
803811
* @since 2.10.0

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
make_trackable,
88
normalize_path
99
} from '../../utils/url.js';
10-
import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
10+
import { create_fetch, dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
1111
import { parse, parse_server_route } from './parse.js';
1212
import * as storage from './session-storage.js';
1313
import {
@@ -276,6 +276,7 @@ export async function start(_app, _target, hydrate) {
276276
}
277277

278278
app = _app;
279+
create_fetch(_app);
279280

280281
await _app.hooks.init?.();
281282

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

Lines changed: 85 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,68 +15,87 @@ export function unlock_fetch() {
1515
loading -= 1;
1616
}
1717

18-
if (DEV && BROWSER) {
19-
let can_inspect_stack_trace = false;
20-
21-
// detect whether async stack traces work
22-
// eslint-disable-next-line @typescript-eslint/require-await
23-
const check_stack_trace = async () => {
24-
const stack = /** @type {string} */ (new Error().stack);
25-
can_inspect_stack_trace = stack.includes('check_stack_trace');
26-
};
27-
28-
void check_stack_trace();
29-
18+
/**
19+
* @param {import('./types.js').SvelteKitApp} app
20+
*/
21+
export function create_fetch(app) {
3022
/**
31-
* @param {RequestInfo | URL} input
32-
* @param {RequestInit & Record<string, any> | undefined} init
23+
* @type {typeof fetch}
3324
*/
34-
window.fetch = (input, init) => {
35-
// Check if fetch was called via load_node. the lock method only checks if it was called at the
36-
// same time, but not necessarily if it was called from `load`.
37-
// We use just the filename as the method name sometimes does not appear on the CI.
38-
const url = input instanceof Request ? input.url : input.toString();
39-
const stack_array = /** @type {string} */ (new Error().stack).split('\n');
40-
// We need to do a cutoff because Safari and Firefox maintain the stack
41-
// across events and for example traces a `fetch` call triggered from a button
42-
// back to the creation of the event listener and the element creation itself,
43-
// where at some point client.js will show up, leading to false positives.
44-
const cutoff = stack_array.findIndex((a) => a.includes('load@') || a.includes('at load'));
45-
const stack = stack_array.slice(0, cutoff + 2).join('\n');
46-
47-
const in_load_heuristic = can_inspect_stack_trace
48-
? stack.includes('src/runtime/client/client.js')
49-
: loading;
50-
51-
// This flag is set in initial_fetch and subsequent_fetch
52-
const used_kit_fetch = init?.__sveltekit_fetch__;
53-
54-
if (in_load_heuristic && !used_kit_fetch) {
55-
console.warn(
56-
`Loading ${url} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://svelte.dev/docs/kit/load#making-fetch-requests`
57-
);
58-
}
25+
let runtime_fetch;
26+
if (DEV && BROWSER) {
27+
let can_inspect_stack_trace = false;
28+
29+
// detect whether async stack traces work
30+
// eslint-disable-next-line @typescript-eslint/require-await
31+
const check_stack_trace = async () => {
32+
const stack = /** @type {string} */ (new Error().stack);
33+
can_inspect_stack_trace = stack.includes('check_stack_trace');
34+
};
35+
36+
void check_stack_trace();
37+
38+
/**
39+
* @param {RequestInfo | URL} input
40+
* @param {RequestInit & Record<string, any> | undefined} init
41+
*/
42+
runtime_fetch = (input, init) => {
43+
// Check if fetch was called via load_node. the lock method only checks if it was called at the
44+
// same time, but not necessarily if it was called from `load`.
45+
// We use just the filename as the method name sometimes does not appear on the CI.
46+
const url = input instanceof Request ? input.url : input.toString();
47+
const stack_array = /** @type {string} */ (new Error().stack).split('\n');
48+
// We need to do a cutoff because Safari and Firefox maintain the stack
49+
// across events and for example traces a `fetch` call triggered from a button
50+
// back to the creation of the event listener and the element creation itself,
51+
// where at some point client.js will show up, leading to false positives.
52+
const cutoff = stack_array.findIndex((a) => a.includes('load@') || a.includes('at load'));
53+
const stack = stack_array.slice(0, cutoff + 2).join('\n');
54+
55+
const in_load_heuristic = can_inspect_stack_trace
56+
? stack.includes('src/runtime/client/client.js')
57+
: loading;
58+
59+
// This flag is set in initial_fetch and subsequent_fetch
60+
const used_kit_fetch = init?.__sveltekit_fetch__;
61+
62+
if (in_load_heuristic && !used_kit_fetch) {
63+
console.warn(
64+
`Loading ${url} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://svelte.dev/docs/kit/load#making-fetch-requests`
65+
);
66+
}
5967

60-
const method = input instanceof Request ? input.method : init?.method || 'GET';
68+
const method = input instanceof Request ? input.method : init?.method || 'GET';
6169

62-
if (method !== 'GET') {
63-
cache.delete(build_selector(input));
64-
}
70+
if (method !== 'GET') {
71+
cache.delete(build_selector(input));
72+
}
6573

66-
return native_fetch(input, init);
67-
};
68-
} else if (BROWSER) {
69-
window.fetch = (input, init) => {
70-
const method = input instanceof Request ? input.method : init?.method || 'GET';
74+
return native_fetch(input, init);
75+
};
76+
} else if (BROWSER) {
77+
runtime_fetch = (input, init) => {
78+
const method = input instanceof Request ? input.method : init?.method || 'GET';
7179

72-
if (method !== 'GET') {
73-
cache.delete(build_selector(input));
74-
}
80+
if (method !== 'GET') {
81+
cache.delete(build_selector(input));
82+
}
7583

76-
return native_fetch(input, init);
84+
return native_fetch(input, init);
85+
};
86+
}
87+
88+
window.fetch = async (input, init) => {
89+
const original_request = normalize_fetch_input(input, init);
90+
91+
return app.hooks.handleFetch({
92+
request: original_request,
93+
fetch: runtime_fetch,
94+
});
7795
};
7896
}
7997

98+
8099
const cache = new Map();
81100

82101
/**
@@ -175,3 +194,17 @@ function build_selector(resource, opts) {
175194

176195
return selector;
177196
}
197+
198+
199+
/**
200+
* @param {RequestInfo | URL} info
201+
* @param {RequestInit | undefined} init
202+
* @returns {Request}
203+
*/
204+
function normalize_fetch_input(info, init) {
205+
if (info instanceof Request) {
206+
return info;
207+
}
208+
209+
return new Request(typeof info === 'string' ? new URL(info) : info, init);
210+
}

packages/kit/src/types/internal.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ServerInitOptions,
1313
HandleFetch,
1414
Actions,
15+
HandleClientFetch,
1516
HandleClientError,
1617
Reroute,
1718
RequestEvent,
@@ -155,6 +156,7 @@ export interface ServerHooks {
155156
}
156157

157158
export interface ClientHooks {
159+
handleFetch: HandleClientFetch;
158160
handleError: HandleClientError;
159161
reroute: Reroute;
160162
transport: Record<string, Transporter>;

packages/kit/types/index.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,14 +772,22 @@ declare module '@sveltejs/kit' {
772772
}) => MaybePromise<void | App.Error>;
773773

774774
/**
775-
* The [`handleFetch`](https://svelte.dev/docs/kit/hooks#Server-hooks-handleFetch) hook allows you to modify (or replace) a `fetch` request that happens inside a `load` function that runs on the server (or during pre-rendering)
775+
* The [`handleFetch`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleFetch) hook allows you to modify (or replace) a `fetch` request that happens inside a `load` function that runs on the server (or during pre-rendering)
776776
*/
777777
export type HandleFetch = (input: {
778778
event: RequestEvent;
779779
request: Request;
780780
fetch: typeof fetch;
781781
}) => MaybePromise<Response>;
782782

783+
/**
784+
* The [`handleFetch`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleFetch) hook allows you to modify (or replace) a `fetch` request that happens inside a `load` function that runs on the client
785+
*/
786+
export type HandleClientFetch = (input: {
787+
request: Request;
788+
fetch: typeof fetch;
789+
}) => MaybePromise<Response>;
790+
783791
/**
784792
* The [`init`](https://svelte.dev/docs/kit/hooks#Shared-hooks-init) will be invoked before the server responds to its first request
785793
* @since 2.10.0

0 commit comments

Comments
 (0)