TL;DR: Тропа SPA темна и полна ужасов. Ты можешь бесстрашно сражаться с ними… или выбрать другой путь, который приведёт тебя к нужному месту: современный Rails.



Я вспоминаю, как думал, что Rails фокусируется на неправильной цели, когда DHH анонсировали Turbolinks в 2012 году. Тогда я был убеждён в том, что мгновенное время ответа во время взаимодействия с пользователем — это ключ к превосходному UX. Из-за возникающих задержек сети, такая интерактивность возможна, только если вы минимизируете зависимость от сети и вместо сетевых обращений поддерживаете большую часть состояния на клиенте.

Я думал, что это необходимо для приложений, над которыми я работал. И с таким мнением я перепробовал множество подходов и фреймворков для реализации одного и того же шаблона: Single-page applications (SPA). Я верил, что SPA — это будущее. Несколько лет спустя, я не уверен в том, каково же будущее, но я точно хочу найти альтернативу.

Кроличья нора SPA


Одностраничное приложение — JavaScript приложение, которое, единожды загруженное, получает полный контроль без необходимости перезагружать страницу: рендеринг, получение данных с сервера, обработка взаимодействия с пользователем, обновление экрана…

Такие приложения выглядят более нативно чем традиционные веб-страницы, где приложение зависит от сервера. Например, если вы используете Trello, вы можете заметить насколько быстро создаются карточки.

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

C SPA всё несколько сложнее. Вам всё ещё нужно серверное приложение, включающую вашу доменную модель и правила, веб-сервер, база данных и какая-то технология доступа к данным… и ещё куча дополнительных штук сверху:

Для сервера:

  • API, удовлетворяющий потребности вашего клиента в данных
  • Система сериализации в JSON для обмена данными с клиентом и система кэширования, которая поддерживает это

Для нового JavaScript клиента:

  • Система шаблонов для преобразования данных в HTML
  • Представление вашей доменной модели и правил
  • Слой доступа к данным в клиентском приложении для передачи данных серверу
  • Система обновления представлений при изменении данных
  • Система для связи URL-адресов и экранов (надеюсь, вы не будете использовать один адрес для всего, это выглядит не совсем как веб-приложение)
  • Система для склеивания всех компонентов, необходимых для отображения экранов и получения данных для этого
  • Новые шаблоны и архитектура для организации всего когда
  • Система для обработки ошибок, логгирования, отслеживания исключений и т.д.
  • Система для генерации JSON для серверных запросов
  • Фреймворк для автоматизированного тестирования, поддерживающая ваш SPA
  • Дополнительный набор тестов для написания и поддержки
  • Дополнительный набор инструментов для сборки, упаковки и развёртывания нового приложения

Суммируя, с SPA у вас будет ещё одно приложение для поддержки. И новый набор проблем для решения. И заметьте, вы не можете заменить одно приложение другим. Вам всё ещё нужно серверное приложение (теперь оно будет рендерить JSON вместо HTML).

Если вы никогда не работали с SPA, вы можете недооценивать сложности, с которыми вы столкнётесь. Я знаю это, потому что я допустил те же ошибки в прошлом. Отрендерить JSON? Я могу с этим справиться. Богатая модель доменных объектов в JavaScript? Звучит забавно. И, знаешь, этот фреймворк решит все эти проблемы. Отличный пирог!

Неверно.

API и обмен данными.


Обмен данными между вашим новым приложением и сервером — сложная проблема.

Есть две противоположных силы:

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

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

Некоторые стандарты могут тут помочь. JSON API, чтобы стандартизировать JSON формат; или GraphQL, для выборки только нужных данных, таких сложных как потребуется, одним запросом. Но ни один из них не спасёт вас от:
  • Проработки каждого обмена данными
  • Реализации запросов, позволяющих выбрать данные эффективно на сервере

И оба эти аспекта представляют собой достаточный объём дополнительной работы.

Время начальной загрузки


Люди привыкли ассоциировать SPA со скоростью, но правда такова, что заставить их загружаться быстро не так-то уж и просто. Этому есть множество причин:

  • Приложению нужны данные перед тем, как отрендерить что-то, и чтобы распарсить достаточно большой объём JavaScript нужно время.
  • Сверх начального HTTP запроса для загрузки приложения, обычно нужно сделать один или больше запросов для получения JSON данных, необходимых для рендеринга экрана.
  • Клиент должен преобразовать JSON в HTML, чтобы показать хоть что-то. В зависимости от устройства и количества JSON для преобразования это может вносить заметные задержки.

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

