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
fix: Compatibility with SvelteKit #512
Conversation
response.clone() breaks compatibility with libraries which hooks fetch on itself, e.g. SvelteKit - https://github.com/sveltejs/kit/blob/master/packages/kit/src/runtime/server/page/load_data.js#L240 Response cloning leading to lost context for SvelteKit and request would be resent during CSR
@sindresorhus I think the test environment should be fixed because there is something wrong with xo or eslint |
Fixed in |
Can you explain your changes? It still does cloning, so I'm not sure how it fixes the issue for SvelteKit? And are you sure this won't have any adverse effects on Ky usage? There is probably a reason we always do cloning, even though I cannot remember why. |
# Conflicts: # package.json
@sindresorhus let me explain a little bit more, SvelteKit has a smart caching system. In vanilla JS it looks like this: /** @type {import('./$types').PageLoad} */
export async function load({ depends, fetch }) {
const resp = await fetch('...some resource');
const json = await resp.json();
return {
someData: json
};
} So, when response is cloned this logic gets broken, because SvelteKit hooks fetch on itself, from https://github.com/sveltejs/kit/blob/5ef7d0b387a8e0ff2f8d631d83585d479595d73c/packages/kit/src/runtime/server/page/load_data.js#L246: Click Meconst proxy = new Proxy(response, {
get(response, key, _receiver) {
async function text() {
const body = await response.text();
if (!body || typeof body === 'string') {
const status_number = Number(response.status);
if (isNaN(status_number)) {
throw new Error(
`response.status is not a number. value: "${
response.status
}" type: ${typeof response.status}`
);
}
fetched.push({
url: same_origin ? url.href.slice(event.url.origin.length) : url.href,
method: event.request.method,
request_body: /** @type {string | ArrayBufferView | undefined} */ (
input instanceof Request && cloned_body
? await stream_to_string(cloned_body)
: init?.body
),
request_headers: cloned_headers,
response_body: body,
response
});
}
if (dependency) {
dependency.body = body;
}
return body;
}
if (key === 'arrayBuffer') {
return async () => {
const buffer = await response.arrayBuffer();
if (dependency) {
dependency.body = new Uint8Array(buffer);
}
// TODO should buffer be inlined into the page (albeit base64'd)?
// any conditions in which it shouldn't be?
return buffer;
};
}
if (key === 'text') {
return text;
}
if (key === 'json') {
return async () => {
return JSON.parse(await text());
};
}
return Reflect.get(response, key, response);
}
}); As you can see from the source code, SvelteKit hooks the fetch response through the Proxy object. And there is also a special logic with the hooked "text" method - calling So, let's look now at ky's source code: for (const [type, mimeType] of Object.entries(responseTypes) as ObjectEntries<typeof responseTypes>) {
result[type] = async () => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType);
// here is SvelteKit hooked response via Proxy
const awaitedResult = await result;
// and here is "un-hooked" response, so SvelteKit will never know that the "text" method was actually called
const response = awaitedResult.clone();
if (type === 'json') {
if (response.status === 204) {
return '';
}
const arrayBuffer = await response.clone().arrayBuffer();
const responseSize = arrayBuffer.byteLength;
if (responseSize === 0) {
return '';
}
// but this could be easy fixed by returning "not cloned " response, this will keep SvelteKit's hook on returned response
if (options.parseJson) {
// calling .text() on hooked response
return options.parseJson(await awaitedResult.text());
}
}
return awaitedResult[type]();
// instead of this
// return response[type]();
};
}
return result; |
The only reason I know for cloning a response is you want to access the response body more than once, so there is already one copy of the response and I think it's safe enough to return body from original response. |
I’d love to get rid of clone() where possible, it’s given us quite a few problems. The main thing that needs to be checked for regressions is hooks that use the response. Each hook needs to receive a cloned response so that they are not dependent upon each other. |
78e2bc6
to
a8a3a26
Compare
Closing as this is not moving forward. We are still happy to improve SvelteKit compatibility, but the submitter must do extensive testing to ensure it doesn't break anything like hooks. |
response.clone() breaks compatibility with libraries which hooks fetch on itself, e.g. SvelteKit - https://github.com/sveltejs/kit/blob/5ef7d0b387a8e0ff2f8d631d83585d479595d73c/packages/kit/src/runtime/server/page/load_data.js#L246
Response cloning leading to lost context for SvelteKit and request would be resent during CSR