Stimulus 1.0: скромный JavaScript-фреймворк для HTML, который у вас уже есть.

Мы в Basecamp много пишем на JavaScript, но не используем его для создания JavaScript-приложений в современном понимании. Все наши приложения имеют в своей основе отрисованный на стороне сервера HTML, а затем добавляют немного JavaScript, чтобы они сверкали.

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

Это позволяет нам веселиться с продуктивностью, как в былые дни. Возврат к тому моменту, когда один-единственный программист мог стремительно прогрессировать, не застревая на уровнях косвенных или распределенных систем. Раньше все считали, что святым Граалем было ограничить свое серверное приложение созданием JSON для клиентского приложения на основе JavaScript.

Нельзя сказать, что такой подход не имеет ценности для некоторых людей в некоторых случаях. Это как общий подход ко многим приложениям, и, конечно же, подобным Basecamp, это регресс общей простоты и производительности.

И также нельзя сказать, что распространение одностраничных приложений JavaScript не принесло реальных преимуществ. Главный из них - более быстрые и гибкие интерфейсы, освобожденные от полностраничного обновления.

Мы хотели, чтобы Basecamp тоже чувствовал себя таким же. Как будто мы следовали за стадом и переписали все с рендерингом на стороне клиента или полностью внедрили на мобильных устройствах.

Это желание привело нас к двойному решению: Турболинки и Стимул.

Turbolinks вверх высоко, стимул вниз низкий

Прежде чем я перейду к Stimulus, нашему новому скромному фреймворку JavaScript, позвольте мне повторить предложение Turbolinks.

Turbolinks происходит от подхода под названием pjax, разработанного на GitHub. Основная концепция остается прежней. Причина, по которой полное обновление страницы часто кажется медленным, заключается не столько в том, что браузеру приходится обрабатывать кучу HTML-кода, отправленного с сервера. Браузеры действительно хороши и очень быстры в этом. И в большинстве случаев тот факт, что полезная нагрузка HTML имеет тенденцию быть больше, чем полезная нагрузка JSON, также не имеет значения (особенно при сжатии с помощью сжатия). Нет, причина в том, что CSS и JavaScript необходимо повторно инициализировать и повторно применить к странице. Независимо от того, кешируются ли сами файлы. Это может быть довольно медленным, если у вас достаточно CSS и JavaScript.

Чтобы обойти эту повторную инициализацию, Turbolinks поддерживает постоянный процесс, как это делают одностраничные приложения. Но по большей части невидимый. Он перехватывает ссылки и загружает новые страницы через Ajax. Сервер по-прежнему возвращает полностью сформированные HTML-документы.

Сама по себе эта стратегия может сделать большинство действий в большинстве приложений очень быстрыми (если они могут возвращать ответы сервера в течение 100–200 мсек, что неизбежно возможно при кэшировании). Для Basecamp он ускорил переход от страницы к странице примерно в 3 раза. Это дает приложению ощущение отзывчивости и плавности, которые были важной составляющей привлекательности одностраничных приложений.

Но только Turbolinks - это только половина дела. Крупнозернистый. Под оценкой полного изменения страницы находится вся точная точность в пределах одной страницы. Поведение, которое показывает и скрывает элементы, копирует контент в буфер обмена, добавляет новую задачу в список и все другие взаимодействия, которые мы связываем с современным веб-приложением.

До Stimulus в Basecamp использовалось несколько разных стилей и узоров для нанесения этих брызг. Часть кода представляла собой лишь щепотку jQuery, часть кода представляла собой щепотку ванильного JavaScript такого же размера, а часть снова представляла собой более крупные объектно-ориентированные подсистемы. Все они обычно работали с явной обработкой событий, зависящей от атрибута поведения данных.

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

Три основных понятия в Stimulus

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

Он разработан так, чтобы восприниматься как прогрессивное усовершенствование, если вы посмотрите на HTML, к которому он обращается. Таким образом, вы можете посмотреть на один шаблон и узнать, какое поведение на него влияет. Вот пример:

<div data-controller="clipboard">
  PIN: <input data-target="clipboard.source" type="text" value="1234" readonly>
  <button data-action="clipboard#copy">Copy to Clipboard</button>
</div>