Например, Discourse, SPA на базе Ember, имеет фантастическое время загрузки, но помимо всего прочего, они предзагружают большой объём JSON данных в виде части начального HTML, чтобы не делать дополнительные запросы. И отмечу, что команда Discourse помешаны в хорошем смысле на скорости и их навыки сильно выше среднего. Подумайте об этом перед тем, как с лёгкостью воспроизводить то же самое в вашем SPA.

Амбициозное решение этой проблемы — изоморфный JavaScript: отрендерите вашу начальную страницу на сервере и быстро отдайте клиенту, пока в фоне SPA загружается и получает полный контроль, когда готово.

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

Мне нравится возможность переиспользования кода в этой идее, но я не видел реализации, позволившей бы мне идти в противоположном направлении. Кроме того, мне кажется очень забавным такой процесс рендеринга страниц:

— Server: запрос к серверному API
— Server: запрос к базе данных
— Server: сгенерировать JSON
— Server: преобразовать JSON в HTML
— Client: отобразить начальный HTML
— Client: загрузить SPA
— Client: распарсить начальный HTML и подписаться на события DOM

Не могли бы вы просто запросить данные из БД, сгенерировать HTML и начать работать?

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

Архитектура


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

Управлять состоянием на клиенте сложно. Традиционные веб-сайты обычно фокусируются на экранах с одной ответственностью, которые теряют состояние, когда перезагружаются. В SPA же приложение отвечает за то, чтобы всё состояние и обновления экрана во время использования были консистентными и проходили гладко.

На практике, если вы начинали писать JavaScript небольшими порциями, чтобы улучшить взаимодействие, то в SPA вам придётся писать тонны дополнительного JavaScript кода. Тут вам стоит убедиться, что вы делаете всё правильно.

Существует столько же разных архитектур, сколько SPA-фреймворков:

  • Большинство фреймворков отличаются от традиционного шаблона MVC. Ember поначалу был вдохновлён Cocoa MVC, но достаточно сильно поменял свою программную модель в последних версиях.
  • Прослеживается тенденция, что разработчики предпочитают компоненты, а не традиционное разделение на контроллер и представление (некоторые фреймворки, такие как Ember и Angular, перешли к такому подходу в последних версиях). Все фреймворки реализуют некоторое подобие одностороннего биндинга данных. Двусторонний биндинг не приветствуется из-за побочных эффектов, которые он может вносить.
  • Большинство фреймворков включают систему роутинга, которая позволяет сопоставлять URL-адреса и экраны, и определяет, как создавать экземпляры компонентов для рендеринга. Это уникальный подход веб, который не существует в традиционных настольных интерфейсах.
  • Большинство фреймворков отделяют HTML шаблоны от JavaScript кода, но React ставит на смешение HTML-генерации и JavaScript и делает это вполне успешно, учитывая его массовое использование. Сейчас также наблюдается хайп вокруг встраивания CSS в JavaScript. Facebook со своей архитектурой Flux довольно сильно повлиял на индустрию, и контейнеры, такие как Redux, vuex и др., находятся под сильным влиянием от него.

Из всех фреймворков, что я видел, Ember — мой любимый. Я обожаю его согласованность и то, что он довольно упрямый. Мне также нравится его модель программирования в последних версиях, сочетающая традиционный MVC, компоненты и роутинг.

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

Наконец, Я не могу смириться со смешением HTML и CSS в компонентах, полных JavaScript логики. Я понимаю какую проблему это решает, но проблемы, которые привносит этот подход, не делают его стоящим этого.

Оставим личные предпочтения, суть в том, что если вы выберете путь SPA, то у вас появится очень сложная проблема: создать архитектуру вашего нового приложения правильно. И индустрия довольно далеко от того, чтобы прийти к согласию по вопросу — как это делать. Каждый год появляются новые фреймворки, шаблоны и версии фреймворков, что слегка меняет модель программирования. Вам потребуется писать и поддерживать тонну кода, основываясь на вашем архитектурном выборе, так что подумайте об этом как следует.

Дублирование кода


При работе с SPA вероятно вы встретитесь с дублированием кода.

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

Например, представим, что вы работаете с инвойсами. Вы возможно имеете класс Invoice в JavaScript, который содержит метод total, который суммирует цену всех элементов, чтобы вы могли отрендерить стоимость. На сервере, вам также понадобится класс Invoice с методом total для вычисления этой стоимости, чтобы отправить её по e-mail. Видишь? Клиентский и серверный класс Invoice реализуют одинаковую логику. Дублирование кода.

