-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
feat: hydratable and friends
#16960
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
base: main
Are you sure you want to change the base?
feat: hydratable and friends
#16960
Conversation
|
|
Very interesting approach |
|
| /** @type {Hydratable} */ | ||
| const hydratable = isomorphic_hydratable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the indirection? why not export function hydratable(...) {...}?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the only way I could figure out to type it correctly
Co-authored-by: Rich Harris <[email protected]>
Co-authored-by: Rich Harris <[email protected]>
| export function validate_effect(rune) { | ||
| const code = get_effect_validation_error_code(); | ||
| if (code === null) return; | ||
| e[code](rune); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will break tree-shaking. we need to use dot notation, even if it's more code at the usage site
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
booo, I was wondering if that was the case. sad sad
| get finally() { | ||
| get(this.#then); | ||
| return (/** @type {any} */ fn) => { | ||
| return get(this.#then)().finally(fn); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should it be this? then we should be able to simplify #then
| return get(this.#then)().finally(fn); | |
| return get(this.#then)(fn, fn); |
(we should add some tests so that we can try out changes like this!)
* chore: tweak resource implementation * fn, fn * tweak

This adds a couple of low-level APIs to enhance the experience of client/server rendering and communication in Svelte. These APIs are similar to
createSubscriberin that you're unlikely to regularly use them in your own application code, but they're crucially important low-level building blocks for metaframework and library authors (like ourselves, with SvelteKit).hydratableThis adds a new export from
svelte, calledhydratable. This has an isomorphic API (the one you're likely to use most often) along with an imperative server/client one (the one you're likely to use if you split your code into separate server/client entrypoints, eg. using export conditions).Isomorphic
You'd use it like this:
When server rendered, the result of
slow_random_numberwill be serialized along with your HTML, so when you later hydrate the same component on the client, it can synchronously pull the data from the serialized cache. This has two benefits: First, you don't have to wait for the async operation on the client during hydration, and second, the value is guaranteed to be the same as it was on the server (slow_random_numberwon't run again, so you won't see the dreaded "flash of previous content" you'd get from a hydration mismatch).You can provide custom serialization and deserialization options as well. The API for this is a little bit nasty: You have to pass either
encodeordecode, not both. This forces library authors to do something like the following, meaning your final code is maximally treeshakeable (decodeisn't needed on the server,encodeisn't needed on the client):It is an error to set the same
hydratablekey more than once, as this behavior is undefined.Imperative
If you're writing a really advanced library, you may need to actually split your code into separate client / server entrypoints and use export maps to load the correct version. In these cases, it can be better to use the imperative API:
hydratable.sethas the same no-multiple-sets rule as above.cacheThis adds two new exports from
'svelte/reactivity'calledcacheandCacheObserver. When provided with a key and a function,cachewill do what it sounds like it will do: Make sure that function is only ever called once, and return the resulting value in all other cases:On the server, this cache lives for the lifetime of a request: For a given cache key, the function passed to
cachewill only ever be executed once. On the client, a given cache key will live as long as there are reactive references to the result.resourceThis adds another new export from
'svelte/reactivity':resource. If you're familiar with TanStack Query, SvelteKit's remote functions feature, or SWR, this will be familiar to you:useristhenable, meaning it can beawaited:If you need it, you can also use the imperative API:
{#if user.error} <Error msg={user.error.message} /> {:else if !user.ready || user.loading} <Loading /> {:else} <User user={user.current} /> {/if}The resource also has
refresh(rerun the function) andset(synchronously update the resource's value) methods.Composition
These APIs compose quite nicely. For example, here's how you'd implement a simple fetcher:
fetcherwill:resourceresult hydratable, so that it's synchronously available on the client if it was previously rendered on the serverBefore submitting the PR, please make sure you do the following
feat:,fix:,chore:, ordocs:.packages/svelte/src, add a changeset (npx changeset).Tests and linting
pnpm testand lint the project withpnpm lint