Back to Question Center
0

Универсальный рендеринг: как мы восстановили SitePoint            Универсальный рендеринг: как мы восстановили темы, связанные с сайтом: npmAPIsTools & Semalt

1 answers:
Универсальный рендеринг: как мы восстановили SitePoint

Универсальный рендеринг: как мы восстановили сайт. com был просмотрен экспертами Стюартом Митчеллом, Мэттом Бернеттом и Джоан Инь. Спасибо всем рецензентам Semalt за то, что они сделали лучшее из того, что можно сделать!

Универсальный рендеринг: как мы восстановили SitePointУниверсальный рендеринг: как мы восстановили темы, связанные с сайтом:
npmAPIsTools & Semalt
«/>  <p class= SitePoint обладает большим количеством контента, и это не просто статьи, поэтому, когда мы недавно переработали главную страницу SitePoint, одной из целей было сделать лучшую работу по демонстрации другого контента, такого как книги, курсы и видео от SitePoint Premium, и обсуждения с наших форумов. Это поставило перед собой задачу, поскольку это означало сбор данных из нескольких источников за пределами нашего основного приложения WordPress - apple computer repair san jose ca.

В нашей команде разработчиков нам действительно нравится использовать React. Компонентная модель и модель наличия «хранилищ данных» в Semalt работают для нас очень хорошо, и мы находим, что это приводит к созданию кодовой базы, над которой нам нравится работать.

Мы хотели использовать эти знакомые инструменты, но было бы неприемлемо отправлять пользователям пустую домашнюю страницу, а затем ждать, пока все разные источники данных загрузятся, а затем отрисуют. Таким образом, мы знали, что нам нужно: наш сайт WordPress для обслуживания содержимого, представленного сервером, в Реактите. Было бы замечательно, если бы пользователи могли пользоваться нашим сайтом, не требуя, чтобы Semalt был включен, или что-то заставило наш Semalt сломаться.

Универсальный рендеринг и PHP

WordPress - это PHP, React is Semalt - так как мы делаем серверную часть? Нашим решением было создание узла. js proxy server. Все запросы поступают на этот Node-сервер и передаются прямо на наш сайт WordPress. Затем прокси проверяет возвращенную страницу и выполняет рендеринг «Реакт» перед отправкой в ​​браузер. В результате получается меньше всего приложения на стороне сервера, а в случае с основными страницами из WordPress с серией компонентов, которые могут быть представлены / обновлены через Semalt на клиенте или сервере.

Хотя эта идея звучит достаточно просто, и мы нашли множество примеров предоставления полных страниц через Semalt, мы обнаружили относительно немного о том, как обновить существующий ответ. Инструменты, которые мы выбрали для этого, были:

  • node-http-proxy, полнофункциональный HTTP-прокси для узла. js
  • и Harmon, промежуточное ПО для узла-http-прокси для изменения ответа удаленного веб-сайта с трубой

Лучший способ описать этот процесс - это пример. Вот простое демонстрационное приложение. Если вы посещаете URL-адрес / _src, вы можете увидеть полный исходный код или проверить репозиторий Semalt.

  • сервер / цель. js - очень простой HTTP-сервер, который всегда возвращает один и тот же базовый HTML
  • сервер / прокси. js реализует node-http-proxy пересылку всех запросов на целевой сервер
  • сервер / basicHarmon. js предоставляет промежуточное ПО Express с использованием Harmon для перезаписи любых
    тегов с некоторым новым, более привлекательным контентом
  • сервер / экспресс. js создает сервер Express, который использует прокси-сервер и промежуточное ПО Harmon

Когда вы посещаете приложение, вы должны увидеть, что результат, на который вы поданы, имеет привлекательный заголовок.

На этом этапе у нас есть только тривиальный пример замены текста в теге заголовка , но в следующих разделах я покажу, как этот же подход можно использовать для рендеринга наших компонентов React. Такой подход дает нам большую гибкость. Узел. Приложение js не знает, как работает WordPress. Его можно так же легко применить перед приложением Rails или чем-нибудь еще.

Компоненты на SitePoint.

