Skip to content

Commit 5db4cd4

Browse files
ximuseltigerchinoRich-Harris
authored
feat: inline fetch response.body data to page (#11473)
* load: inline fetch `response.body` data to page * add changeset * fetch 'body': don't try to tee() more than once * update changeset message * Update .changeset/short-oranges-kiss.md * lint * add newlines for readability * use for-await --------- Co-authored-by: Tee Ming <[email protected]> Co-authored-by: Rich Harris <[email protected]>
1 parent 4a16e7f commit 5db4cd4

File tree

6 files changed

+129
-0
lines changed

6 files changed

+129
-0
lines changed

.changeset/short-oranges-kiss.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sveltejs/kit": minor
3+
---
4+
5+
feat: inline load fetch `response.body` stream data as base64 in page

packages/kit/src/runtime/server/page/load_data.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
311311
}
312312
}
313313

314+
/** @type {ReadableStream<Uint8Array>} */
315+
let teed_body;
316+
314317
const proxy = new Proxy(response, {
315318
get(response, key, _receiver) {
316319
/**
@@ -342,6 +345,39 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
342345
});
343346
}
344347

348+
if (key === 'body') {
349+
if (response.body === null) {
350+
return null;
351+
}
352+
353+
if (teed_body) {
354+
return teed_body;
355+
}
356+
357+
const [a, b] = response.body.tee();
358+
359+
void (async () => {
360+
let result = new Uint8Array();
361+
362+
for await (const chunk of a) {
363+
const combined = new Uint8Array(result.length + chunk.length);
364+
365+
combined.set(result, 0);
366+
combined.set(chunk, result.length);
367+
368+
result = combined;
369+
}
370+
371+
if (dependency) {
372+
dependency.body = new Uint8Array(result);
373+
}
374+
375+
void push_fetched(base64_encode(result), true);
376+
})();
377+
378+
return (teed_body = b);
379+
}
380+
345381
if (key === 'arrayBuffer') {
346382
return async () => {
347383
const buffer = await response.arrayBuffer();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const body_stream_to_buffer = async (body) => {
2+
let buffer = new Uint8Array();
3+
const reader = body.getReader();
4+
while (true) {
5+
const { done, value } = await reader.read();
6+
if (value != null) {
7+
const newBuffer = new Uint8Array(buffer.length + value.length);
8+
newBuffer.set(buffer, 0);
9+
newBuffer.set(value, buffer.length);
10+
buffer = newBuffer;
11+
}
12+
if (done) break;
13+
}
14+
return buffer;
15+
};
16+
17+
export async function load({ fetch }) {
18+
const res = await fetch('/load/fetch-body-stream-b64/data');
19+
20+
const l = await fetch('/load/fetch-body-stream-b64/data', {
21+
body: Uint8Array.from(Array(256).fill(0), (_, i) => i),
22+
method: 'POST'
23+
});
24+
25+
return {
26+
data: await body_stream_to_buffer(res.body),
27+
data_long: await body_stream_to_buffer(l.body)
28+
};
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script>
2+
export let data;
3+
4+
$: arr = [...new Uint8Array(data.data)];
5+
6+
let ok = 'Ok';
7+
8+
$: {
9+
const p = new Uint8Array(data.data_long);
10+
ok = p.length === 256 ? 'Ok' : 'Wrong length';
11+
12+
if (p.length === 256) {
13+
for (let i = 0; i < p.length; i++) {
14+
if (p[i] !== i) {
15+
ok = `Expected ${i} but got ${p[i]}`;
16+
break;
17+
}
18+
}
19+
}
20+
}
21+
</script>
22+
23+
<span class="test-content">{JSON.stringify(arr)}</span>
24+
25+
<br />
26+
27+
{ok}
28+
<span style="word-wrap: break-word;">
29+
{JSON.stringify([...new Uint8Array(data.data_long)])}
30+
</span>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const GET = () => {
2+
return new Response(new Uint8Array([1, 2, 3, 4]));
3+
};
4+
5+
export const POST = async ({ request }) => {
6+
return new Response(await request.arrayBuffer());
7+
};

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,28 @@ test.describe('Load', () => {
318318
}
319319
});
320320

321+
test('fetches using a body stream serialized with b64', async ({ page, javaScriptEnabled }) => {
322+
await page.goto('/load/fetch-body-stream-b64');
323+
324+
expect(await page.textContent('.test-content')).toBe('[1,2,3,4]');
325+
326+
if (!javaScriptEnabled) {
327+
const payload = '{"status":200,"statusText":"","headers":{},"body":"AQIDBA=="}';
328+
const post_payload =
329+
'{"status":200,"statusText":"","headers":{},"body":"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="}';
330+
331+
const script_content = await page.innerHTML(
332+
'script[data-sveltekit-fetched][data-b64][data-url="/load/fetch-body-stream-b64/data"]'
333+
);
334+
const post_script_content = await page.innerHTML(
335+
'script[data-sveltekit-fetched][data-b64][data-url="/load/fetch-body-stream-b64/data"][data-hash="16h3sp1"]'
336+
);
337+
338+
expect(script_content).toBe(payload);
339+
expect(post_script_content).toBe(post_payload);
340+
}
341+
});
342+
321343
test('json string is returned', async ({ page }) => {
322344
await page.goto('/load/relay');
323345
expect(await page.textContent('h1')).toBe('42');

0 commit comments

Comments
 (0)