Вы можете прочитать это и иметь довольно хорошее представление о том, что происходит. Даже ничего не зная о Stimulus и не глядя на сам код контроллера. Это почти как псевдокод. Это сильно отличается от чтения фрагмента HTML, который имеет внешний файл JavaScript и применяет к нему обработчики событий. Он также поддерживает разделение задач, которое было потеряно во многих современных JavaScript-фреймворках.

Как видите, Stimulus не заботится о создании HTML. Скорее, он прикрепляется к существующему документу HTML. HTML в большинстве случаев отображается на сервере либо при загрузке страницы (первое обращение или через Turbolinks), либо через запрос Ajax, который изменяет DOM.

Stimulus занимается манипулированием этим существующим HTML-документом. Иногда это означает добавление класса CSS, который скрывает элемент, анимирует его или выделяет его. Иногда это означает перестановку элементов в группы. Иногда это означает манипулирование содержимым элемента, например, когда мы преобразуем время UTC, которое может быть кэшировано, в местное время, которое может отображаться.

Бывают случаи, когда вы хотите, чтобы Stimulus создавал новые элементы DOM, и вы определенно можете это сделать. Мы могли бы даже добавить немного сахара, чтобы облегчить задачу в будущем. Но это вариант использования меньшинства. Основное внимание уделяется манипулированию, а не созданию элементов.

Чем Stimulus отличается от основных фреймворков JavaScript

Это отличает Stimulus от большинства современных фреймворков JavaScript. Почти все они сосредоточены на превращении JSON в элементы DOM с помощью какого-либо языка шаблонов. Многие используют эти фреймворки для создания пустой страницы, которая затем заполняется исключительно элементами, созданными с помощью этого преобразования JSON в шаблон.

Стимул также различается по вопросу о состоянии. У большинства фреймворков есть способы поддерживать состояние в объектах JavaScript, а затем отображать HTML на основе этого состояния. Стимул - полная противоположность. Состояние сохраняется в HTML, так что контроллеры могут быть отброшены между изменениями страницы, но все же повторно инициализируются, как и при повторном появлении кешированного HTML.

Это действительно совершенно другая парадигма. Я уверен, что многие ветераны JavaScript-разработчиков, привыкшие работать с современными фреймворками, будут смеяться. И эй, смейся прочь. Если вас устраивает сложность и усилия, необходимые для поддержки приложения в водовороте, скажем, React + Redux, то Turbolinks + Stimulus вам не понравится.

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

Стимулы и связанные с ними идеи были извлечены из дикой природы

В Basecamp мы уже много лет использовали эту архитектуру в нескольких различных версиях Basecamp и других приложений. GitHub использовал аналогичный подход с большим эффектом. Это не только действенная альтернатива общепринятому пониманию того, как выглядит «современное» веб-приложение, но и невероятно убедительная.

Фактически, это похоже на тот же секретный соус, который был у нас в Basecamp, когда мы разрабатывали Ruby on Rails. Ощущение, что современные основные подходы излишне запутаны и что мы можем делать больше, быстрее и с гораздо меньшими затратами.

Более того, вам даже не нужно выбирать. Stimulus и Turbolinks отлично работают в сочетании с другими, более тяжелыми подходами. Если 80% вашего приложения не требует использования большого оборудования, подумайте об использовании для этого нашего двухкомпонентного перфоратора. Затем разверните тяжелую технику для той части вашего приложения, которая действительно может извлечь из этого выгоду.

В Basecamp мы используем и используем несколько более сложных подходов, когда того требует случай. Наши календари обычно используют рендеринг на стороне клиента. Наш текстовый редактор - это Trix, полностью сформированный текстовый процессор, который не имеет смысла в качестве набора контроллеров стимулов.

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

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

"Попробуй".

Стимул был задуман во время двухнедельного глубокого погружения в текущее состояние JavaScript, которое я изучил около года назад. Я изучил наши шаблоны в кодовой базе Basecamp 3, извлек архетип и использовал новейшие лучшие методы JavaScript, чтобы воплотить его в жизнь. Это глубокое погружение впервые привело к появлению Webpacker для Rails, но этот фреймворк не мог бы появиться без невероятной работы Сэма Стивенсона и Джавана Махмали. Они взяли мой модный прототип Stimulus и переписали его с нуля на TypeScript. Так же, как с Турболинкс 5. Это красивый фрагмент кода.