Как сказано выше, изоморфный JavaScript мог бы нивелировать эту проблему, позволяя проще переиспользовать код. И я говорю нивелировать, потому что соответствие между клиентом и сервером не всегда 1-к-1. Вы захотите быть уверены, что некоторый код никогда не покидает сервер. Большое количество кода имеет смысл только для клиента. А также, некоторые аспекты просто отличаются (например, серверный элемент может сохранять данные в БД, а клиент может использовать удалённый API). Переиспользование кода, даже если это возможно, — это сложная проблема.

Вы можете поспорить, что вам не нужна богатая модель в вашем SPA и что вы вместо этого будете работать с JSON/JavaScript объектами напрямую, распределяя логику по компонентам UI. Теперь у вас есть то же самое дублирование кода, перемешанное с вашим кодом UI, удачи с этим.

И то же самое случится если вы захотите шаблоны для рендеринга HTML между сервером и клиентом. Например, для SEO, как насчёт сгенерировать страницы на сервере для поисковых роботов? Вам потребуется заново написать ваши шаблоны на сервере и убедится, что они синхронизированы с клиентскими. Опять дублирование кода.

Необходимость воспроизводить логику шаблонов на сервере и клиенте, по моему опыту, — источник возрастающего несчастья программистов. Сделать это один раз — это нормально. Когда вы сделаете это в 20ый раз, вы схватитесь за голову. Сделав это в 50ый раз, вы задумаетесь, нужны ли все эти SPA штуки.

Хрупкость


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

Во-первых, неважно насколько вы осторожны, неважно как много тестов вы пишете. Чем больше кода вы пишете, больше багов у вас будет. И SPA представляет (извините, если я сильно давлю) огромную кучу кода для написания и поддержки.

Во-вторых, как упоминалось выше, разработка богатого GUI это сложно и выливается в сложные системы, состоящие из множества элементов, взаимодействующих друг с другом. Чем более сложную систему вы создаёте, тем больше у вас багов. И по сравнению с традиционными веб-приложениями, использующими MVC, сложность SPA просто безумная.

Например, для сохранения консистетности на сервере вы можете использовать ограничения в БД, валидации модели и транзакции. Если что-то пойдёт не так, вы отвечаете с сообщением об ошибке. В клиенте, всё слегка сложнее. Очень многое может пойти не так просто потому, что слишком много всего происходит. Может быть так, что какая-то запись сохраняется удачно, а какая-то другая запись нет. Возможно, вы перешли в оффлайн посередине какой-то операции. Вы должны убедиться, что UI остаётся консистентным, и что приложение восстанавливается после ошибки. Всё это возможно, конечно, только намного сложнее.

Организационные вызовы


Это звучит глупо, но для разработки SPA, вам нужны разработчики, которые знают, что с этим делать. В то же время, вам не стоит недооценивать сложность SPA, вам не стоит думать, что любой опытный веб-разработчик с правильной мотивацией и пониманием может написать отличное SPA с нуля. Вам нужны правильные навыки и опыт или ожидайте, что важные ошибки будут сделаны. Я знаю это, потому что это в точности мой случай.

Это возможно более важный вызов для вашей компании, чем вы думаете. Подход SPA поощряет команды узкой специализации вместо команд из специалистов общего профиля:

  • SPA фреймворки представляют собой сложные части программного обеспечения, в которых требуется бесчисленное количество часов опыта, чтобы быть продуктивным. Только люди из вашей компании, проводящие эти часы за кодом, смогут поддерживать эти приложения.
  • SPA фреймворки требуют продуманных и производительных API. Выполнение этих требований требует совершенно иного набора навыков, чем те, что необходимы работы с SPA.

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

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

Современный Rails


Есть 3 вещи, входящих в современный Rails, которые могут изменить ваше мнение о разработке современных веб-приложений:
  • одна из них — Turbolinks и это взрыв мозга
  • другая — старый друг, которого сегодня упускают из вида: SJR ответы и простые AJAX запросы для рендеринга
  • и последняя была добавлена недавно: Stimulus

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

Turbolinks


Идея Turbolinks проста: ускорьте ваше приложение, полностью заменив перезагрузку страниц на AJAX запросы, которые заменяют `` элемент. Внутреннее колдовство, выполняющее эту работу, скрыто. Как разработчик, вы можете сосредоточиться на традиционном серверном программировании.

Turbolinks вдохновлён pjax и прошёл через несколько ревизий.

Раньше я беспокоился о его производительности. Я был неправ. Ускорение огромно. То, что меня убедило, это то, как я использовал его в проекте, но вы можете просто попробовать пробную версию в Basecamp и поиграться с ней. Попробуйте создать проект с некоторыми элементами, а затем перемещайтесь по ним, щелкая по разделам. Это даст вам хорошее представление о том, как выглядит Turbolinks.

