elliott/hydratable
Elliott Johnson 17 hours ago
parent 9af60134db
commit 9c88438e58

@ -2,7 +2,7 @@
title: Hydratable data
---
In Svelte, when you want to render asynchonous content data on the server, you can simply `await` it. This is great! However, it comes with a major pitall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:
In Svelte, when you want to render asynchonous content data on the server, you can simply `await` it. This is great! However, it comes with a pitall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:
```svelte
<script>
@ -17,7 +17,7 @@ In Svelte, when you want to render asynchonous content data on the server, you c
<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 build to solve this problem. You probably won't need this very often -- it will probably be used behind the scenes by whatever datafetching library you use. For example, it powers [remote functions in SvelteKit](/docs/kit/remote-functions).
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:
@ -30,7 +30,7 @@ To fix the example above:
// 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());
const user = await hydratable('user', getUser);
</script>
<h1>{user.name}</h1>
@ -45,63 +45,21 @@ 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.
## Imperative API
## Serialization
If you're writing a library with separate server and client exports, it may be more convenient to use the imperative API:
```ts
import { hydratable } from 'svelte';
const value = hydratable.get('foo'); // only works on the client
const hasValue = hydratable.has('foo');
hydratable.set('foo', 'whatever value you want'); // only works on the server
```
## Custom serialization
By default, Svelte uses [`devalue`](https://npmjs.com/package/devalue) to serialize your data on the server so that decoding it on the client requires no dependencies. If you need to serialize additional things not covered by `devalue`, you can provide your own transport mechanisms by writing custom `encode` and `decode` methods.
### `encode`
Encode receives a value and outputs _the JavaScript code necessary to create that value on the client_. For example, Svelte's built-in encoder looks like this:
```js
import * as devalue from 'devalue';
/**
* @param {any} value
*/
function encode (value) {
return devalue.uneval(value);
}
encode(['hello', 'world']); // outputs `['hello', 'world']`
```
### `decode`
`decode` accepts whatever the JavaScript that `encode` outputs resolves to, and returns whatever the final value from `hydratable` should be.
### Usage
When using the isomorphic API, you must provide either `encode` or `decode`, depending on the environment. This enables your bundler to treeshake the unneeded code during your build:
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';
import { BROWSER } from 'esm-env';
import { encode, decode } from '$lib/encoders';
const random = hydratable('random', () => Math.random(), { transport: BROWSER ? { decode } : { encode }});
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2)
}
});
</script>
```
For the imperative API, you just provide `encode` or `decode` depending on which method you're using:
```ts
import { hydratable } from 'svelte';
import { encode, decode } from '$lib/encoders';
const random = hydratable.get('random', { decode });
hydratable.set('random', Math.random(), { encode });
{await promises.one}
{await promises.two}
```

@ -566,7 +566,6 @@ async function run_test_variant(
}
} catch (err) {
if (config.runtime_error) {
console.log(err);
assert.include((err as Error).message, config.runtime_error);
} else if (config.error && !unintended_error) {
assert.include((err as Error).message, config.error);

Loading…
Cancel
Save