Skip to content

Commit

Permalink
merge master
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Dec 13, 2023
2 parents 8f26d9c + f7b4f18 commit e0b7373
Show file tree
Hide file tree
Showing 24 changed files with 238 additions and 72 deletions.
5 changes: 0 additions & 5 deletions .changeset/bright-buttons-flash.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/swift-deers-draw.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/thin-shrimps-cheer.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/warm-otters-compete.md

This file was deleted.

4 changes: 2 additions & 2 deletions documentation/docs/20-core-concepts/20-load.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ To get data from an external API or a `+server.js` handler, you can use the prov
- It can be used to make credentialed requests on the server, as it inherits the `cookie` and `authorization` headers for the page request.
- It can make relative requests on the server (ordinarily, `fetch` requires a URL with an origin when used in a server context).
- Internal requests (e.g. for `+server.js` routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the `text` and `json` methods of the `Response` object. Note that headers will _not_ be serialized, unless explicitly included via [`filterSerializedResponseHeaders`](hooks#server-hooks-handle).
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the `text`, `json` and `arrayBuffer` methods of the `Response` object. Note that headers will _not_ be serialized, unless explicitly included via [`filterSerializedResponseHeaders`](hooks#server-hooks-handle).
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request - if you received a warning in your browser console when using the browser `fetch` instead of the `load` `fetch`, this is why.

```js
Expand Down Expand Up @@ -278,7 +278,7 @@ Cookies will only be passed through the provided `fetch` function if the target
For example, if SvelteKit is serving my.domain.com:
- domain.com WILL NOT receive cookies
- my.domain.com WILL receive cookies
- api.domain.dom WILL NOT receive cookies
- api.domain.com WILL NOT receive cookies
- sub.my.domain.com WILL receive cookies

Other cookies will not be passed when `credentials: 'include'` is set, because SvelteKit does not know which domain which cookie belongs to (the browser does not pass this information along), so it's not safe to forward any of them. Use the [handleFetch hook](hooks#server-hooks-handlefetch) to work around it.
Expand Down
20 changes: 20 additions & 0 deletions packages/kit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# @sveltejs/kit

## 1.30.0

### Minor Changes

- feat: inline `response.arrayBuffer()` during ssr ([#10535](https://github.com/sveltejs/kit/pull/10535))

### Patch Changes

- fix: allow `"false"` value for preload link options ([#10555](https://github.com/sveltejs/kit/pull/10555))

- fix: call worker `unref` instead of `terminate` ([#10120](https://github.com/sveltejs/kit/pull/10120))

- fix: correctly analyse exported server API methods during build ([#11019](https://github.com/sveltejs/kit/pull/11019))

- fix: avoid error when back navigating before page is initialized ([#10636](https://github.com/sveltejs/kit/pull/10636))

- fix: allow service-worker.js to import assets ([#9285](https://github.com/sveltejs/kit/pull/9285))

- fix: distinguish better between not-found and internal-error ([#11131](https://github.com/sveltejs/kit/pull/11131))

## 1.29.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sveltejs/kit",
"version": "1.29.1",
"version": "1.30.0",
"description": "The fastest way to build Svelte apps",
"repository": {
"type": "git",
Expand Down
22 changes: 14 additions & 8 deletions packages/kit/src/exports/vite/build/build_service_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as vite from 'vite';
import { dedent } from '../../../core/sync/utils.js';
import { s } from '../../../utils/misc.js';
import { get_config_aliases } from '../utils.js';
import { assets_base } from './utils.js';

/**
* @param {string} out
Expand Down Expand Up @@ -64,16 +63,16 @@ export async function build_service_worker(
);

await vite.build({
base: assets_base(kit),
build: {
lib: {
entry: /** @type {string} */ (service_worker_entry_file),
name: 'app',
formats: ['es']
},
modulePreload: false,
rollupOptions: {
input: {
'service-worker': service_worker_entry_file
},
output: {
entryFileNames: 'service-worker.js'
entryFileNames: '[name].js',
assetFileNames: `${kit.appDir}/immutable/assets/[name].[hash][extname]`,
inlineDynamicImports: true
}
},
outDir: `${out}/client`,
Expand All @@ -84,6 +83,13 @@ export async function build_service_worker(
publicDir: false,
resolve: {
alias: [...get_config_aliases(kit), { find: '$service-worker', replacement: service_worker }]
},
experimental: {
renderBuiltUrl(filename) {
return {
runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname`
};
}
}
});
}
11 changes: 11 additions & 0 deletions packages/kit/src/exports/vite/build/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ export function resolve_symlinks(manifest, file) {
return { chunk, file };
}

const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS']);

// If we'd written this in TypeScript, it could be easy...
/**
* @param {string} str
* @returns {str is import('types').HttpMethod}
*/
export function is_http_method(str) {
return method_names.has(str);
}

/**
* @param {import('types').ValidatedKitConfig} config
* @returns {string}
Expand Down
24 changes: 23 additions & 1 deletion packages/kit/src/runtime/client/fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ if (DEV) {

const cache = new Map();

/**
* @param {string} text
* @returns {ArrayBufferLike}
*/
function b64_decode(text) {
const d = atob(text);

const u8 = new Uint8Array(d.length);

for (let i = 0; i < d.length; i++) {
u8[i] = d.charCodeAt(i);
}

return u8.buffer;
}