Я не думаю, что Turbolinks просто потрясает своей новизной (pjax — 8 лет). Или своей технической утонченностью. Меня поражает то, как простая идея может повысить вашу производительность на порядок по сравнению с альтернативой SPA.

Позвольте мне выделить некоторые проблемы, которые он устраняет:

  • Обмен данными. У вас его нет. Не нужно сериализовывать JSON, создавать API-интерфейсы или думать о запросах данных, которые удовлетворяют потребности клиентов с учётом производительности.
  • Начальная нагрузка. В отличие от SPA, этот подход стимулирует быстрое время загрузки (by design). Для рендеринга экрана вы можете получить данные, которые вам нужны непосредственно из базы данных. И эффективный запрос данных из реляционных баз данных или кэширование HTML — это хорошо решаемые проблемы.
  • Архитектура: Вам не нужна сложная архитектура для организации вашего JavaScript-кода. Вам нужно всего лишь сосредоточиться на правильной архитектуре вашего серверного приложения, что вам всё равно нужно делать при использовании SPA.

MVC на сервере, в варианте, используемом Rails и многими другими фреймворками, намного проще, чем любой из шаблонов, используемых для архитектуры богатых графических интерфейсов: получить запрос, поработать с БД для его удовлетворения и отобразить страницу HTML в качестве ответа.

Наконец, ограничение, что всегда заменяется имеет замечательный эффект: вы можете сосредоточиться на первоначальном рендеринге страниц вместо того, чтобы обновлять определенные разделы (или обновлять некоторые состояния в мире SPA). В общем случае он просто все делает.

  • Дублирование кода. Существует только одно представление вашего приложения, которое живет на сервере. Ваша доменная модель, её правила, экраны приложений и т.д. Нет необходимости дублировать концепции в клиенте.
  • Хрупкость. По сравнению с SPA, JavaScript для работы на ваших страницах и его сложность сокращены до небольших долей, и поэтому количество ошибок. Кроме того, вы можете полагаться на атомарное выполнение операций на сервере, используя транзакции базы данных, ограничения и валидации.

Заметьте, я говорю не об обозначении проблем, а об их устранении. Например, GraphQL или SPA-регидратация — это суперумные решения для очень сложных проблем. Но что, если вместо того, чтобы пытаться найти решение, вы ставите себя в ситуацию, когда эти проблемы не существуют? Это изменение подхода к проблеме. И мне потребовались годы, чтобы в полной мере оценить способность этого подхода решать проблемы.

Разумеется, Turbolinks не является беспроблемной серебряной пулей. Самая большая проблема заключается в том, что он может сломать существующий JavaScript код:

  • Turbolinks поставляется со своим пользовательским событием «загрузка страницы», и существующие плагины, полагающиеся на регулярные загрузки страниц, не будут работать. Сегодня есть лучшие способы добавить поведение к DOM, но устаревшие виджеты не будут работать, если не будут адаптированы.
  • JavaScript-код, изменяющий DOM, должен быть идемпотентным, поскольку его могут запускать несколько раз. Опять же, это делает недействительным много существующего JavaScript.
  • Скорость отличная, но это не совсем как в SPA, который может обрабатывать некоторые взаимодействия, не загружая сервер. Я расскажу больше о компромиссах позже.

AJAX рендеринг и SJR ответы


Помните, когда рендеринг HTML через Ajax был в тренде 15 лет назад? Угадай, что? Это все еще замечательный инструмент, который есть в вашем арсенале:
  • Получение фрагмента HTML с сервера и добавление его в DOM, ощущается супербыстрым (на 100мс быстрым).
  • Вы можете рендерить HTML на сервере, что позволяет повторно использовать ваши представления и извлекать необходимые данные непосредственно из базы данных.

Вы можете видеть, как этот подход ощущается в Basecamp, открыв меню вашего профиля, нажав на верхнюю правую кнопку:



Открывается мгновенно. Со стороны разработки вам не нужно заботиться о сериализации JSON и клиентской стороне. Вы можете просто отобразить этот фрагмент на сервере, используя все возможности Rails.

Похожий инструмент, который Rails включает в себя в течение многих лет, — это серверные ответы JavaScript (SJR). Они позволяют вам отвечать на запросы Ajax (обычно формировать представления) с JavaScript, который исполняется клиентом. Он дает те же преимущества, что AJAX-рендеринг HTML-фрагментов: исполняется очень быстро, вы можете повторно использовать код на стороне сервера, и вы можете напрямую обращаться к базе данных для создания ответа.

