-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
feat: hydratable
#17154
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
#17154
Changes from all commits
91882ee
352ebbe
2d475ac
2218b1e
d541688
fd92394
4b146b6
1ad5de0
a6b7bc2
f76c1aa
bc9df88
a025b9a
53ccd2e
37f2e0e
05e60b1
caad89b
5af28fe
923b086
9fed6f0
9343114
ce926c3
2ec34a2
90dd32b
3f3ad1a
640fabd
9af6013
9c88438
0c34711
74b878f
614fdf2
9884945
b5f2143
3c36c7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'svelte': minor | ||
| --- | ||
|
|
||
| feat: `hydratable` API |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| --- | ||
| title: Hydratable data | ||
| --- | ||
|
|
||
| In Svelte, when you want to render asynchronous content data on the server, you can simply `await` it. This is great! However, it comes with a pitfall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes: | ||
|
|
||
| ```svelte | ||
| <script> | ||
| import { getUser } from 'my-database-library'; | ||
|
|
||
| // This will get the user on the server, render the user's name into the h1, | ||
| // and then, during hydration on the client, it will get the user _again_, | ||
| // blocking hydration until it's done. | ||
| const user = await getUser(); | ||
| </script> | ||
|
|
||
| <h1>{user.name}</h1> | ||
| ``` | ||
|
|
||
| That's silly, though. If we've already done the hard work of getting the data on the server, we don't want to get it again during hydration on the client. `hydratable` is a low-level API built to solve this problem. You probably won't need this very often -- it will be used behind the scenes by whatever datafetching library you use. For example, it powers [remote functions in SvelteKit](/docs/kit/remote-functions). | ||
|
|
||
| To fix the example above: | ||
|
|
||
| ```svelte | ||
| <script> | ||
| import { hydratable } from 'svelte'; | ||
| import { getUser } from 'my-database-library'; | ||
|
|
||
| // During server rendering, this will serialize and stash the result of `getUser`, associating | ||
| // it with the provided key and baking it into the `head` content. During hydration, it will | ||
| // look for the serialized version, returning it instead of running `getUser`. After hydration | ||
| // is done, if it's called again, it'll simply invoke `getUser`. | ||
| const user = await hydratable('user', getUser); | ||
| </script> | ||
|
|
||
| <h1>{user.name}</h1> | ||
| ``` | ||
|
|
||
| This API can also be used to provide access to random or time-based values that are stable between server rendering and hydration. For example, to get a random number that doesn't update on hydration: | ||
|
|
||
| ```ts | ||
| import { hydratable } from 'svelte'; | ||
| const rand = hydratable('random', () => Math.random()); | ||
| ``` | ||
|
|
||
| If you're a library author, be sure to prefix the keys of your `hydratable` values with the name of your library so that your keys don't conflict with other libraries. | ||
|
|
||
| ## Serialization | ||
|
|
||
| All data returned from a `hydratable` function must be serializable. Not to fear, though -- this doesn't mean you're limited to JSON! Svelte uses [`devalue`](https://npmjs.com/package/devalue) for serialization, which means it can serialize all sorts of things, including `Map`, `Set`, `URL`, and `BigInt`. Check the documentation page for a full list. In addition to these, thanks to some Svelte magic, you can also fearlessly use promises: | ||
|
|
||
| ```svelte | ||
| <script> | ||
| import { hydratable } from 'svelte'; | ||
| const promises = hydratable('random', () => { | ||
| return { | ||
| one: Promise.resolve(1), | ||
| two: Promise.resolve(2) | ||
| } | ||
| }); | ||
| </script> | ||
|
|
||
| {await promises.one} | ||
| {await promises.two} | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,10 +14,55 @@ You (or the framework you're using) called [`render(...)`](svelte-server#render) | |
| The `html` property of server render results has been deprecated. Use `body` instead. | ||
| ``` | ||
|
|
||
| ### hydratable_clobbering | ||
elliott-with-the-longest-name-on-github marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ``` | ||
| Attempted to set `hydratable` with key `%key%` twice with different values. | ||
|
|
||
| First instance occurred at: | ||
| %stack% | ||
|
|
||
| Second instance occurred at: | ||
| %stack2% | ||
| ``` | ||
|
|
||
| This error occurs when using `hydratable` multiple times with the same key. To avoid this, you can: | ||
| - Ensure all invocations with the same key result in the same value | ||
| - Update the keys to make both instances unique | ||
|
|
||
| ```svelte | ||
| <script> | ||
| import { hydratable } from 'svelte'; | ||
| await Promise.all([ | ||
| // which one should "win" and be serialized in the rendered response? | ||
| hydratable('hello', () => 'world'), | ||
| hydratable('hello', () => 'dad') | ||
| ]) | ||
| </script> | ||
| ``` | ||
|
|
||
| ### hydratable_serialization_failed | ||
|
|
||
| ``` | ||
| Failed to serialize `hydratable` data for key `%key%`. | ||
|
|
||
| `hydratable` can serialize anything [`uneval` from `devalue`](https://npmjs.com/package/uneval) can, plus Promises. | ||
|
|
||
| Stack: | ||
| %stack% | ||
| ``` | ||
|
|
||
| ### lifecycle_function_unavailable | ||
|
|
||
| ``` | ||
| `%name%(...)` is not available on the server | ||
| ``` | ||
|
|
||
| Certain methods such as `mount` cannot be invoked while running in a server context. Avoid calling them eagerly, i.e. not during render. | ||
|
|
||
| ### render_context_unavailable | ||
|
|
||
| ``` | ||
| Failed to retrieve `render` context. %addendum% | ||
| If `AsyncLocalStorage` is available, you're likely calling a function that needs access to the `render` context (`hydratable`, `cache`, or something that depends on these) from outside of `render`. If `AsyncLocalStorage` is not available, these functions must also be called synchronously from within `render` -- i.e. not after any `await`s. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we know, at the time we call
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is incorporated into the addendum -- I just couldn't dream up a way to be both helpful and put this much context into the addendum |
||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| <!-- This file is generated by scripts/process-messages/index.js. Do not edit! --> | ||
|
|
||
| ### unresolved_hydratable | ||
|
|
||
| ``` | ||
| A `hydratable` value with key `%key%` was created, but at least part of it was not used during the render. | ||
|
|
||
| The `hydratable` was initialized in: | ||
| %stack% | ||
| ``` | ||
|
|
||
| The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing | ||
| the result inside a `svelte:boundary` with a `pending` snippet: | ||
|
|
||
| ```svelte | ||
| <script> | ||
| import { hydratable } from 'svelte'; | ||
| import { getUser } from '$lib/get-user.js'; | ||
|
|
||
| const user = hydratable('user', getUser); | ||
| </script> | ||
|
|
||
| <svelte:boundary> | ||
| <h1>{(await user).name}</h1> | ||
|
|
||
| {#snippet pending()} | ||
| <div>Loading...</div> | ||
| {/snippet} | ||
| </svelte:boundary> | ||
| ``` | ||
|
|
||
| Consider inlining the `hydratable` call inside the boundary so that it's not called on the server. | ||
|
|
||
| Note that this can also happen when a `hydratable` contains multiple promises and some but not all of them have been used. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| ## unresolved_hydratable | ||
|
|
||
| > A `hydratable` value with key `%key%` was created, but at least part of it was not used during the render. | ||
| > | ||
| > The `hydratable` was initialized in: | ||
| > %stack% | ||
| The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing | ||
| the result inside a `svelte:boundary` with a `pending` snippet: | ||
|
|
||
| ```svelte | ||
| <script> | ||
| import { hydratable } from 'svelte'; | ||
| import { getUser } from '$lib/get-user.js'; | ||
| const user = hydratable('user', getUser); | ||
| </script> | ||
| <svelte:boundary> | ||
| <h1>{(await user).name}</h1> | ||
| {#snippet pending()} | ||
| <div>Loading...</div> | ||
| {/snippet} | ||
| </svelte:boundary> | ||
| ``` | ||
|
|
||
| Consider inlining the `hydratable` call inside the boundary so that it's not called on the server. | ||
|
|
||
| Note that this can also happen when a `hydratable` contains multiple promises and some but not all of them have been used. |
Uh oh!
There was an error while loading. Please reload this page.