/**
* Should be called on the initial run of load functions that hydrate the page.
* Saves any requests with cache-control max-age to the cache.
Expand All @@ -86,10 +102,16 @@ export function initial_fetch(resource, opts) {

const script = document.querySelector(selector);
if (script?.textContent) {
const { body, ...init } = JSON.parse(script.textContent);
let { body, ...init } = JSON.parse(script.textContent);

const ttl = script.getAttribute('data-ttl');
if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) });
const b64 = script.getAttribute('data-b64');
if (b64 !== null) {
// Can't use native_fetch('data:...;base64,${body}')
// csp can block the request
body = b64_decode(body);
}

return Promise.resolve(new Response(body, init));
}
Expand Down
93 changes: 61 additions & 32 deletions packages/kit/src/runtime/server/page/load_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,25 @@ export async function load_data({
return result ?? null;
}

/**
* @param {ArrayBuffer} buffer
* @returns {string}
*/
function b64_encode(buffer) {
if (globalThis.Buffer) {
return Buffer.from(buffer).toString('base64');
}

const little_endian = new Uint8Array(new Uint16Array([1]).buffer)[0] > 0;

// The Uint16Array(Uint8Array(...)) ensures the code points are padded with 0's
return btoa(
new TextDecoder(little_endian ? 'utf-16le' : 'utf-16be').decode(
new Uint16Array(new Uint8Array(buffer))
)
);
}

/**
* @param {Pick<import('@sveltejs/kit').RequestEvent, 'fetch' | 'url' | 'request' | 'route'>} event
* @param {import('types').SSRState} state
Expand Down Expand Up @@ -243,38 +262,33 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)

const 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;
/**
* @param {string} body
* @param {boolean} is_b64
*/
async function push_fetched(body, is_b64) {
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}`
);
}

return body;
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,
is_b64
});
}

if (key === 'arrayBuffer') {
Expand All @@ -285,13 +299,28 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
dependency.body = new Uint8Array(buffer);
}

// TODO should buffer be inlined into the page (albeit base64'd)?
// any conditions in which it shouldn't be?
if (buffer instanceof ArrayBuffer) {
await push_fetched(b64_encode(buffer), true);
}

return buffer;
};
}

async function text() {
const body = await response.text();

if (!body || typeof body === 'string') {
await push_fetched(body, false);
}

if (dependency) {
dependency.body = body;
}

return body;
}

if (key === 'text') {
return text;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/kit/src/runtime/server/page/serialize_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export function serialize_data(fetched, filter, prerendering = false) {
`data-url=${escape_html_attr(fetched.url)}`
];

if (fetched.is_b64) {
attrs.push('data-b64');
}

if (fetched.request_headers || fetched.request_body) {
/** @type {import('types').StrictBody[]} */
const values = [];
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/runtime/server/page/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface Fetched {
request_headers?: HeadersInit | undefined;
response_body: string;
response: Response;
is_b64?: boolean;
}

export type Loaded = {
Expand Down
6 changes: 2 additions & 4 deletions packages/kit/src/utils/fork.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function forked(module, callback) {
* @param {T} opts
* @returns {Promise<U>}
*/
const fn = function (opts) {
return function (opts) {
return new Promise((fulfil, reject) => {
const worker = new Worker(fileURLToPath(module), {
env: {
Expand All @@ -53,7 +53,7 @@ export function forked(module, callback) {
}

if (data?.type === 'result' && data.module === module) {
worker.terminate();
worker.unref();
fulfil(data.payload);
}
}
Expand All @@ -66,6 +66,4 @@ export function forked(module, callback) {
});
});
};

return fn;
}
2 changes: 1 addition & 1 deletion packages/kit/src/version.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// generated during release, do not modify

/** @type {string} */
export const VERSION = '1.29.1';
export const VERSION = '1.30.0';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export async function load({ fetch }) {
const res = await fetch('/load/fetch-arraybuffer-b64/data');

const l = await fetch('/load/fetch-arraybuffer-b64/data', {
body: Uint8Array.from(Array(256).fill(0), (_, i) => i),
method: 'POST'
});

return {
data: res.arrayBuffer(),
data_long: l.arrayBuffer()
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script>
export let data;
$: arr = [...new Uint8Array(data.data)];
let ok = 'Ok';
$: {
const p = new Uint8Array(data.data_long);
ok = p.length === 256 ? 'Ok' : 'Wrong length';
if (p.length === 256) {
for (let i = 0; i < p.length; i++) {
if (p[i] !== i) {
ok = `Expected ${i} but got ${p[i]}`;
break;
}
}
}
}
</script>

<span class="test-content">{JSON.stringify(arr)}</span>

<br />

{ok}
<span style="word-wrap: break-word;">
{JSON.stringify([...new Uint8Array(data.data_long)])}
</span>
Loading

0 comments on commit e0b7373

Please sign in to comment.