Данные с поддержкой гидратации
В Svelte, когда нужно отрендерить на сервере данные, полученные асинхронно, достаточно просто сделать await. Это здорово! Однако есть один подводный камень: при гидратации на клиенте Svelte вынужден заново выполнить эту асинхронную работу, и весь процесс гидратации блокируется ровно на то время, которое требуется для её завершения:
<script> import { getUser } from 'my-database-library';
// Это получит пользователя на сервере, отрендерит имя пользователя в <h1>, // а затем во время гидратации на клиенте снова получит пользователя, // блокируя гидратацию до тех пор, пока запрос не завершится. const user = await getUser();</script>
<h1>{user.name}</h1>Это, конечно, глупо. Если мы уже проделали всю тяжёлую работу по получению данных на сервере, нет никакого смысла делать это заново во время гидратации на клиенте. hydratable — это низкоуровневый API, созданный именно для решения этой проблемы. Скорее всего, вам редко придётся использовать его напрямую: он будет применяться «под капотом» той библиотекой для загрузки данных, которую вы выберете. Например, именно он лежит в основе удалённых функций в SvelteKit.
Чтобы исправить пример выше:
<script> import { hydratable } from 'svelte'; import { getUser } from 'my-database-library';
// Во время серверного рендеринга результат `getUser` будет сериализован и сохранён, // привязан к указанному ключу и встроен в содержимое `<head>`. // При гидратации вместо повторного вызова `getUser` будет использована уже сериализованная версия. // После завершения гидратации при повторных вызовах будет снова выполняться `getUser`. const user = await hydratable('user', () => getUser());</script>
<h1>{user.name}</h1>Этот API также можно использовать для получения случайных или зависящих от времени значений, которые остаются неизменными между серверным рендерингом и гидратацией. Например, чтобы получить случайное число, которое не будет меняться при гидратации:
import { hydratable } from 'svelte';
const rand = hydratable('random', () => Math.random());Если вы автор библиотеки, обязательно добавляйте префикс с именем вашей библиотеки к ключам значений hydratable, чтобы избежать конфликтов с другими библиотеками.
Сериализация
Заголовок раздела «Сериализация»Все данные, возвращаемые из функции hydratable, должны быть сериализуемыми. Но это вовсе не означает, что вы ограничены только JSON — Svelte использует библиотеку devalue, которая поддерживает сериализацию множества типов, включая Map, Set, URL и BigInt. Полный список смотрите в её документации.
Более того, благодаря магии 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}CSP (Политика безопасности контента)
Заголовок раздела «CSP (Политика безопасности контента)»hydratable добавляет встроенный блок <script> в head, возвращаемый из render. Если вы используете Политику безопасности контента (CSP), этот скрипт, скорее всего, не выполнится. Вы можете передать nonce в render.
const nonce = crypto.randomUUID();
const { head, body } = await render(App, { csp: { nonce }});Это добавит nonce к блоку скрипта, предполагая, что вы позже добавите тот же самый nonce в CSP-заголовок документа, который его содержит:
response.headers.set( 'Content-Security-Policy', `script-src 'nonce-${nonce}'`);Важно, чтобы nonce — что, если отвлечься от британского сленгового значения, означает «число, используемое один раз» — использовался только при динамическом серверном рендеринге индивидуального ответа.
Если же вы генерируете статический HTML заранее, вы должны использовать хэши:
const { head, body, hashes } = await render(App, { csp: { hash: true }});hashes.script будет массивом строк, например ["sha256-abcd123"]. Как и с nonce, хэши должны использоваться в вашем CSP-заголовке:
response.headers.set( 'Content-Security-Policy', `script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`);Мы рекомендуем использовать nonce вместо хэша, если это возможно, так как hash будет мешать потоковому SSR в будущем.