Open Rich-Harris opened 1 year ago
Explicit true
for generated key?
const object = teleport(true, () => ({ answer: 42 }))
that would defeat the object, really. if you're going to add true
you may as well just add a key
I like keeping the ID as a second param (always)
Can you elaborate on "We could use the same promise serialization mechanism we currently use:"?
From the example snippets, I'd think teleport simply acts like
function teleport(input) {
if (input_is_promise)
return input.then((result) => {
serialize_somehow(result);
return result;
})
else {
serialize_somehow(result);
return result;
}
}
PS, if someone's looking for more name suggestions https://github.com/sveltejs/kit/issues/3729 👀
input
would never be a promise, it would only ever be a function that returns a promise
Can you elaborate
Yeah, it would basically use the same logic we use for serializing data from a server load
function: https://github.com/sveltejs/kit/blob/40e85888d7cf53fb4c92bdca9efaa79c1fe5c682/packages/kit/src/runtime/server/page/render.js#L489-L553
On the server it would be something like this:
const teleported = [];
const streamed = [];
function teleport(fn, id = uid++) {
const result = fn();
// this calls `devalue.uneval` with a replacer that deals with Promises
const { data, chunks } = serialize(result);
// `data` contains `__sveltekit_xyz123.defer(someid)` etc
teleported.push(`__sveltekit_xyz123.teleported.set(${id}, ${data})`);
// `chunks` is an `AsyncIterable<string>` where each string contains
// `__sveltekit_xyz123.resolve(someid, somedata)`
if (chunks) streamed.push(chunks);
return result;
}
On the client it would be this:
// during hydration
function teleport(fn, id = uid++) {
return __sveltekit_xyz123.teleported.get(id);
}
// during navigation
function teleport(fn, id) {
return fn();
}
We could have two overloads for teleport
, to have both nice formatting and optional key:
declare function teleport<T>(data: () => T): T
declare function teleport<T>(key: string, data: () => T): T
The implementation would then check which type is the first argument.
Need this badly
Any kind of workarounds? It's slowing our app when we need to do the exact same request multiple times despite the data having not changed
This would improve app mounting speeds significantly since it no longer needs to reload data loaded during SSR.
The overload which @tmaxmax suggested seems to be ideal, although I wonder what will happen if teleport
s are called in different orders from server/client side (eg: if blocks or etc) without an explicit key parameter?
Does this relate in any way to Svelte 5 rune syntax?
Use case – JSON-RPC like API with tracing
We hit our API by batching multiple RPC requests in a single HTTP request. The body of the HTTP request is the array of RPC request bodies, which are JSON objects. For tracing, each RPC request is assigned separate trace ID and span ID.
This is incompatible with SvelteKit fetch
caching in SSR. On the server, SvelteKit custom fetch
caches responses in the HTML; during hydration, fetch
retrieves the responses from the HTML cache.
The problem is that the cache key is an hash of the request header and body. The HTTP request body includes each RPC's trace ID and span ID, which differ between SSR and hydration, resulting in cache misses.
For context, we are using @effect/rpc but I think this applies more generally.
If the key is optional, having it be the second argument would be more logical. But having it be the first argument would result in neater code
Why not both using overloads? I've definitely seen this pattern before:
function teleport<T>(func: () => T): T
function teleport<T>(key: string, func: () => T): T
// implementation...
@Rich-Harris the key is optional if we transform the code to include a unique file location (file, line, column) hash for the teleport
method.
teleport(() => ...)
// turns into
teleport(() => ..., GENERATED_HASH)
Describe the problem
One point of confusion with
load
functions is that universal loads run during SSR and upon hydration (and then for subsequent client-side navigations). This is most visible if you return something non-deterministic:The value of
data.random
inside+page.svelte
will differ between the server-rendered HTML and the hydrated document. If it were a+page.server.js
instead, the result of calling theload
function would be serialized, and would therefore be consistent.There are good reasons for re-running universal
load
functions upon hydration:(Note that
event.fetch
calls are not repeated — the responses are serialized into, and read from, the HTML.)Despite that, it would be useful to have a mechanism to avoid re-running code in cases where you just want to use the same value between server and client, and are happy with the serialization constraints.
Describe the proposed solution
I propose a new
event.teleport
helper:teleport(...)
would just return the data associated with the automatically-generated IDteleport
would just befn => fn()
Automatically generating IDs requires that
teleport
be called synchronously inside theload
body (and not be insideif
blocks etc — in other words, hooks rules). In some circumstances that might be untenable, so users could specify a key (if a key is reused, we would throw an error):Bikeshedding alert
If the key is optional, having it be the second argument would be more logical. But having it be the first argument would result in neater code:
Reusing entire
load
functionsIf you wanted to, you could easily reuse the entire function body:
Promises
We could use the same promise serialization mechanism we currently use:
Alternatives considered
keep
,sticky
,reuse
etcImportance
nice to have
Additional Information
No response