Вы можете увидеть, как это происходит, если вы зайдёте в Basecamp и пытаетесь создать новый todo. После того, как вы нажмете «Добавить todo», сервер сохранит todo и ответит фрагментом JavaScript, который добавляет новый todo в DOM.

Я думаю, что многие разработчики сегодня смотрят на AJAX-рендеринг и SJR-ответы с презрением. Я тоже это помню. Они являются инструментом и, как таковые, могут подвергаться злоупотреблениям. Но при правильном использовании это потрясающее решение. Позвольте вам предложить отличный UX и интерактивность по очень низкой цене. К сожалению, как и Turbolinks, их сложно оценить, если вы ещё не сражались с SPA.

Stimulus


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

  • Он использует MutationObserver для привязки поведения к DOM, то есть ему не важно, как HTML появляется на странице. Конечно, это отлично работает с Turbolinks.
  • Он сэкономит вам кучу шаблонного кода для привязки поведения к DOM, для привязки обработчиков к событиям и для размещения элементов в указанном контейнере.
  • Он нацелен на то, чтобы ваш HTML-код был читабельным и понятным, что приятно, если вы когда-либо сталкивались с проблемой поиска того, какая часть JavaScript действует на этом проклятом элементе.
  • Он поощряет сохранение состояния в DOM. Опять же, это означает, что ему не важно, как генерируется HTML, что подходит для многих сценариев, в том числе Turbolinks.

Если вы примете Rails-путь, ваш JavaScript будет сосредоточен на изменении HTML-кода, созданного на стороне сервера, и улучшении взаимодействия (c небольшим количеством JavaScript). Stimulus предназначен для организации такого кода. Это не система SPA и не претендует на то, чтобы быть таковой.

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

Игра компромиссов


Turbolinks обычно продается как «Получите все преимущества SPA без каких-либо неудобств». Я не думаю, что это полностью верно:

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

Теперь разработка — это игра компромиссов. И в этой игре:
  • Современный Rails позволяет вам создавать приложения, которые достаточно быстры и отлично выглядят.
  • Для огромного множества приложений Rails позволяет реализовать те же функции с меньшим количеством кода и меньшей сложностью.

Я считаю, что с Rails вы можете получить 90% того, что предлагает SPA с 10% усилий. Что касается производительности, Rails убивает SPA. Что касается UX, я думаю, что многие разработчики делают ту же ошибку, что и я, предполагая, что SPA UX является непревзойденным. Это не так. Фактически, как обсуждалось выше, вам лучше знать, что вы делаете при создании своего SPA, или UX будет на самом деле хуже.

Заключение


Я наблюдаю за тем, как компании массово принимают SPA фреймворки, и вижу бесчисленные статьи о том, как делать причудливые вещи в стиле SPA. Я думаю, что существует много «использований неправильного инструмента для работы», поскольку я твердо верю, что типы приложений, которые оправдывают использование SPA, ограничены.

И я говорю, что оправдывают, потому что SPA сложны. Во всяком случае, я надеюсь, что убедил вас в этом. Я не говорю, что невозможно создать великолепные SPA-приложения, или что современные Rails-приложения великолепны по определению, просто один подход суперсложный, а другой намного проще.

Готовя эту статью, я наткнулся на этот твит:



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

По прошествии многих лет я понял, что сложность часто является выбором. Но в мире программирования на удивление сложно выбрать простоту. Мы так высоко ценим сложность, что принятие простоты часто заставляет мыслить по-другому, что по определению — трудно.

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

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

