Основное приложение Reverb - это всеми любимый монолит Rails. Он отлично обслуживает наш API и уровень просмотра трафика. По мере роста мы добавляли больше интерактивных элементов пользовательского интерфейса, и по мере того, как мы это делали, мы следовали Rails Way ™. Мы украсили наше поведение jQuery, а создание шаблонов оставили Rails. Этот подход, даже в 2016 году, по-прежнему является отличным выбором для небольшой интерактивности пользовательского интерфейса, но через пару лет мы начали искать что-то, что могло бы помочь структурировать наши компоненты JavaScript, которые становились все более сложными.

Во время поиска у нас было несколько критериев:

  • Хорошо работает с приложениями, отрисованными на стороне сервера.
  • Может монтироваться изолированно
  • Позволяет проводить простое модульное тестирование
  • Легко научить разработчиков любого уровня

React отлично подходит для нашего текущего приложения. Нам нужны были изолированные компоненты, которые могли бы инкапсулировать сложную логику представления, которую можно было бы внедрить в любом месте страницы. В прошлом мы добились большого успеха с Ember для автономных приложений, но возможность компоновки React предоставила лучшую модель, позволяющую нам постепенно превращаться в компоненты JS.

У Rails есть проблема с JavaScript

Изначально мы использовали react-rails для монтирования наших компонентов непосредственно внутри представлений Rails. Это обеспечило быстрый способ начать работу, но даже после нескольких компонентов мы наблюдали аналогичные проблемы с нашими компонентами jQuery: зависимости были неявными и открывались через глобальное состояние. Без адекватной поддержки модулей JavaScript или инкрементных перестроек наши компоненты React не решили все проблемы, которые мы хотели решить, но приблизили нас к нашей цели.

Проблема не в самом React, и не в том, что в этом виноват драгоценный камень react-rails (они делают все, что в их силах, поверх существующего конвейера активов). Конвейер ресурсов Rails показывает свой возраст и изо всех сил пытается идти в ногу с инструментами, доступными в экосистеме JavaScript. Мы попытались сколотить несколько решений, которые украсили конвейер активов. Browserify-rails очень помог, но в конце концов мы отказались от конвейера ресурсов в пользу Webpack. JavaScript просто не является первоклассным соображением в экосистеме Rails, и решения, которые опираются на существующий конвейер ресурсов, предлагают больше проблем, чем решения.

Переход на Webpack

Нам потребовалось несколько итераций, но в конце концов мы остановились на Webpack как на нашем отдельном инструменте для сборки ресурсов. Webpack предоставил все необходимые инструменты, и наряду с такими плагинами, как webpack-manifest, мы смогли легко дополнить соглашения о конвейере ресурсов, установленные Rails.

Мы отказались от драгоценного камня react-rails, но позаимствовали его лучшие части; а именно, его помощник просмотра и его библиотека javascript в стиле ujs для рендеринга на стороне клиента. Одним из недостатков этого подхода является то, что изолированные компоненты React по-прежнему должны быть открыты для окна, но любые компоненты, используемые в компоненте, могут быть импортированы, не загрязняя глобальное пространство имен.

Замена самого конвейера ресурсов для нашего JavaScript (и, кстати, наших таблиц стилей) была сложной задачей. Мы начали с того, что изолировали все наши файлы JavaScript React и ES6 в отдельную папку; четкая граница между конвейером ресурсов Rails и удобной точкой входа для нашей сборки webpack.

Как мы реагируем

Используя наш помощник, мы генерируем часть модели DOM, которая позже обрабатывается нашим механизмом рендеринга на стороне клиента. Используя этот подход, мы по-прежнему можем вводить свойства из визуализированного представления на стороне сервера, что позволяет нам создавать динамические компоненты, которым еще не нужен API для получения необходимых данных. Это дает нам фантастический мостик, где мы можем добавлять кусочки React там, где это необходимо.

Я включил сюда пример помощника представления Rails, средства рендеринга на стороне клиента и хлебных крошек, которые мы оставляем на стороне сервера, чтобы соединить сервер с разрывом на стороне клиента.

Наш помощник просмотра очень похож на помощника из react-rails. Нет причин заново изобретать что-то, что работает. Мы используем это для создания пустого div с некоторыми свойствами данных, которые средство визуализации на стороне клиента будет использовать для замены фактическими компонентами React.

Мы используем этот помощник вместе с нашими обычными визуализированными представлениями Rails, что позволяет нам смешивать компоненты React при добавлении интерактивности пользовательского интерфейса. Это также позволяет нам переходить к более крупным компонентам React без необходимости заменять все отображаемое на стороне сервера представление в выпуске большого взрыва.

Помощник отображает небольшой фрагмент в DOM, который становится заполнителем для нашего средства визуализации на стороне клиента React.

После того, как наше представление визуализировано на стороне сервера, наше средство визуализации на стороне клиента сканирует DOM на предмет div, которые ожидают гидратации фактическими компонентами React. Эти компоненты должны быть доступны через окно, но мы заботимся о пространстве имен этих компонентов ввода, чтобы предотвратить конфликты. Более сложные компоненты обычно просто используют реагирующий маршрутизатор в качестве точки входа, где компоненты отображаются на основе текущего маршрута.