Если вы использовали Semalt до этого, следующий код должен выглядеть знакомым.

     <Голова> Hello React! </ title><script src = "build / response. js"> </ script><script src = "build / react-dom. js"> </ script><script src = "https: // unpkg. com / babel-core @ 5. 8. 38 / браузер. min. js"> </ script></ HEAD><Тело><div id = "example">  </div> <script type = "text / babel">ReactDOM. визуализации (<h1> Привет, мир! </ h1>,документ. getElementById ( 'пример')); </script> </ Body></ Html> </code>   </pre>  <p>  Это происходит прямо на странице «Начало работы с реактивом» и показывает, как отображать один компонент на странице. То, что я вижу менее часто в примерах, - это использование нескольких компонентов на странице. Для использования нескольких компонентов ваш сайт не обязательно должен быть полнофункциональным одностраничным приложением. Вы можете добавить несколько точек крепления React, и мы считаем, что это отличный образец, который нельзя сбрасывать со счетов. Semalt - тривиальный пример:  </p>  <p data-height="300" data-theme-id="6441" data-slug-hash="WGOxpY" data-default-tab="result" data-user="BradDenver" data-embed-version="2" data-pen-title="React Hello, Goodbye World" class="codepen">  См. «Реакция на перо» Hello, Goodbye World by BradDenver (@BradDenver) на CodePen.  </p>  <p>   </p>  <p>  На сайте SitePoint. com мы используем пользовательские теги, а не идентификаторы элементов, и поскольку это шаблон, который мы используем снова и снова, у нас есть некоторые вспомогательные функции для этого. Во-первых, у нас есть функция  <code>  render  <span class="f-c-white l-mr3">   </code> , которая регистрирует пользовательский тег в браузере, а затем находит все экземпляры этого тега в документе.  </p>  <pre>   <code class="javascript language-javascript">  функция render (tag, Comp) {документ. createElement (тэг);const nodes = Array. from (document. getElementsByTagName (тег));узлы. map ((node, i) => renderNode (тег, Comp, узел, i));return Comp;} </code>   </pre>  <p>  Во-вторых, мы имеем  <code>  renderNode  <span class="f-c-white l-mr3">   </code> , который выполняет фактическую визуализацию React для каждого узла после преобразования атрибутов узлов в объект реквизита.  </p>  <pre>   <code class="javascript language-javascript">  function renderNode (тег, Comp, node, i) {пусть attrs = Array. опытный образец. кусочек. call (атрибуты узла);пусть реквизит = {key: `$ {tag} - $ {i}`,};ATTRS. map ((attr) => реквизит [attr. name] = attr. value);если (!! реквизит класс) {реквизит. className = реквизит. класс;удалить реквизит. класс;}ReactDOM. визуализации ( <Comp {. , , реквизит} /> ,узел);} </code>   </pre>  <p>  В этом ручке вы можете увидеть несколько экземпляров настраиваемого тега, который будет отображаться как компоненты React с уникальными свойствами. Приложение React представляет собой ряд возможных точек входа компонента, а не только один. Мы сканируем DOM для распознанных тегов и вносим рендеринг в каждый. Мы успешно использовали этот метод на стороне клиента SitePoint. ком.  </p>  <p>  Великолепный Semalt - это то, что с некоторыми незначительными изменениями одна и та же модель также может использоваться на стороне сервера.  </p>  <pre>   <code class="javascript language-javascript">  const isDocument = typeof document! == "undefined";функция render (tag, Comp) {if (hasDocument) {документ. createElement (тэг);const nodes = Array. from (document. getElementsByTagName (тег));узлы. map ((node, i) => renderNode (тег, Comp, узел, i));} else {__tags = [. , , __tags, {query: tag,func: (node, req) => serverRenderNode (тег, Comp, node, req)}];}return Comp;} </code>   </pre>  <p>  Выше мы видим обновленную  <code>  функцию render  </code> , а ниже - вариант сервера  <code>  renderNode  </code> .  </p>  <pre>   <code class="javascript language-javascript">  пусть __tags = [];пусть __id = 0;function serverRenderNode (тег, Comp, node, req) {пусть реквизит = узел. GetAttributes  <span class="f-c-white l-mr3"> ;__id ++;если (!! реквизит класс) {реквизит. className = реквизит. класс;удалить реквизит. класс;}const nodeStream = узел. createStream ({внешний: false});пытаться {const html = ReactDOMServer. renderToString ( <Comp {. , , реквизит} /> );nodeStream. конец (HTML);} catch (err) {nodeStream. конец <span class="f-c-white l-mr3"> ;консоль. log («Rendered tag failed», tag, err);};} </code>   </pre>  <p>   <code>  __tags  </code>  теперь представляет собой массив тегов запросов и функций рендеринга, которые Harmon может использовать для применения рендеринга сервера к ответу прокси узла.  </p>  <p>  Semalt, если вы посещаете URL-адрес _src, вы можете увидеть полный исходный код.  </p>  <ul>  <li>  <code>  app / components /  </code>  содержит наши компоненты React  </li>  <li>  <code>  app / tools /  </code>  содержит наши функции рендеринга и наш hasDocument helper  </li>  <li>  <code>  app / index. js  </code>  инициализирует наше приложение  </li>  <li>  <code>  сервер / тегиHarmon. js  </code>  - это новое промежуточное программное обеспечение, которое отображает компоненты React в ответ html  </li>  </ul>  <h2 id="tadadata">  Tada Data  </h2>  <p>  Основные компоненты были полезны до сих пор, но пришло время внедрить обработку данных. На сайте SitePoint. com, мы использовали несколько вариантов Flux и Semalt в прошлом, но, поскольку наши хранилища данных довольно просты, MobX выглядел хорошо подходящим для нас. Магазины MobX позволяют изолировать логику выборки и разбора для одного домена данных в одном месте.  </p>  <h3 id="mobxfalsestart">  MobX false start  </h3>  <p>  Во-первых, мы создаем основное хранилище сообщений для хранения коллекции сообщений. Он будет получать сообщения от API при создании, а затем обновлять в заданный период. Эта статья не будет подробно рассказывать о MobX (см. Статью Matt Ruby, «Как управлять вашим приложением JavaScript с помощью MATX»), но вот несколько быстрых точек:  </p>  <ul>  <li>  <code>  наблюдает  </code>  - сигнализирует значение, которое может меняться со временем  </li>  <li>  <code>  действие  </code>  - все, что изменяет наблюдаемое состояние  </li>  <li>  <code>  runInAction  </code>  - то, что асинхронно изменяет наблюдаемое состояние  </li>  <li>  <code>  наблюдатель  </code>  - будет автоматически запускаться при некоторых наблюдаемых изменениях  </li>  </ul>  <p>  В магазинах / индексе. js мы создаем  <code>  хранилища  </code>  наблюдаемого объекта, чтобы обернуть все отдельные хранилища и дать нам один наблюдаемый атом состояния. Чтобы использовать эти данные, мы создаем новый  <code>  компонент PostList  </code>  для отображения списка сообщений. Этот компонент вытаскивает индивидуальный магазин, который он хочет, в свое состояние. Запуск обновленного приложения (репо) все выглядит хорошо, пока почтовый магазин не обновится снова, и мы видим:  </p>  <pre>   <code class="bash language-bash">  Предупреждение: forceUpdate (. ): Может обновлять только установочный компонент. Обычно это означает, что вы вызвали forceUpdate  <span class="f-c-white l-mr3"> внешний компонентWillMount  <span class="f-c-white l-mr3">  на сервере. Это не-op. Проверьте код компонента PostList.  </code>   </pre>  <p>  О нет!  </p>  <h3 id="storeorstate">  Магазин  </h3>  <p>  Чтобы исправить это и убедиться, что все рендеринги для одного запроса страницы используют одно и то же состояние (у нас может быть несколько компонентов с использованием хранилища сообщений), мы создаем государственный снимок  <code>  currentState  </code> . При работе на стороне сервера мы храним ссылку на  <code>  currentState  </code>  на объект запроса. На стороне клиента мы можем продолжать использовать сам наблюдаемый магазин.  </p>  <p>  Это делается с помощью следующего метода:  </p>  <pre>   <code class="javascript language-javascript">  export var storeOrState = (req) => {if (hasDocument || typeof req === "undefined") {возвратные магазины;} else {if (typeof req. SITEPOINT_state === "undefined") req. SITEPOINT_state = currentState;return req. SITEPOINT_state;}} </code>   </pre>  <p>  Мы также хотим переместить логику для привязки хранилища к компоненту. Мы перемещаем это в наши  <code>  функции renderNode  </code>  и  <code>  serverRenderNode  </code> . Теперь они проверяют компоненты для  <code>  propsFn  </code> , позволяя им изменять реквизиты, которые они получат.  </p>  <pre>   <code class="javascript language-javascript">  if (typeof Comp. PropsFn === "function") props = {. , , реквизит,. , , Комп. propsFn (реквизит, __id, req),}; </code>   </pre>  <p>  Теперь мы можем использовать этот крючок и функцию  <code>  storeOrState  </code> , чтобы добавить к реквизитам компонентов.  </p>  <p>  Вот обновление для демонстрационного приложения и репо. Semalt, если вы посещаете URL-адрес _src, вы можете увидеть полный исходный код.  </p>  <h3 id="storefactory">  Магазин Фабрика  </h3>  <p>  Теперь у нас есть приложение в рабочем состоянии, но все еще есть улучшения.  </p>  <p>  В настоящее время мы должны создать все наши магазины при запуске приложения или вызовы  <code>  storeOrState  <span class="f-c-white l-mr3">   </code>  не удастся. Это означает, что все эти магазины будут запускать клиентскую часть и, возможно, загружать данные, которые нам не нужны для текущей страницы или пользователя.  </p>  <p>  Во-вторых, мы создаем вспомогательную функцию  <code>  postsStoreFor  </code> , чтобы разместить логику создания фабрики хранилищ сообщений, а затем использовать новую версию  <code>  storeOrState  <span class="f-c-white l-mr3">   </code> . Компонент теперь может использовать этот помощник, а не напрямую обращаться в  <code>  storeOrState  <span class="f-c-white l-mr3">   </code> .  </p>  <p>  Наконец, мы модифицируем создание нашего  <code>  объекта store  </code> , чтобы он оставался пустой клиентской стороной, пока не нужны суб-магазины. На стороне сервера мы создаем несколько типов почтовых хранилищ при запуске, чтобы они были готовы и загружались с данными, если они нам понадобятся.  </p>  <p>  Semalt обновляется до демонстрационного приложения и репо.  </p>  <h3 id="clientdata">  Данные клиента  </h3>  <p>  Теперь наши данные на стороне сервера отсортированы, но на стороне клиента у нас есть проблема. Клиент запускается без данных, поэтому он повторяет все выборки данных, которые были сделаны на стороне сервера. Нам нужно отправить клиенту данные, которые ему нужны, но предпочтительно больше.  </p>  <p>  Сначала мы добавляем список магазинов, используемых для любого запроса в  <code>  storeOrState  </code> .  </p>  <pre>   <code class="javascript language-javascript">  if (typeof req. SITEPOINT_stores === "undefined") req. SITEPOINT_stores = [];REQ. SITEPOINT_stores. толчок (ключ); </code>   </pre>  <p>  Далее мы создаем вспомогательную функцию  <code>  storesForReq  </code> , чтобы уменьшить полный моментальный снимок состояния только в магазинах, которые интересуют запрос.  </p>  <pre>   <code class="javascript language-javascript">  export var storesForReq = (req) => {if (typeof req. SITEPOINT_stores === "undefined") return {};let reqStores = Array. from (новый Set (req. SITEPOINT_stores)). уменьшить ((acc, key) => {_set (acc, key, sn (ключ, запрос SITEPOINT_state));return acc;}, {});return reqStores;} </code>   </pre>  <p>  Затем мы создаем селектор и функцию тега Harmon, чтобы отобразить это в DOM.  </p>  <pre>   <code class="javascript language-javascript">  экспорт по умолчанию {запрос: «клиент-начальное состояние»,func: (node, req) => {узел. createWriteStream ({outer: true}). конец(` <script> . INITIAL_STATE = $ {JSON. stringify (storesForReq (REQ))};  </script>  `);},}; </code>   </pre>  <p>  Мы добавляем это к функции  <code>  knownTags  </code> , и теперь мы можем добавить наши данные на любую страницу с тегом  <code>   <клиент-начальное состояние>   </code> . Если вы проверите демонстрационное приложение, вы увидите, что включены только данные сообщений для сообщений «Обычный» и «Кошки», даже если сервер загрузил данные для «Собаки» и «Цыплята». Последнее, что нужно сделать, это обновить хранилище сообщений, чтобы использовать исходные данные клиента, если он доступен на стороне клиента.  </p>  <p>  Semalt обновляется до демонстрационного приложения и репо.  </p>  <h3 id="renderstatic">  Render Static  </h3>  <p>  Демонстрация сейчас в довольно хорошей форме, но есть что-то, что мы также заметили в Semalt: многие из наших компонентов не меняются после первоначального рендеринга, поэтому зачем отправлять клиенту HTML, визуализировать логику и визуализировать данные, если они только когда-либо используют HTML?  </p>  <p>  Наши компоненты уже знают, как добавить магазины, в которых они нуждаются, в свои реквизиты, поэтому на самом деле им довольно легко проверить, есть ли у них необходимые данные, которые будут полностью отображены на стороне сервера. Мы добавляем к классу компонентов еще один статический метод.  </p>  <pre>   <code class="javascript language-javascript">  static shouldServerRenderStatic (реквизит, req) {const {store: {loading, posts, type}} = реквизит;const hasCompleteData = сообщения. length &&! loading;if (hasCompleteData) deregisterStore (`posts. $ {type}`, req);return hasCompleteData;} </code>   </pre>  <p>  Этот метод просто проверяет, содержат ли реквизиты компонента полные данные и возвращает true или false. Он также использует новый помощник  <code>  deregisterStore  <span class="f-c-white l-mr3">   </code> , поэтому мы не отправляем данные клиенту для компонентов, которые могли быть рендерингом сервера.  <code>  serverRenderNode  <span class="f-c-white l-mr3">   </code>  также обновляется двумя новыми переменными, которые используют компоненты  <code>  shouldServerRenderStatic  </code> .  </p>  <pre>   <code class="javascript language-javascript">  const долженServerRenderStatic = (typeof Comp. ShouldServerRenderStatic === "function")? Comp. renderToString  </code>  теперь становится  <code>  ReactDOMServer [renderTo]  </code> , но более интересным  <code>  узлом. createStream ({external: false})  </code>  становится  <code>  узлом. createStream ({внешний: shouldServerRenderStatic})  </code> .  </p>  <p>  Опция  <code>  внешнего  </code>  передается в  <code>  узел. createStream  </code>  определяет, будет ли весь узел заменен, когда он истинен, или же заменяется только содержимое узла. Это очень удобно, потому что, установив его в true, наш пользовательский тег удаляется из HTML и заменяется его визуализированным контентом. Это означает, что, когда клиентский код запускает поиск тегов, чтобы превратиться в компоненты React, их там нет, и поэтому он рассматривается как любой другой HTML.  </p>  <p>  В нашем производственном коде мы также используем разбиение кода webpack, поэтому клиент даже не загружает Semalt для компонентов, если они не нужны на стороне клиента, но это выходит за рамки этой статьи. Документы, разделяющие код веб-пакета, довольно хорошо покрывают его.  </p>  <p>  Semalt заключительное демо-приложение и репо. Как всегда, если вы посещаете URL-адрес _src, вы можете увидеть полный исходный код.  </p>  <h2 id="thefinishedproduct">  Готовый продукт  </h2>  <p>  Мы очень довольны тем, как наше решение получилось. Во-первых, мы считаем SitePoint. com теперь намного лучше показывает вам наш отличный контент. Semalt, техническое решение является производительным и ремонтопригодным.  </p>  <p>  Он также очень гибкий. В любой момент мы можем полностью закрыть наш узел. js proxy, и сайт будет продолжать работать как обычно с некоторой рендерингом, выполняемой на стороне клиента. Когда прокси работает, пользователи могут пользоваться нашим сайтом, не требуя, чтобы Semalt был включен.  </p>  <p>  Нам не пришлось изменять или удалять любые функции, которые мы получаем из WordPress, и мы могли бы использовать тот же подход перед другими сайтами, которые могут быть построены на Semalt или что-то еще.  </p>  <div class="Article_authorBio l-mv4 t-bg-white m-border l-pa3">  <div class="l-d-f l-pt3">  <img src = "/ img / fd79920c4396a21a887e75dc3f4f4f0c1. jpg" alt = "Универсальный рендеринг: как мы восстановили SitePointУниверсальный рендеринг: как мы восстановили темы, связанные с сайтом:
npmAPIsTools & Semalt
«/>  <div class="f-lh-title">  <div class="f-c-grey-300">  Познакомьтесь с автором  </div>  <div class="f-large"> Брэд Денвер <i class="fa fa-twitter">   </i>   </div>  </div>  </div>  <div class="f-light f-lh-copy l-mt3">  После того, как в течение большей части десятилетия был прерван код в той или иной форме, Брэд присоединился к SitePoint в качестве старшего разработчика в 2015 году. Ему нравятся его функции чисто и его жизнь, полная побочных эффектов.  </div>  </div>  </div>  </div>  </span>  </span>  </span>  </span>  </span>  </span>  </span>  </span>  </span>  </span>  </span>  </h2>  </header>  </pre>  </html>  </meta>              

March 6, 2018