Комментарии (45)


  1. Dansoid
    23.07.2018 10:13
    +2

    Спасибо за статью. Я также не поленился и нашел те < 50 строчек кода, которые включают поддержку Turbolinks в ASP.NET MVC. Надо будет на досуге поэкспериментировать пока Blazor еще не готов.


    1. mayorovp
      23.07.2018 22:45

      В ASP.NET MVC есть Unobtrusive AJAX который делает нечто аналогичное.


  1. RouR
    23.07.2018 10:37

    На полученный фрагмент HTML с сервера нужно навешивать обработчики событий (onclick и т.п.) в соответствии с бизнесс-логикой. Причём навешивать только на элементы этого нового фрагмента. Это дополнительная задача, о которой как-то забыли.


    1. jreznot Автор
      23.07.2018 10:39

      Вроде не забыли, этим занимается упомянутый фреймворк Stimulus.


    1. ashedrin
      23.07.2018 11:32

      Автор предлагает использовать для этих целей Stimulus.


      1. staticlab
        23.07.2018 13:01

        Как-то концептуально Stimulus мне показался похожим на Vue или Angular.


  1. fairwind
    23.07.2018 11:27

    Из крайности в крайность.
    Понятно, SPA не серебряная пуля, и много сложнее традиционных приложений.
    Но и решает более сложные задачи.
    Что касается Turbolinks и прочего JS из Rails, то ИМХО это жуть и кошмар.
    Работает? Да.
    Эффективно? Кхм.


    1. mystdeim
      23.07.2018 11:36
      +2

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


      1. fairwind
        23.07.2018 12:05

        Для простых проектов вполне пойдет, согласен.
        Но SPA — не для простых проектов, КМК.
        Насчет эффективности попробую на примере. Вот, допустим, эта самая страница хабра работает на Turbolinks. Мы нажимаем кнопку "обновить комментарии", нам прилетает 55 Kb HTML всей страницы, даже если новых комментариев нет.
        Если без Turbolinks — нам нужно по клику на кнопке сделать AJAX-запрос (~100 байт, если нет новых комментариев), и обновить не всю страницу, а только комментарии, да и то если они изменились. Можно HTML генерировать на сервере и просто добавить в нужный узел, а можно брать JSON и готовить HTML на клиенте (с помощью handlebars, например).
        И для AJAX-запросов можно использовать старый добрый JQuery, а можно xhr или fetch.
        Да, это сложнее.


        1. mystdeim
          23.07.2018 12:34

          Всю страницу не надо запрашивать, делается SJR запрос и обновляется только нужный узел


          1. mkv
            23.07.2018 14:27

            Но вместе с его html


    1. jreznot Автор
      23.07.2018 11:36

      Ну, в Discourse работает реально классно, например, на их собственном форуме meta.discourse.org


      1. fairwind
        23.07.2018 12:09

        В статье же говорится, что это SPA на базе Ember? Или что-то работает на Turbolinks?


        1. jreznot Автор
          23.07.2018 12:55

          Они просто используют такой-же подход с пререндером, наверное не совсем удачная параллель


  1. gnaeus
    23.07.2018 11:51
    +1

    Отличная статья. Вы прямо сформулировали те мысли, которые не раз посещали меня за последние годы.


    Но одна проблема остается нерешенной. Как только на сайте появляются сложные UI элементы (динамические таблицы, деревья, формы ввода и т.п.), разработка всего этого на серверных шаблонах превращается в боль. И здесь уже не поможет Progressive Enhancement вроде Stimulus. Нужны полноценные клиентские шаблонизаторы. Теперь часть страниц нашего сайта превращаются в точки входа для клиентских виджетов. И хорошо, если написанных на одном и том-же фреймворке.


    И вот, чтобы не утонуть в этой мешанине, нам придется:


    • реализовать единообразный API (хотя бы JSON RPC)
    • настроить систему сборки (webpack) и code splitting
    • выбрать клиентский фреймворк
    • выбрать state container (чтобы обеспечить взаимодействие наших отдельных виджетов)
    • etc.

    Отдельная проблема — это разные шаблонизаторы на клиенте и на сервере. Каждый раз нужно решать, достаточно ли сложна задача, чтобы переносить ее на клиент? Или можно еще чуть-чуть допилить серверный шаблон. Возможно, тут может помочь Vue с его альтернативными шаблонизаторами: можно использовать Pug и на клиенте, и на сервере. А вот с React / Angular так не прокатит.


    1. jreznot Автор
      23.07.2018 12:56

      Рад, что вам понравился мой перевод. Оригинальный автор — Jorge Manrubia medium.com/@jmanrubia/escaping-the-spa-rabbit-hole-with-turbolinks-903f942bf52c


      1. RomanPokrovskij
        23.07.2018 13:37

        Вопрос в сторону. Не поделитесь ли вы или ваши читатели, как использовать medium что бы не пропускать такие статьи? Вот просто тэги web development и frontend на latest показывают мне столько белеберды на китайском турецком что читать невозможно. А какой-то «подписки», «фильтров по языкам» я не нашел. Плохо искал? Как пользуетесь mediumом вы?


        1. mkv
          23.07.2018 14:30

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


        1. shir
          23.07.2018 19:42

          Мне medium как-то сам рекомендует. Во всяком случае все интересные переводы которые тут попадаются мне в рекомендациях там попадались. Хотя вроде ни на один блог не был подписан даже.


    1. staticlab
      23.07.2018 13:16
      +1

      Как только на сайте появляются сложные UI элементы (динамические таблицы, деревья, формы ввода и т.п.), разработка всего этого на серверных шаблонах превращается в боль. И здесь уже не поможет Progressive Enhancement вроде Stimulus. Нужны полноценные клиентские шаблонизаторы.

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


      1. mkv
        23.07.2018 14:34

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


        1. staticlab
          23.07.2018 14:49

          Скорее всего рендер этих компонентов вынесен на сторону сервера, а клиентский скрипт только подменяет innerHTML. Статья такой подход поощряет. То есть придётся переделывать серверную логику, чтобы хотя бы по AJAX он отдавал JSON, а не HTML. Кстати, как Vue относится к пререндеренным на сервере компонентам? Умеет подхватывать без ошибок и предупреждений?


          1. mkv
            23.07.2018 15:05

            Не уверен что понял вопрос.

            Можно конвертить их сразу в json из бд. Как указано здесь

            <%= tad.div id: :boards, data { lists: @lists.to_json(include: :cards)} %>


            1. staticlab
              23.07.2018 15:22

              Кстати, вариант.


          1. gnaeus
            23.07.2018 15:09

            Ну, вообще это поддерживает любой фреймворк с Server Side Rendering (в т.ч. Vue, React, Angular). Но, как по мне, это не особо нам поможет. Потому что придется иметь два шаблона — клиентский (например JSX) и серверный (например ASP.NET Razor). И нужно чтобы они идеально совпадали. А это просто рассадник багов.


            1. staticlab
              23.07.2018 15:21

              Не совсем так. Им всё равно требуются исходные данные для рендера. И дальше будет сравнение виртуального DOM с реальным, на основе пришедшего с сервера HTML. Если будет даже минимальное расхождение, React выдаёт предупреждение.


              Кроме того, полностью от Razor может быть тяжело отказаться, потому что шаблоны иногда бывают активные, придётся дорабатывать бэкенд.


        1. RomanPokrovskij
          23.07.2018 22:28

          Заинтересовало, что значит «постепенно», vue в таком использовании сможет 1) жить совместно с Views\Shared\_Layout.cshtml? 2) Как им заменять view component, partial view?


          1. mkv
            23.07.2018 23:44

            Речь была о другом в том комменте, но отвечу на вопросы)

            Views\Shared\_Layout.cshtml

            Откуда это? :)
            Как им заменять view component, partial view?

            Вот так github.com/rails/webpacker


  1. Undiabler
    23.07.2018 13:17

    Суммируя, с SPA у вас будет ещё одно приложение для поддержки.

    И это же прекрасно. Команда фронтендщиков смогут сконцентрироваться на UI, сами решать как отрисовывать элементы, гибко менять хоть весь дизайн целиком каждый день.
    На базе хорошего бэкенда можно даже создавать паралельно iOS/Android приложения, внешние плагины и т.д.
    В первую очередь SPA это показатель грамотной архитектуры где фронтенд отделен от бэкенда.
    Да, это слегка увеличивает накладные расходы, но взамен добавляет гибкость и скорость развития проекту над которым работает больше двух человек.


    1. mkv
      23.07.2018 14:33

      Там где нужен только один сайт, у вас вместо одного приложения будут два.

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


      1. staticlab
        23.07.2018 14:52

        Почему минимум в два? Со стороны бекенда может быть нереально много логики, а фронт — значительно проще. Пример: банк-клиент (неважно, веб-версия или мобильное приложение).


        1. mkv
          23.07.2018 15:43

          И даже в этом случае кол-во знаний для использования фронтенд фреймворка для веб девелопера(в этом случае бэкенд девелопера) увеличивается на порядок. Хотя банк-клиент как пример здесь не очень.


          1. staticlab
            23.07.2018 15:47

            Так в том и дело, что фронтенд-фреймворк бэкендеру не нужно будет знать, если это будет обязанность фронтендера. Точно так же, как ему не нужно будет знать ни Андроид, ни Айос.


            1. mkv
              23.07.2018 15:50

              Тогда все еще хуже. Для проекта нужен будет еще один девелопер)) И мы возвращаемся к…

              И кол-во требуемых специалистов это тоже увеличивает как минимум в два раза


              1. staticlab
                23.07.2018 15:54

                Я уже сказал, что это скорее всего будет не так. Бэкенд могут писать человек 20, а фронтовая команда, например, человек 5. И чем вам не нравится пример с банком? Тем, что там бэкендом занимаются сотни?


                1. mkv
                  23.07.2018 16:09

                  Я уже сказал, что это скорее всего будет не так. Бэкенд могут писать человек 20, а фронтовая команда, например, человек 5.

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

                  Со стороны бекенда может быть нереально много логики, а фронт — значительно проще. Пример: банк-клиент (неважно, веб-версия или мобильное приложение).

                  Тем что фронт на примере банк-клиента может быть небольшим лишь в сравнение с бэкендом. В остальном он будет достаточно величины, чтобы рассматривать фронтенд фреймворки. И опять же обсуждались другие проекты.


                  1. staticlab
                    23.07.2018 16:13

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

                    Извините, но Undiabler начал эту ветку с условия о том, что речь идёт о проекте, "над которым работает больше двух человек".


                    1. mkv
                      23.07.2018 16:18

                      Без проблем. Кол-во человек говорит о размере проекта косвенно. В первом комменте я указал что лишь часть проектов требует увеличения кол-ва девелоперов. Я думаю что мы поняли друг друга и на этом предлагаю закончить ветку.


      1. Undiabler
        23.07.2018 15:20

        От того что вы смешаете несколько проектов в одну репу/сайт у вас не получится одно приложение, у вас получится мешанина которую поддерживать сможет только фулстек разработчик с бубном который и css/js поправить может и sql запрос к базе.

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


        1. mkv
          23.07.2018 15:38

          От того что вы смешаете несколько проектов в одну репу/сайт у вас не получится одно приложение, у вас получится мешанина которую поддерживать сможет только фулстек разработчик с бубном который и css/js поправить может и sql запрос к базе.

          Вы о чем? Кто что смешает? Открою для вас секрет большинство сайтов и разрабатывается без фронтенд фреймворков. И скажу даже больше кол-во проектов на которых размер js настолько большое что требует для этого отдельный фреймворк — невелико. И не имеет значения насколько это вам не нравится.

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

          Вы сами себе противоречите. Чтобы не было и швец и жнец, и появляется еще одна позиция для разработки сайта.


          1. staticlab
            23.07.2018 15:52

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

            А начиная с какого количества кода по-вашему разумно внедрять фронтенд-фреймворк?


            Вы сами себе противоречите. Чтобы не было и швец и жнец, и появляется еще одна позиция для разработки сайта.

            Так если фронт и бэк тесно связаны, всяко потребуется фуллстек.


            1. mkv
              23.07.2018 16:13

              А начиная с какого количества кода по-вашему разумно внедрять фронтенд-фреймворк?

              Тогда, когда его поддержка становится невозможной.

              Так если фронт и бэк тесно связаны, всяко потребуется фуллстек.

              Вы о чем вообще? Перечитайте ветку.


          1. Undiabler
            23.07.2018 17:06

            Смешивать бэкенд и фронтенд конечно же.
            Rust/php/python/go с js/html/css если так понятней.

            Смешивание этих сущностей и есть отсутствие нормальной архитектуры. Бекенд отдает куски html который зависит от js/css. Начинаются танцы с динамическими импортами разных зависимостей на страницах, двойные накладные расходы на чудо проверки: «а что собственно поддерживает фронтенд» и т.д.

            И не важно фреймворк у вас или нет. Открою для вас секрет — SPA можно сделать и без фреймворка. Да-да, spa на голом js+html если зашла уже речь о таких миниатюрных проектах где фреймворк не нужен.


            1. mkv
              23.07.2018 17:14

              Смешивание этих сущностей и есть отсутствие нормальной архитектуры. Бекенд отдает куски html который зависит от js/css. Начинаются танцы с динамическими импортами разных зависимостей на страницах, двойные накладные расходы на чудо проверки: «а что собственно поддерживает фронтенд» и т.д.

              В каком месте вы его смешиваете? Все это отделено view слоем и шаблонами.

              И не важно фреймворк у вас или нет. Открою для вас секрет — SPA можно сделать и без фреймворка. Да-да, spa на голом js+html если зашла уже речь о таких миниатюрных проектах где фреймворк не нужен.

              Да какая разница как все это реализовываете. У вас в любом случае html, css и js. Это то как сейчас работает веб. Вы его не изменили своим SPA, от слова вообще никак. Только все это еще и делаете на машине клиента.


  1. RomanPokrovskij
    23.07.2018 23:10

    Если вы недоумеваете: «где я пропустил в логике автора, то место где, где объясняется почему при перечисленных проблемах SPA (время нач. загрузки, хрупкость, сложность), оригинальный rails (без Turbolinks, SJR, Stimulus) является недостаточным их решением?», то вы не одиноки. Мое понимание статьи распалось на две несвязанные части «критика SPA» и «плюшки Turbolinks etc.».