От переводчика: Давид Хейнемейер Ханссон написал небольшой текст о том, почему он и его команда Ruby on Rails разработала свой собственный Javascript фреймворк. Оригинал текста размещен в репозитории нового проекта
Мы пишем много Javascript в Basecamp, но мы не используем его для создания "JavaScript-приложений" в современном смысле. Все наши приложения рендерят HTML на стороне сервера, затем мы добавляем вкрапления Javascript, чтобы оживить их.
Это путь величественного монолита. Basecamp работает на множестве платформ, включая нативные мобильные приложения, с единым набором контроллеров, представлений и моделей, созданных под Ruby on Rails. Иметь общий интерфейс, который обновляется из единого места, — это ключ к тому, чтобы маленькая команда работала хорошо, несмотря на множество поддерживаемых платформ.
Это позволяет нам быть продуктивными, как и в старые добрые времена. Возвращение к тем дням, когда единственный программист мог обеспечить немалый прогресс, не застревая в слоях абстракций распределенных систем. Время до того, как каждый стал думать, что святым граалем является ограничение серверной стороны до только лишь производства JSON для Javascript-приложений на клиенте.
Это не значит, что нет смысла в таком подходе для некоторых людей в каком-то месте. Но как основной подход ко многим видам приложений, и конечно, таким как Basecamp, — это в целом регрессия с точки зрения простоты и продуктивности.
Также это не значит что распространие одностраничных JavaScript-приложений не принесло никакой пользы. Они принесли скорость, более динамичные интерфейсы и свободу от перезагрузки страницы целиком.
Мы тоже хотели для Basecamp такого ощущения. Чтобы выглядело так, как будто мы последовали стадному чувству и переписали все с клиентским рендерингом или перешли на полностью нативные приложения на мобильных.
Это желание привело нас к двойному решению: Turbolinks и Stimulus.
Turbolinks наверх, Stimulus вниз
Перед тем как я перейду к Stimulus, нашему скромному JavaScript фреймворку, позвольте мне вкратце пересказать назначение Turbolinks.
Turbolinks происходит от так называемого pjax, разработанного в GitHub. Основная идея остается той же. Причиной, по которой полная перезагрузка страницы кажется медленной, не является то, что браузеру тяжело обработать HTML, отправленный с сервера. Браузеры реально хороши и быстры в этом. То, что обычно HTML-контент больше аналогичного JSON тоже неважно (особенно с учетом gzip). Нет, основная причина в том, что CSS и Javascript должны переинициализироваться и примениться к странице заново. Вне зависимости от того, закешированы ли файлы. Это может быть медленным, если у вас приличный размер CSS и JavaScript.
Чтобы обойти эту переинициализацию, Turbolinks держит постоянный процесс, так же как одностраничные приложения делают это. Но, в основном, это процесс невидимый. Он перехватывает ссылки и загружает новые страницы по Ajax. Сервер по-прежнему возвращает полные HTML-документы.
Эта стратегия в одиночку может сделать большинство действий в приложениях реально быстрыми (если сервер способен отвечать за 100-200мс, что возможно с кешированием). Для Basecamp это ускорило переходы по страницам в 3 раза. Это дает приложению то самое чувство отзывчивости и динамичности, которое являлось большей частью доводов "за" одностраничные приложения.
Но Turbolinks это лишь половина истории. Ниже уровнем полной смены страниц лежат мелкие обновления в рамках одной страницы. Показ и скрытие элементов, копирование в буффер обмена, добавление новой записи в todo-лист и другие взаимодействия, которые мы делаем в современных веб-приложениях.
До Stimulus Basecamp использовал смесь разных стилей и паттернов, чтобы добавить эти фичи. Часть кода была просто на jQuery, аналогичная по объему часть была на ванильном JavaScript и несколько большая объекто-ориентированная система. Все они вместе работали через явную обработку событий, опираясь на data-behavior атрибуты.
Было легко добавлять новый код наподобие такого, но это не было комплексным решением и мы имели несколько параллельно существующих самодельных стилей и паттернов. Это усложняло переиспользование кода и обучение новых разработчиков какому-то единому подходу.
Три основных концепта в Stimulus
Stimulus заворачивает лучшее от этих паттернов в скромный маленький фреймворк, крутящийся вокруг трех основных концептов: контроллеров (controllers), действий (actions) и целей (targets).
Он спроектирован так, чтобы прогрессивно улучшать 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 в большинстве случаев рендерится на сервере либо по загрузке страницы (первый заход), либо через Ajax-запрос, который меняет DOM.
Stimulus сконцентрирован на манипуляциях с существующим HTML-документом. Иногда это означает добавление CSS-класса, который прячет, анимирует или подсвечивает элемент. Иногда это означает перестановку элементов в группах. Иногда означает манипуляции с контентом элемента, например, преобразование UTC-времени, которое кешируется вместе с контентом, в локальное, показываемое пользователю.
В этих случаях вы захотите, чтобы Stimulus создавал новые DOM-элементы, и вы определенно вольны делать это. Может быть, в будущем мы даже добавим немного сахара, чтобы сделать это проще. Но это второстепенные сценарии. Основной фокус на манипуляциях, а не создании элементов.
Как Stimulus отличается от мейнстримовых JavaScript фреймворков
Это делает Stimulus очень отличающимся от большинства современных JavaScript-фреймворков. Почти все они сосредоточены на превращении JSON в DOM-элементы через какой-то вид языка шаблонов. Большинство использует эти фреймворки чтобы родить пустую страницу, заполненную исключительно элементами созданными через JSON-to-template рендеринг.
Stimulus также отличается в вопросах состояния. Большинство фреймворков имеют способы поддержания состояния внутри JavaScript-объектов, чтобы затем отрендерить HTML, основанный на этом состоянии. Stimulus является полной противоположностью. Состояние хранится в HTML, так что контроллеры могут быть выкинуты между сменой страниц, но они переинициализируются, как только закешированный HTML появляется вновь.
Это значительно отличающаяся парадигма. Я уверен, много опытных JavaScript-разработчиков, кому доводилось работать с современными фреймворками, будут издеваться. Но нет, отстаньте. Если вы счастливы со сложностью и усилиями, которые требуются чтобы поддерживать приложение в водовороте, скажем, React + Redux, то Turbolinks + Stimulus не понравятся вам.
Но с другой стороны, если у вас есть чувство, штука над которой вы работаете не требует такого уровня сложности и разделения приложений, которые подразумеваются в современых технологиях, то вы скорее всего обретете спасение в нашем подходе.
Stimulus и похожие идеи из реального мира
В Basecamp мы использовали эту архитектуру на нескольих версиях Basecamp и других приложений в течение нескольких лет. GitHub использовал похожий подход с замечательным эффектом. Это не просто альтернатива мейнстримовому пониманию, как выглядит современное веб-приложение, но и удивительно конкурентноспособная.
На самом деле это похоже на тот секретный ингредиент, который мы имели в Basecamp, когда делали Ruby on Rails. Чувство, что современные популярные подходы чрезмерно непрямолинейны, так что мы можем делать больше с меньшими усилиями.
Более того, вам даже не нужно выбирать. Stimulus и Turbolinks прекрасно работают в соединении с другими более тяжелыми подходами. Если 80% вашего приложения не вписывается в сложную установку, попробуйте нашу двухкомпонентную сборку для этого. Затем разверните тяжелую технику для той части приложения, где вы реально выиграете от этого.
В Basecamp мы делаем используем несколько более сложных подходов, где есть необходимость. Наша функциональность календарей использует клиентский рендеринг. Наш текстовый редактор, Trix, это полностью собранный обработчик текстов, который не имел бы смысла как пачка контроллеров Stimulus.
Этот набор альтернативных фреймворков про избавление от тяжестей насколько это возможно. Чтобы остаться в рамках парадигмы "запрос-ответ" для большинства взаимодействий, которые прекрасно работают с этой простой моделью. Затем обращаемся к дорогим инструментам, где есть смысл для достижения максимального эффекта.
Прежде всего, это набор инструментов для маленьких команд, которые хотят состязаться в охвате с большими командами, использующими более трудоемкие, мейнстримовые подходы.
Дайте ему шанс!
Комментарии (13)
amerov
06.01.2018 13:08Надеюсь, что они не начали патчить JS как Ruby.
printercu
08.01.2018 12:21В js сами начали: core-js
justboris Автор
08.01.2018 12:59core-js — это полифиллы. Они отличаются от тупого патчинга тем, что предоставляют код, который будет вшит по стандарту в будущие версии браузера.
Из книги "Effective Javascript", стр. 110, Chapter 4: Objects and Prototypes
But problems arise when multiple libraries monkey-patch the same prototypes in incompatible ways.
Despite the hazards, there is one particularly reliable and invaluable use of monkey-patching: the polyfill. [...] Since their behavior is standardized, providing implementations for these methods does not pose the same risk of incompatibility between libraries.То есть манки-патчинг приносит проблемы, когда два разных куска кода патчат прототипы на свой лад. А если патч является частью стандарта, то плохого в этом ничего нет
alex6636
06.01.2018 17:47+1Ого, кто-то разработал js-фреймворк… в это невозможно поверить!
justboris Автор
06.01.2018 20:59+1В том-то и дело, что не кто-то, а создатели Rails. Очень интересно было узнать почему не выбрали из существующих, как, например, Laravel в туториалах рекомендует Vue
k12th
06.01.2018 23:03Им было важно, чтобы фреймворк вытаскивал данные из DOM-дерева, а не ходил за ними AJAX-ом (не хотят отказываться от серверного рендеринга, хотя на кой это в таск-трекере — мне лично неясно).
zharikovpro
07.01.2018 01:57Рендеринг шаблонов на стороне сервера и *щепотки* JavaScript кодить существенно проще и дешевле, чем делать SPA. Старая школа :)
k12th
Ух ты, Knockout!
С учетом того, что vue.js точно так же умеет садиться на существующий DOM, ценность сего стимулюса пока не ясна.
justboris Автор
И Knockout и Vue на DOM садиться умеют, но состояние они из него не подгружают. В Angular 1 была подобная фича с ngInit но и там она считалась хаком на крайний случай. Во Vue есть такой feature request, но его отклонили.
А в Stimulus наоборот, вокруг атрибутов все состояние и строится. Например, в туториале показывается как из data-атрибутов строится state.
k12th
Занятно, это может быть полезно.