Hydration — это название, данное процессу в JavaScript-фреймворках для инициализации страницы в браузере после того, как она ранее была обработана сервером. Хотя сервер может создать исходный HTML-код, нам необходимо дополнить этот вывод обработчиками событий и инициализировать состояние нашего приложения таким образом, чтобы оно могло быть интерактивным в браузере.
В большинстве фреймворков этот процесс требует больших затрат, когда наша страница загружается впервые. В зависимости от того, сколько времени требуется для загрузки JavaScript и завершения гидратации, мы можем представить страницу, которая выглядит интерактивной, но на самом деле таковой не является. Это может быть ужасно для пользователя и особенно ощущается на младших устройствах и медленных сетях.
Можно подумать, что есть много способов решить эту проблему. И здесь. Но ни один из них не является надежным. Авторы библиотек крутились вокруг этого в течение многих лет, постепенно совершенствуя методы. Итак, сегодня рассмотрим тему Hydration специально, чтобы лучше понять, с чем мы имеем дело.
Итак, вы взяли свой любимый клиентский рендеринг JavaScript Framework и теперь используете его для рендеринга на сервере. Лучше SEO. Лучшая производительность.
Это распространенное заблуждение. Просто сервер, отображающий ваш SPA, не может внезапно все исправить. На самом деле, более чем вероятно, что вы увеличили полезную нагрузку JavaScript и можете иметь еще больше времени, пока ваше приложение не станет интерактивным, чем когда вы просто выполняли клиентский рендеринг.
Готовый к Hydration код большинства фреймворков больше, чем их типичный клиентский код, потому что в конечном итоге он должен делать обе вещи. Сначала это может быть только Hydration, но, поскольку ваша структура позволяет рендеринг на стороне клиента, для этого также нужен код.
И теперь вместо того, чтобы немедленно отправлять нашу в основном пустую HTML-страницу, которая может отображать некоторую обратную связь с пользователем во время загрузки данных, нам нужно дождаться загрузки и рендеринга всей страницы на сервере, прежде чем мы запустим процесс, аналогичный рендерингу в браузер. Эта страница также намного больше, поскольку она содержит весь наш HTML и данные, необходимые нашему приложению для самозагрузки.
Не все так плохо. Вообще говоря, ваш основной контент должен быть виден быстрее, поскольку вам не нужно было ждать, пока браузер выполнит дополнительный цикл загрузки JavaScript, прежде чем он начнет работать. Но вы также задержали загрузку ресурсов, включая JavaScript, необходимый для Hydration вашего приложения.
Когда дело доходит до Hydration на стороне клиента, есть две довольно неприятные характеристики. Во-первых, мы рендерим на сервере, и теперь нам нужно снова отрендерить его в браузере, чтобы все Hydration. Во-вторых, мы склонны посылать все по сети дважды. Один раз в HTML и один раз в JavaScript.
Обычно он отправляется в трех формах:
Представления шаблона находятся как в вашем связанном JavaScript, так и в отображаемом HTML, а данные присутствуют как обычно в виде тега скрипта, отображаемого на странице, так и частично в окончательном HTML.
При клиентском рендеринге мы просто отправляли шаблон и запрашивали данные для его рендеринга. Дублирования нет. Однако мы находимся во власти сети, загружающей пакет JavaScript, прежде чем мы сможем что-либо показать.
Таким образом, имея реализованный HTML-код с сервера, мы получаем все преимущества серверного рендеринга. Это позволяет нам не зависеть от времени загрузки JavaScript для отображения нашего сайта. Но как нам бороться с неотъемлемыми дополнительными накладными расходами, связанными с серверным рендерингом?
Примеры: Remix , SvelteKit , SolidStart .
Одна из идей, которую мы видели в ряде фреймворков JS SSR, — это возможность просто удалить <script>
тег на некоторых страницах. Эти страницы являются статическими и не нуждаются в JavaScript. Отсутствие JavaScript означает отсутствие дополнительного сетевого трафика, сериализации данных и Hydration.
Ну, конечно, если вам не нужен JavaScript. Вы можете добавить немного ванильного JavaScript на страницу, и, может быть, для чего-то это подойдет, но это далеко не желательно. Вы в основном создаете второй слой приложения.
Это не бессмысленный способ приблизиться к этому. Но на самом деле, как только вы добавляете динамические элементы и хотите использовать структуру, вы втягиваете все в себя. Этот подход — это то, что мы всегда могли делать с SSR практически со всеми существующими решениями, но он также не особенно гибкий. Это крутой трюк, но на самом деле он не решает большинство проблем.
Примеры: Astro
Этот подход называется «прогрессивным» или «ленивым» Hydration. Это не означает, что мы не будем загружать JavaScript. Просто это не загрузит его сразу. Давайте загрузим его при взаимодействии, будь то щелчок или наведение курсора или когда что-то прокручивается в поле зрения. Дополнительным преимуществом этого является то, что если мы никогда не взаимодействуем с частью страницы, возможно, мы даже никогда не отправляем этот JavaScript. Но есть одна загвоздка.
Большинству фреймворков JavaScript требуется Hydration сверху вниз. Это верно как для React, так и для Svelte. Поэтому, если ваше приложение содержит общий корень (как это делают одностраничные приложения), нам нужно его загрузить. И если наше дерево рендеринга не очень мелкое, вы можете обнаружить, что когда вы нажимаете эту кнопку на полпути вниз по экрану, вам все равно нужно загружать и гидратировать огромное количество кода. Откладывание накладных расходов до тех пор, пока пользователь что-то не сделает, на самом деле не лучше. Это, вероятно, хуже, так как теперь это гарантия того, что вы заставите их ждать. Но ваш сайт будет иметь лучшую оценку Lighthouse.
Так что, возможно, это может принести пользу приложениям с широкими и мелкими деревьями, но это не совсем обычный случай в вашем современном одностраничном приложении (SPA). Наши шаблоны, связанные с маршрутизацией на стороне клиента, поставщиками контекста и граничными компонентами (приостановка, ошибка и т. д.), заставляют нас строить вещи глубоко.
Сам по себе этот подход также ничего не может сделать, чтобы избавить нас от сериализации всех данных, которые можно было бы использовать. Мы не знаем, что в конечном итоге загрузится, поэтому все это должно быть доступно.
Примеры: Компилятор Prism
Другая мысль, которая обычно возникает у людей сразу же, заключается в том, что мы можем реконструировать свое состояние из отрендеренного HTML. Вместо отправки большого двоичного объекта JSON вы должны инициализировать состояние из значений, вставленных в HTML. На первый взгляд, это не такая уж ужасная идея. Проблема в том, что модели для просмотра не всегда 1 к 1.
Если у вас есть производные данные, попытка вернуться к оригиналу для повторного получения во многих случаях невозможна. Например, если вы показываете отформатированную метку времени в своем HTML, вы, возможно, не закодировали секунды, но что вы будете делать, если другой параметр пользовательского интерфейса позволяет вам изменить формат, который делает это.
К сожалению, это относится не только к состоянию, которое мы инициализируем, но и к данным, поступающим в базы данных и API. И часто это не так просто, как не сериализовать все это на странице. Помните, что в большинстве случаев Hydration снова запускает приложение при инициализации в браузере сверху вниз. Службы извлечения изоморфных данных часто пытаются повторно загрузить их в браузере в это время, если вы не отправите их и не настроите какой-то кеш на стороне клиента с данными.
Представьте веб-страницу в основном в виде статического HTML, который не нужно повторно отображать или обрабатывать в браузере. Внутри него есть несколько мест, где пользователь может взаимодействовать, которые мы можем назвать нашими «островами». Этот подход часто называют частичной гидратацией, поскольку нам нужно только гидратировать эти острова и можно пропустить отправку JavaScript для чего-либо еще на странице.
С приложением, спроектированным таким образом, нам нужно только сериализовать входные данные или реквизиты для компонентов верхнего уровня, поскольку мы знаем, что ничего над ними не имеет состояния. Мы знаем на 100%, что он никогда не сможет перерендериться. То, что за пределами островов, не способно измениться. Таким образом, мы можем решить большую часть проблемы двойных данных, просто никогда не отправляя данные, которые мы не используем. Если это не вход верхнего уровня, он никак не может понадобиться в браузере.
Но где мы устанавливаем границы? Делать это на уровне компонентов разумно, поскольку это то, что мы, люди, можем понять. Но чем более гранулированы острова, тем они эффективнее. Когда что-то под островом может перерисовываться, вам нужно отправить этот код в браузер.
Одним из решений является разработка компилятора, достаточно умного, чтобы определять состояние на уровне подкомпонента. Таким образом, из нашего дерева удаляются не только статические ветки, но даже те, которые вложены в компоненты с отслеживанием состояния. Но такому компилятору потребуется специализированный предметно-ориентированный язык (DSL), чтобы его можно было анализировать кросс-модульным способом.
Что еще более важно, острова означают, что сервер отображает каждую страницу при навигации. Этот многостраничный (MPA) подход является классическим способом работы в Интернете. Но это означает отсутствие переходов на стороне клиента и потерю состояния клиента при навигации. По сути, частичная Hydration — это улучшенная версия наших статических маршрутов, описанных выше. Тот, где вы платите только за те функции, которыми пользуетесь.
Примеры: Qwik
Если частичная Hydration — это обновленная версия наших статических маршрутов, то Hydration вне порядка — это улучшение отложенной загрузки. Что, если бы мы не были ограничены типичными фреймворками нисходящего рендеринга для Hydration. Это позволяет этой кнопке на полпути вниз по странице гидратироваться независимо от того, загружаете ли вы кучу клиентских менеджеров маршрутизации и состояния на странице над ней в иерархии компонентов.
Это имеет довольно жесткое ограничение. Для того чтобы это работало, компонент должен иметь все необходимое для первоначальной работы независимо от своего родителя. Но компоненты имеют прямую связь со своими родителями, что выражается через их входные данные или «реквизиты».
Одним из решений является внедрение зависимостей для получения всех входных данных в соответствующих компонентах. Связь не является прямой. И на серверном рендеринге входные данные всех компонентов могут быть сериализованы.
Но это также относится к дочерним элементам, передаваемым в наши компоненты. Они должны быть полностью отрендерены заранее.
Это делает этот подход совершенно другим чувством для развития, поскольку правила взаимодействия родителей и детей, к которым мы привыкли, должны быть организованы и ограничены. И, как и отложенная загрузка, этот подход не спасает нас от дублирования данных, поскольку, хотя он может выполнять гидратацию довольно детально, он никогда не знает, какие компоненты на самом деле нужно отправить в браузер.
Примеры: компоненты React Server
Что, если бы вы могли использовать частичную гидратацию, а затем повторно визуализировать статические части на сервере? Если бы вы это сделали, у вас были бы серверные компоненты. Вы получите те же преимущества, что и частичная гидратация, с уменьшенным размером кода компонента и удалением повторяющихся данных, но не откажетесь от сохранения состояния на стороне клиента при навигации.
Проблема заключается в том, что для повторного рендеринга статических частей на сервере вам нужен специальный формат данных, чтобы иметь возможность сравнивать с ним существующий HTML. Вам также необходимо поддерживать нормальный HTML-рендеринг сервера для первоначального рендеринга. Это означает гораздо более сложный этап сборки и другой вид компиляции и связывания между этими серверными и клиентскими компонентами.
Более того, даже если вы удалили дополнительные накладные расходы, вам потребуется больше времени выполнения в браузере, чтобы это работало. Таким образом, сложность этой системы, вероятно, не компенсирует затраты, пока вы не перейдете к более крупным веб-сайтам и приложениям. Но когда вы достигли этого порога, кажется, что небо — это предел. Возможно, это не лучший подход для максимизации начальной загрузки страниц, но это уникальный способ сохранить преимущества SPA без масштабирования вашего сайта до бесконечного JavaScript.
Это область, над которой постоянно ведется работа, поэтому постоянно появляются новые методы. И правда в том, что лучшим решением может быть сочетание разных техник.Заключение
Надеемся, теперь у вас есть больше информации о работе, которая велась последние несколько лет, чтобы решить одну из самых сложных проблем современного JavaScript.
Источник статьи: http://dev.to/