С этими инструментами у нас была адаптируемая платформа, которая позволяла нам переходить к визуализированным представлениям на стороне клиента там, где это необходимо, заменять сложные для тестирования взаимодействия с jQuery и, как побочный эффект, дала нам отличную платформу для разработки. Я не могу выразить Достаточно, особенно в большом приложении Rails, насколько расширяющие возможности инструменты, такие как Hot Module Replacement, реальная модульная система, впечатляющие инструменты линтинга и надежные среды тестирования, влияют на нашу способность быстро итерировать взаимодействия и с уверенностью.

Что это было?! Мерцание ?! Я скучаю по представлениям Rails: ’(

Абстракции представлений React были значительным улучшением по сравнению с jQuery, а его простой API означал быстрое принятие разработчиками здесь, в Reverb. Вскоре мы перешли от нескольких изолированных компонентов к целым представлениям, полностью состоящим из компонентов React. Это когда мы сталкиваемся с некоторыми проблемами.

В тех более сложных представлениях, где верхний и нижний колонтитулы отрисовывались на стороне сервера, но тело визуализировалось преимущественно React, нам приходилось ждать, ждать и ждать, пока React взял под контроль DOM и отрендерил в наших компонентах. Это может вызвать неприятные ощущения в пользовательском интерфейсе.

Даже там, где мы тщательно спланировали хорошие пустые состояния, у нас остались компоненты пользовательского интерфейса, которые мгновенно возникли и заставили DOM перемещаться по мере гидратации представлений. Это можно смягчить, угадав размеры div и создав хорошие пустые контейнеры, и мы даже зашли так далеко, что на стороне сервера рендерили наши пустые состояния с помощью Rails только для того, чтобы React удалял эти пустые div по мере их установки, но это было далеко от идеала. Это означало поддержание идентичных частичных файлов DOM как в компонентах React, так и в представлениях Rails, а также умное использование jQuery для удаления этих пустых блоков div после того, как компонент React был на месте.

Мы слышали сирену универсального javascript, но опоздали на вечеринку. Наш бэкэнд не говорил на JavaScript. Было предпринято несколько попыток решить эту проблему в Rails, и мы многому научились, читая примеры response_on_rails @shakacode, но обнаружили, что некоторые из их серверных инструментов JavaScript не совсем соответствуют нашим потребностям. Однако их подход казался разумным, и мы начали искать решение, которое лучше соответствовало бы нашим потребностям.

Как я научился перестать беспокоиться и полюбить Node.js

Теперь, когда наш JS отправлялся в наш интерфейс в пакетах, созданных Webpack, мы решили, что можем доставить тот же пакет в приложение Node.js, чтобы помочь нашему бэкэнду Rails в рендеринге некоторого React на стороне сервера. React поставляется с поддержкой рендеринга на стороне сервера из коробки, но нам пришлось проделать некоторую дополнительную работу, чтобы наши маршрутизируемые компоненты работали хорошо как на стороне клиента, так и на стороне сервера.

В Reverb мы использовали реагирующий маршрутизатор для маршрутизации на стороне клиента, и мы не хотели поддерживать еще одну серверную часть стека маршрутизации. К счастью, после небольшого придумывания, ругательства над документами и большого количества скрещивания пальцев мы смогли собрать что-то, что отлично работало с обеих сторон стека.

Хотя мы отправляем один и тот же файл Routes.jsx на сервер и клиент, код на стороне сервера использует немного другой (не основанный на истории браузера) маршрутизатор для выполнения той же задачи, что и наш маршрутизатор на стороне клиента.

Механизм рендеринга React

Возврат результатов из Rails

Хотя это простое приложение Node.js могло отображать наши компоненты React, нам все еще нужен был способ вернуть их в представления Rails. После нескольких модификаций нашего помощника `response_component` мы смогли обращаться к Node.js, когда это было необходимо.

Поскольку мы развертываем наш механизм рендеринга React вместе с нашим основным стеком Rails, мы используем сокетное соединение для связи между двумя фреймворками. Такое разделение задач позволяет разделять наш React Rendering Engine между множеством разных стеков и языков по мере нашего роста.

Имея эти части на месте, мы можем обслуживать фрагменты React из нашего бэкэнда, которые позже подбираются нашим рендерером на стороне клиента. Это означает кардинальную разницу в UX для тех представлений, которые преимущественно отрисовывались через React, не заставляя нас использовать произвольные пустые контейнеры div. Фактически, мы можем доставить пользователю множество компонентов без побочных эффектов (т. Е. Тех, которые не содержат внешних вызовов API) мгновенно.

Мерцание исчезло!

React был большим подарком для просмотра разработки здесь, в Reverb. С помощью компонентов React мы можем легко провести модульное тестирование наших представлений, сократить разрыв в итерациях с помощью горячей перезагрузки модуля и предоставить изолированные повторно используемые компоненты для всех наших платформ приложений. Подобная работа приближает нас к созданию того опыта, который мы хотим для наших пользователей.

Есть вопросы? Не стесняйтесь обращаться к нам. В ближайшие недели мы будем оценивать возможность использования некоторых из этих компонентов в проектах с открытым исходным кодом, так что будьте внимательны, вам может понравиться то, что вы слышите.

Эта работа стала результатом огромных усилий:

Терон Хьюмистон

Джо Леверинг

Джефф Массанек

Джефф Мейерс