TL;DR: бюджеты производительности — существенная, но недооценённая часть успешного продукта и здоровой команды. Большинство наших партнёров не осведомлены об условиях реального мира — и в результате выбирают не те технологии. Мы установили бюджет по времени пять и менее секунд до интерактивности сайта после первой загрузки, а также две или менее секунд при последующих загрузках. При соблюдении этих нормативов мы ограничены типичным устройством из реального мира и типичной сетевой конфигурацией. Это Android-смартфон за $200 на канале 400 Кбит/с, RTT 400 мс. Это означает бюджет ~130-170 КБ ресурсов критического пути, в зависимости от их состава: чем больше JS — тем меньше объём.
За последние несколько лет мы имели удовольствие работать с десятками команд. Работа оказалась просветляющей, иногда в очень неожиданных местах. Один из самых неожиданных результатов — частые случаи «западни JavaScript».
«Нам нужен новый термин для упущенных деловых возможностей из-за современного фронтенда. Может быть, “западня JavaScript”»?
Управленцы, которые дают добро на создание прогрессивных веб-приложений (PWA), часто основным мотивом называют практически беспроблемный охват новых пользователей. В то же время разработчики осваивают инструменты, которые делают возможной достижение такой цели. Никто не хотел плохого. Тем не менее, результаты «готового» проекта PWA часто требуют недель или месяцев болезненной переделки, чтобы обеспечить минимально приемлемую производительность.
Такая переделка задерживает запуск, что, в свою очередь, задерживает сбор данных о жизнеспособности выбранной PWA-стратегии. Разработчики часто не осознают проблем, пока не становится слишком поздно. Они запускают сайты, которыми просто невозможно пользоваться, если только вы не один из богатых обладателей самых лучших смартфонов.
Установка базового уровня
Те команды, которым удаётся избежать неприятных результатов, склонны демонстрировать несколько общих черт:
- Руководители полны энтузиазма. Они используют подход «делайте то, что нужно», чтобы обеспечить и сохранить быструю работу приложения.
- На раннем этапе установлены бюджеты производительности.
- Бюджеты масштабируются в соответствии с параметрами сети и устройств на рынке.
- Инструменты и системы непрерывной интеграции (CI) помогают отслеживать прогресс и предотвратить регрессию.
Эти параметры основаны друг на друге: сложно планировать правильные вещи, если у вас нет руководства, которое ценит важность удобного пользовательского интерфейса и его долговременное значение для бизнеса. Команды с такой поддержкой могут устанавливать бюджеты производительности, проводить «конкурсы» между конкурирующими подходами и инвестировать в инфраструктуру производительности. У них больше воли, чтобы пойти против «общепринятых стандартов», когда популярные инструменты доказывают своё несоответствие.
Благодаря бюджетам производительности все находятся в одной лодке — создаётся культура общего энтузиазма для улучшения пользовательского интерфейса. Командам с бюджетами ещё и легче отслеживать прогресс и составлять графики. Это помогает руководителям: у них появляются осмысленные метрики в обоснование инвестиций.
Бюджеты устанавливают объективные границы для определения, какие изменения в кодовой базе станут шагом вперёд, а какие — шагом назад с точки зрения пользователя. Без этого вы неизбежно скатитесь в западню и начнёте притворяться, что можете позволить себе больше, чем разрешено на самом деле. Очень редко мы видели команды, которые добивались успеха без бюджета, сбора метрик RUM и использования репрезентативных пользовательских устройств.
Совещания с партнёрами показательны. Мы сразу получали чёткое представление, насколько плоха производительность у сайта — по проценту ведущих программистов, менеджеров проекта и руководителей с топовыми смартфонами, которые они используют преимущественно в городской местности.
Улучшения для пользователей состоят из двух этапов:
- Пересмотр предположений и растущее понимание условий реального мира
- Автоматическое тестирование по объективному базовому уровню
Никогда раньше у фронтенд-разработчиков не было доступа к таким отличным инструментам измерения производительности и техникам диагностики, тем не менее плохие результаты в норме вещей. В чём дело?
JS ваш самый дорогой ресурс
Один отчётливый тренд — это вера в то, что фреймворк JavaScript и одностраничная архитектура (SPA) обязательно должны использоваться для разработки прогрессивных веб-приложений. Это неправда (подробнее об этом в следующей статье), а таким сайтам обязательно понадобится больше скриптов в каждом документе (например, для router-компонентов). Мы регулярно видим сайты, загружающие более 500 КБ скриптов (сжатых). Это важно, потому что все загрузки скриптов влияют на самую главную метрику: время до появления интерактивности (Time to Interactive). Сайты с таким количеством скриптов просто недоступны значительной части пользователей; статистически, пользователи не будут ждать загрузки интерфейса так долго. Если же они дожидаются загрузки, то испытывают жуткие лаги.
Нас часто спрашивали: «Почему так важно ограничение JS в 200 КБ, у нас некоторые картинки большего размера?» Хороший вопрос! Чтобы ответить на него, важно понимать, как браузер обрабатывает ресурсы (разного типа) и концепцию критического пути. В качестве своевременного введения, рекомендую недавнее выступление Кевина Шаафа.
Загрузка JavaScript с опозданием может привести к тому, что «отрендеренные на сервере» страницы не работают так, как ожидает пользователь, что очень раздражает. Такой эффект — главная причина, почему мы стремимся гарантировать надёжную интерактивность
Представим такую страницу:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/styles.css">
<script src="/app.js" async></script>
</head>
<body>
<my-app>
<picture slot="hero-image">
<source srcset="img@desktop.png, img@desktop-2x.png 2x"
media="(min-width: 990px)">
<source srcset="img@tablet.png, img@tablet-2x.png 2x"
media="(min-width: 750px)">
<img srcset="img@mobile.png, img@mobile-2x.png 2x"
alt="I don't know why. It's a perfectly cromunlent word!">
</picture>
</my-app>
</body>
</html>
Браузер получает этот документ в ответ на GET-запрос к
https://example.com/
. Сервер отправляет её в виде потока байтов, а когда браузер встречается с каждым из упомянутых в документе подресурсов, он их запрашивает.После завершения загрузки эта страница должна реагировать на действия пользователя — там самая «интерактивность» из параметра “Time to Interactive” (TTI). Браузеры обрабатывают действия пользователя, генерируя события DOM, которых ждёт код приложения. Обработка пользовательских действий происходит в основном потоке документа, где работает и JavaScript.
Вот некоторые операции, которые могут происходить в других потоках, в любом случае сохраняя браузеру возможность реагирования на действия пользователя:
- Разбор HTML
- Разбор CSS
- Разбор и компиляция JavaScript (иногда)
- Некоторые задачи сбора мусора JS
- Разбор и растрирование картинок
- Трансформации и анимации CSS с аппаратным ускорением
- Прокрутка основного документа (если нет активных обработчиков тач-событий)
Однако следующие операции должны идти в основном потоке:
- Выполнение JavaScript
- Конструирование DOM
- Макетирование
- Обработка ввода со стороны пользователя (в том числе прокрутка в присутствии активных обработчиков тач-событий)
Если бы документ в нашем примере не полагался на JavaScript для создания элемента
<my-app>
, то содержимое документа, скорее всего, стало бы интерактивным как только загрузится достаточно CSS и контента для осмысленного рендеринга.Исполнение скрипта задерживает интерактивность несколькими способами:
- Если скрипт исполняется дольше 50 мс, время до достижения интерактивного состояния увеличивается на всё время, необходимое для скачивания, компиляции и исполнения JS
- Любой DOM или UI, созданный на JS, недоступен для использования, пока работает скрипт
Изображения же не блокируют основной поток, не блокируют взаимодействие во время парсинга и растрирования и не мешают другим частям UI запуститься в интерактивном режиме или сохранить её. Поэтому картинка 150 КБ не увеличит значительно TTI, а вот JS такого размера задержит интерактивность на время, необходимое для следующих задач:
- Запрос кода, включая DNS, TCP, HTTP с накладными расходами на разархивирование
- Разбор и компиляция функций верхнего уровня JS
- Выполнение скрипта
Эти шаги часто повторяются.
Если выполнение скрипта укладывается в 50 мс, то TTI не увеличится, но это нереально. 150 КБ сжатого JavaScript разархивируется примерно в 1 МБ кода. Как задокументировал Эдди, весь процесс занимает более секунды на большинстве телефонов в мире, не считая времени на скачивание.
JavaScript — самая тормозящая часть любой веб-страницы как для загрузки, так и для производительности устройства. Разработчикам и менеджерам с быстрыми смартфонами в быстрых сетях эти скрытые затраты могут быть вдвойне непонятны.
Глобальная правда
Крайне важно решить, какой бенчмарк использовать для определения бюджета производительности. Некоторые разработчики и компании очень близко знают свою аудиторию — и могут делать информированные оценки об устройствах и сетях для нынешних и будущих пользователей. Однако у большинства нет такой информации для выставления базового уровня. С чего начать?
Здесь важны две цифры:
Медианный пользователь работает в медленной сети. Вопрос только в том, насколько медленное соединение.
Наши метрики в Google дают противоречивую картину (я работаю над тем, чтобы внести ясность). Некоторые системы показывают медианные RTT около 100 мс для пользователей 3G. Другие показывают, что медианный пользователь не способен передать или получить отдельный пакет менее чем за 400 мс на некоторых крупных рынках.
Полагаю, следует выбрать консервативный вариант. Соперничающие, перегруженные соты способны сделать «быстрые» сети брутально медленными, дисперсия передачи сильно уменьшает эффективность TCP, а натуральные всплески сетевого трафика работают против нас.
Для разработчиков Google работает специально созданная сеть «деградированного 3G», чтобы оценивать поведение приложений в таких условиях. Она симулирует соединение с RTT 400 мс и пропускной способностью 400-600 Кбит/с (плюс дисперсия задержки и симуляция потери пакетов). Учитывая противоречивую картину, которую показывают наши метрики, это можно принять за базовый уровень.
Однако симуляция потери пакетов и дисперсия задержки способны сильно затруднить бенчмарки и занизить результаты. Эффект потерянного пакета во время поиска DNS даёт разницу в секунды, что затрудняет сравнение результата до и после внесённых во время разработки изменений. Вероятно, наш базовый уровень должен взять более низкую пропускную способность и повышенную задержку, пожертвовав потерей пакетов. Мы теряем в точности реального мира, зато получаем повторяемость тестов, возможность сравнивать результаты до и после внесённых изменений, а также сравнивать разные продукты. Здесь ещё можно говорить и говорить о влиянии DNS, TLS, сетевой топологии и других факторов. Если хотите углубиться в эту тему, настоятельно рекомендую книгу Ильи Григорика “High Performance Browser Networking”. Одно лишь описание RRC стоит вашего потраченного времени.
Вернёмся к нашему базовому уровню. Мы примерно определились с симуляцией сети: RTT 400 мс, канал 400 Кбит/с. Что насчёт самого устройства?
На прошлогодней конференции Chrome Dev Summit я обсудил некоторые температурные и энергетические ограничения, которые создают огромную разницу в производительности между мобильным и десктопным устройством. Добавьте сюда зияющую пропасть между производительностью топовых и дешёвых устройств из-за разных характеристик чипа, таких как размер кэша. К счастью, там проще подобрать базовый уровень, чем со скоростью сети: более половины мобильных пользователей в США используют устройства под Android. Если посмотреть на другие страны, то подавляющее большинство продаваемых смартфонов сейчас (и последние пять лет) работают под Android. Средняя цена таких устройств в большинстве регионов падает благодаря повсеместной распространённости Android и неуклонному снижению цены в экосистеме. В свою очередь, это влияет на единственный главный тренд в определении глобального базового уровня бюджета производительности по оборудованию: следующий миллиард пользователей выйдет в онлайн, когда сможет себе это позволить. Это означает снижение средней цены смартфонов в обозримом будущем. А это, в свою очередь, означает, что все улучшения в количестве-транзисторов-на-доллар конвертируются в более низкую цену, а не в более быстрые устройства (в среднем).
В 2016 году истинно медианное устройство продавалось примерно за $200 без привязки к оператору. В этом году медианное устройство ещё дешевле, но примерно с такой же производительностью. Можно ожидать, что производительность медианного устройства застынет на одном уровне ещё несколько лет. Это одна из причин, почему я в прошлом году предложил в качестве базового устройства Moto G4, а в этом году рекомендую его же или Moto G5 Plus.
Подводя итог, наш глобальный базовый уровень для измерения производительности такой:
- ~$200 (новый, без привязки к оператору) Android-смартфон
- На медленных сетях 3G, эмуляция:
- RTT 400 мс
- скорость 400 Кбит/с
Для большинства разработчиков создание приложений в такой среде сродни выращиванию овощей на Марсе. К счастью, эта конфигурация доступна на webpagetest.org/easy, так что мы можем воссоздать марсианские условия здесь на Земле в любое время.
Вычисление приемлемого уровня
Последнее, что нужно обсудить в бюджете производительности — это время. Какое время будет слишком долгим?
Мне нравится определение Моники:
«The Monica Perf Test: если вы успеваете отвести взгляд до зрительного контакта с незнакомцем, пока загружается приложение до первой отрисовки, то это слишком медленно»
…но оно скорее качественное, чем количественное. В цифрах, хотелось бы, чтобы каждая загрузка страницы занимала менее секунды (см. RAIL). В реальном мире такое невозможно, так что мы с партнёрами установили метрику Time-to-Interactive (TTI):
- TTI до 5 секунд для первой загрузки
- TTI до 2 секунд для последующих загрузок
Теперь у нас есть всё необходимое для создания примерного бюджета производительности для продукта в 2017 году.
Первая загрузка
Если вычесть время, условия сетевого соединения и основные этапы критического пути, то мы получаем несколько интересных результатов. Можно начать с бюджета для первой загрузки в 5 секунд и посчитать, какую передачу можно себе позволить.
Сначала вычтем из бюджета 1,6 секунды на поиск DNS и рукопожатие TLS, что оставляет 3,4 секунды для всего остального.
Теперь посчитаем, сколько данных можно передать по такому каналу за 3,4 секунды: 400 Кбит/с = 50 КБ/с. 50 КБ/с * 3,4 = 170 КБ.
ПРИМЕЧАНИЕ: Это обсуждение явно приведёт в ярость компетентных сетевых инженеров. В предыдущих версиях этой статьи обсуждались медленный старт, bdp, масштабирование окна tcp и тому подобное. Всё это было сложно понять. Упрощение не слишком значительно влияет на общие выводы, так что эти подробности были исключены.
Современные веб-приложения в основном состоят из JS, то есть нам также нужно вычесть время на разбор и оценку JS. Сжатие gzip для кода JS составляет от 5x до 7x, так что 170 КБ архива JS превращаются примерно в 850 КБ-1 МБ кода JS. Согласно предыдущим оценкам, для его запуска требуется около секунды (предполагая, что там нет ресурсоёмкого DOM, а он конечно же есть). Поиграв немного с этими цифрами, можно уложиться в 3,4 секунды скачивания и парсинга/оценки, ограничившись передачей JS в размере 130 КБ.
И последняя деталь: если какой-то из ресурсов критического пути загружается из другого места (например, из CDN), то нужно вычесть из бюджета ещё время соединения с ним (~1,6 с), что ещё больше сокращает долю времени из 5 секунд, которую мы можем потратить на сетевую передачу данных и работу на стороне клиента.
Подводя итоги, в идеальных условиях наш примерный бюджет для ресурсов критического пути (CSS, JS, HTML и данные) составляет:
- 170 КБ для сайтов без особого количества JS
- 130 КБ для сайтов, сделанных на фреймворках JS
Это даёт нам возможность рассмотреть единственный самый неотложный вопрос, который стоит в современной фронтенд-разработке: «Вы можете себе это позволить?»
Например, есть ваш JS-фреймворк отнимает ~40 КБ на сайте, перегруженном JS (у которого бюджет 130 КБ благодаря времени обработки JS), то остаётся всего лишь 90 КБ на остальное. Всё ваше приложение должно поместиться в этот объём. 100-килобайтный фреймворк, загружаемый с CDN, уже на 20 КБ превышает бюджет.
Вспомните: ваш любимый фреймворк может уложиться в 40 КБ, но что насчёт системы данных? Router-компонентов, которые вы добавили? Внезапно 130 КБ уже не кажутся таким уж большим объёмом, с учётом данных, шаблонов и стилей.
Жизнь по бюджету означает постоянно спрашивать себя: «Действительно ли я могу себе это позволить?»
Вторая загрузка
В идеальном мире все страницы загружаются быстрее, чем за секунду, но по многим причинам часто это неосуществимо. Поэтому дадим себе чуть больше свободы и бюджет 2 секунды на вторую (третью, четвёртую и т.д.) загрузку.
Почему не пять? Потому что нам больше не нужно обращаться в сеть для загрузки UI. Service Workers и архитектуры offline first позволяют вывести интерактивные пиксели на экран, вообще не обращаясь к сети. Это ключ к стабильно высокой производительности.
Две секунды — это вечность на современных CPU, но нам по-прежнему нужно грамотно их распределить с учётом следующих факторов:
- Время создания процесса (Android относительно медленный по сравнению с другими ОС)
- Время для чтения байтов с диска (оно ненулевое даже на флеш-накопителях!)
- Время выполнения кода
Все виденные мною приложения, которые укладывались в пятисекундную первую загрузку и правильно реализовали принцип offline-first, укладывались также в бюджет двух секунд, и достижение лимита в одну секунду тоже возможно! Но внедрение offline-first — огромная проблема для многих команд. Проектирование с локальным сохранением последних пользовательских данных (last-seen), надёжное и последовательное кэширование ресурсов приложения, фокусы с обновлением кода приложения с помощью жизненного цикла Service Worker могут стать большой задачей.
Я с нетерпением жду, что инструменты продолжат развиваться в этом направлении. Самый продвинутый из известных мне фреймворков сейчас — Polymer App Toolbox, так что если не уверены с чего начать, начинайте с него.
130-170 КБ… Да вы точно шутите!?!
Многие команды, с которыми нам пришлось разговаривать, задавали вопрос: а возможно ли вообще уместить что-то осмысленное в такой маленький объём 130 КБ. Возможно! Паттерн PRPL делает это с помощью агрессивного разделения кода в зависимости от маршрута, кэширования гранулированных ресурсов (последовательные страницы) с помощью Service Worker и умного использования современных расширений протоколов вроде HTTP/2 Push.
Все вместе эти инструменты позволяют уместить современный функциональный интерфейс менее чем в 100 КБ по критическому пути.
К сожалению, по-прежнему сложно из конкретного лога определить, какие части страницы являются критическими ресурсами для TTI, а какие нет. Но я верю, что инструменты быстро исправят этот недостаток, учитывая исключительную важность данной метрики.
Несмотря на все рассуждения, можно уложиться в бюджет даже не отказываясь полностью от фреймворков. И Wego, и Ele.me созданы с помощью современных инструментов (Polymer и Vue, соответственно) — и реально работают сегодня, помогая клиентам осуществлять транзакции. Большинство приложений менее сложны, чем эти. Жить по бюджету не значит голодать.
Инструменты для команд на бюджете
Уложиться в бюджет действительно трудно, но выгоды для бизнеса и пользователей огромны. Не так часто обсуждаются выгоды для групп разработчиков и их руководителей. Ни один ведущий программист или менеджер проекта не хочет оказаться напротив руководителя, который подходит к нему с телефоном в руках и спрашивает: «А чего оно работает так медленно, когда я в отпуске?»
Это не теоретические рассуждения.
Я видел команды, которые только что закончили переделку кода на современном технологическом стеке — и они в течение часа сидели прибитыми, когда мы показывали их «лучшее» и «быстрое» приложение в условиях реального мира.
Любой потеряет лицо, если продукт не соответствует ожиданиям. Месяцы незапланированного аврала по повышению производительности отодвигают внедрение новых функций и негативно влияют на моральный дух команды. Когда производительность становится проблемой, менеджеры среднего звена стараются подавить неуверенность в себе и в то же время закрыть команду от летящего дерьма, на что сама команда и рассчитывает. Хуже того, менеджеры могут и сами начать сомневаться в команде. Кризис производительности может иметь долговременные последствия; может ли организация быть уверена, что команда выдаст качественный продукт? Могут ли они доверять ведущему программисту, который рекомендует использовать новую технологию или осуществить дополнительные крупные инвестиции? Потом взаимные обвинения. Это ужасный опыт, особенно для разработчиков, которые слишком часто находятся в позиции невероятного давления «исправь это как можно быстрее» — и «это» может быть базовой технологией, на которой основан продукт.
В худших случаях продукт невозможно исправить за время достаточно малое, чтобы помочь бизнесу. Часто развитие идёт эволюционным путём, и если стартап или небольшая команда сделала ставку на неправильный стек технологий, то в отсутствие времени ошибка может стать фатальной. Что самое плохое, ошибку могут не замечать очень, очень долго. Если все сотрудники группы носят в карманах топ-смартфоны с последней версией iOS и не выезжают из города, а экономика продукта основана на росте широкой аудитории, то никто не поймёт отсутствие роста этой аудитории.
Конечно, производительность — это не (весь) продукт. Многие заторможенные или предназначенные для узкой ниши приложения великолепно себя чувствуют. Уникальный сервис, который нужен людям (и они готовы постараться, чтобы его получить), способен перевесить любые эти опасения. Некоторые преуспели даже в App Store и Play Market, где непросто привлечь аудиторию. Но продуктам на конкурентных рынках важно каждое преимущество.
Некоторые конкретные инструменты и техники могут помочь в работе командам, которые внедряют бюджет производительности:
- webpagetest.org/easy: наш любимый инструмент для одноразового анализа
- Скрипты WPT: для команд, которые не хотят устанавливать специальный инстанс WPT, и у них есть публичные URL приложений WIP, интеграция со скриптами WPT может стать хорошим вариантом для проведения регулярных «проверок»
- Приватные инстансы WPT: если команда хочет интегрировать WPT напрямую в свою CI или в системы commit-queue (автоматизированная очередь тестов перед коммитом), то следует изучить вариант установки приватного сервера WPT и оборудования
- Scripted Lighthouse: не готовы для полноценного инстанса WPT? Scripting Lighthouse поможет CI автоматизировать анализ сайта и поиск регрессий
grunt-perfbudget
— ещё более простой инструмент автоматизированного WPT-тестирования для вашей CI. Используйте его!- Speedcurve и Calibre: сетевые сервисы автоматизируют регулярную проверку производительности в условиях реального мира
- Webpack Performance Budgets: если команда использует Webpack на этапах сборки, то эта конфигурация во время разработки выдаёт очень удобные предупреждения о ресурсах, превышающих бюджет
- bundlesize и pr-bot устанавливают бюджеты для каждого скрипта, которые будут автоматически приведены в действие в процессе пулл-реквеста. Рекомендуется!
Борьба с раздутием кода часто означает, что нужно сделать из предупреждений явные ошибки. Командам, использующим системы непрерывной интеграции или автоматизированные очереди тестов перед коммитом, настоятельно рекомендуется запретить коммиты, которые превышают бюджет производительности.
Если группа начинает работать с нуля, моя настоятельная рекомендация — начать со стека, ориентированного на структуру приложения, разделение кода и целевую сборку. Лучшее на сегодняшний день:
- Polymer App Toolbox
- Next.js, предпочтительно с Preact в качестве более лёгкой библиотеки исполняющей системы
Какие бы инструменты ни выбрала ваша команда, самым важным является бюджет. Без него даже самые продвинутые «лёгкие» фреймворки могут запросто создать раздутые, непригодные к использованию приложения. Начинать с глобального базового уровня и увеличивать бюджет только на основании конкретных метрик — единственный известный мне способ гарантировать, что проект будет хорош для всех.
Примечания
Ради экономии времени и места придётся отложить для следующей статьи обсуждение архитектур, имеющих запас на будущее. Любопытствующие могут изучить Service Workers, Navigation Preload и Streams. Их совместная мощь должна фундаментально трансформировать оптимальное время загрузки страниц в 2018 году и далее.
И последнее. Благодарю всех, кто просмотрел первые черновики этой статьи, среди них: Винамрата Сингал, Пол Кинлэн, Питер О’Шонесси, Эдди Османи и Грэй Нортон. Надеюсь, их героические попытки избавить статью от ошибок не уступают моему таланту добавлять новые.
Комментарии (44)
staticlab
21.12.2017 10:55предпочтительно с Preact в качестве более лёгкой библиотеки исполняющей системы
Легковесность — это, безусловно, хорошо. Но, к сожалению, можем споткнуться о проблемы совместимости сторонних компонентов с Preact.
andreymal
21.12.2017 11:11Ладно инстаграм, они не захотели нагружать и без того нагруженные сервера генерацией HTML-кода и перенесли весь рендеринг в браузер. А нахрена SPA всем остальным?
vasIvas
21.12.2017 11:21Мне кажется что spa прежде всего для пользователей, так как пропадает раздражающая перезагрузка, которая к тому же задерживает рендер. Кроме того, интерактив (изменении информации вреальном времени), без которого сложно представить современное приложение, будет очень задерживать рендер из-за своей «неповоротливости». Ещё мне как разработчику больше нравится spa.
andreymal
21.12.2017 11:24пропадает раздражающая перезагрузка
Я уже писал об этом ранее: просто подгружаем кусок HTML-кода аяксом и пихаем на страницу — всё, раздражающая перезагрузка пропала. Скрипт килобайт на десять максимум, с учётом костылей под все браузеры. Так уже делают ВК и гитхаб, например
интерактив
То же самое, всё это делается простыми скриптами в несколько килобайт без тотального SPA (но впадать в крайности с jQuery-лапшой не надо). Возьмите тот же хабрахабр: он ни разу не SPA, но вы же наверно не будете отрицать, что комментарии очень даже интерактивны?)
staticlab
21.12.2017 13:28Для справки, у нас фронтовый скрипт небольшого изоморфного приложения (React 15, Redux, React-Router 3, полифиллы для IE) весит 58 кб (gzip). При этом потенциал для оптимизации ещё есть (переход на React 16, замена некоторых библиотек).
andreymal
21.12.2017 14:01Для справки, у меня скрипт для полной аяксификации сайта (велосипед-аналог pjax) весит 50кб со всеми костылями и документацией, с минификацией и гзипом ужался в 1.6кб)
Проба сжатия на скорую руку остальных скриптов одного крупного сайта дала 55кб, но тут в лоб не совсем корректно сравнивать, нужно ещё фичастость учитывать
justboris
22.12.2017 00:51А если надо перезагрузить контент не в одном блоке, а в разных местах страницы?
Так уже делают ВК и гитхаб, например
Регулярно натыкаюсь в ВК на такие баги. После нескольких переходов по страницам состояние нотификаций и текущего трека в шапке сайта одно, а внизу в коненте — другое. А все как раз потому, что данные приходят с сервера отдельными кусочками и не синхронизируются, в отличие от нормальных SPA, где все рендерится из единого стора.
andreymal
22.12.2017 00:55А если надо перезагрузить контент не в одном блоке, а в разных местах страницы?
Загружаем несколько контентов, делов-то
состояние нотификаций и текущего трека в шапке сайта одно, а внизу в коненте — другое
Лично я с этим не сталкивался, но поверю, что оно есть. Ничто не мешает подгружать куски HTML-кода и поменять ему пару классов согласно информации из единого стора. То, что разработчики ВК так не сделали (если правда не сделали) — их личные проблемы.
justboris
22.12.2017 01:11А можете рассказать поподробнее? Я смотрю документацию pjax и вижу, что там можно обновить только один контейнер по клику:
$('.group-invite-accept').on('click', event => { $.pjax.submit(event, '#pjax-container'); });
Как в вашей имплементации можно обновить несколько блоков? И где будет написано по какому url какой блок забирать?
andreymal
22.12.2017 01:13pjax хрень, но ничего не мешает сделать (и я делал) аналог, позволяющий передавать несколько контентов, мыслите абстрактнее и не зацикливайтесь на конкретной реализации :)
И где будет написано по какому url какой блок забирать?
А это вообще зачем?
justboris
22.12.2017 01:16Так расскажите поподробнее о своей реализации! Как будет выглядеть перезагрузка нескольких контейнеров в примере кода выше?
Все варианты, что я сейчас могу придумать, похожи на спагетти-код.
andreymal
22.12.2017 01:19Как будет выглядеть перезагрузка нескольких контейнеров в примере кода выше?
Я ещё не понял, какая задача решается? Возможно, вы пытаетесь впихнуть pjax туда, где достаточно простого fetch (и совсем необязательно это будет спагетти-код, если руки не кривы)
justboris
22.12.2017 02:42Я ещё не понял, какая задача решается?
Что-нибудь как в ВК, есть центральный контент, а есть левое меню. Нужно счетчики слева обновлять, например когда приглашение в группу принято, нужно там новую цифру подгрузить.
andreymal
22.12.2017 11:13Нет смысла юзать для этого pjax. Центральный контент пусть подгружается как HTML-код с pjax'ом, а счётчики простыми числами гоняются по каким-нибудь вебсокетам (быть может, с хранением их в едином сторе, тут архитектура уже на свой вкус). Поменять пару классов и textContent'ов по событию из вебсокетов или из стора — задача элементарная, реактов и ангуляров не требующая.
justboris
22.12.2017 11:37Ага, то есть все-таки надо будет написать сколько-то клиентского кода, а не
просто подгружаем кусок HTML-кода аяксом и пихаем на страницу
как вы писали ранее.
В итоге так все равно получаем большой js-бандл с лапшой из селекторов в разные места страницы, "зато не на реакте", прекрасно.
andreymal
22.12.2017 11:43Зависит от задачи, очевидно же. Pjax-подобные решения покрывают процентов девяносто задач, для остального, если вы внимательно читали мои комментарии, я и сам накатал 55кб сжатого жс-кода на одном большом сайте. С реактом получилось бы раз в пять больше.
Если вы не умеете писать на js и ваш код с селекторами постоянно получается лапшой — это ваши личные проблемы с вашей кривизной рук, уж простите ?\_(?)_/?justboris
22.12.2017 11:50Конечно, у всех кривые руки, один вы профессионал. Вот Хабрахабр например. На эту страницу грузится ~80 Кб кода на Jquery (не считая веса библиотек). И все равно встречаются вот такие баги.
Можно сколько угодно пенять на кривые руки, а можно сделать рендер комментариев из json полностью на клиенте и исключить такую проблему в принципе.
andreymal
22.12.2017 11:54Рендер на клиенте от «таких багов» не поможет. Забыли обновить статус прочитанности в сторе — всё, получаем тот же баг.
dom1n1k
21.12.2017 14:51Проверил сейчас сайты нескольких популярных европейских интернет-магазинов, которыми пользуюсь. Все сделаны по классической схеме с рендерингом на сервере + вкрапления ajax в некоторых элементах. Перезагрузка вообще не раздражает, поскольку происходит практически мгновенно. Щелк — лаг 0.5-1 сек — и новая страница открылась. Картинки, конечно, догружаются ещё пару-тройку секунд, но страница в это время уже полностью дееспособна. Это в 100 раз круче, чем наблюдать долбаные спиннеры. И это при том, что все эти сайты (я немного посмотрел в дев-тулс) ещё далеко не идеалы оптимизации, ещё есть что улучшить.
DistortNeo
22.12.2017 14:49Всё верно, SPA для интернет-магазинов — зло. Использование магазина подзумевает открытие нескольких вкладок для выбра товара. Не очень приятно при каждом открытии вкладки ожидать её прогрузки, потому что какой-то недальновидный программист решил магазин оформить как SPA.
Ещё бесит SPA в почтовиках (типа gmail).
SPA нужно использовать только там, где это действительно оправдано, а не лепить везде, потому что это модно.staticlab
22.12.2017 14:56То есть SPA в вашем понимании не включает в себя изоморфизм?
DistortNeo
22.12.2017 15:15То есть SPA в вашем понимании не включает в себя изоморфизм?
Не могу дать ответа на вопрос, я не веб-разработчик, для меня изоморфизм — это понятие из теории графов.
SPA в моём понимании — сайты, где многооконность сознательно огорожена программистом: невозможность открытия письма в новой вкладке, потеря состояния и рассинхронизация при работе сразу в нескольких вкладках и т.д.
Так сложилось, что большинство подобных SPA-сайтов ещё и перегружены скриптами, поэтому их многократная загрузка в нескольких вкладках приводит к совершенно излишним тормозам.
Всё, что я наблюдаю: простые сайты, которые сделаны с минимумом JS, удобны в использовании, ну а нафаршированные JS — обычно нет. А вот используется там изоморфный рендеринг или нет, мне все равно.
staticlab
22.12.2017 16:21Хорошо, как же тогда назвать одностраничные приложения, которые позволяют и открывать отдельные страницы в новой вкладке, и синхронизируются между вкладками, и не требуют ожидания прогрузки данных в странице?
DistortNeo
22.12.2017 16:56Хорошо, как же тогда назвать одностраничные приложения, которые позволяют и открывать отдельные страницы в новой вкладке, и синхронизируются между вкладками, и не требуют ожидания прогрузки данных в странице?
Не знаю. Но если основной сценарий работы с сайтом предполагает открытие множества специализированных страниц, то какой смысл его реализовывать как SPA?
dom1n1k
22.12.2017 19:53И весь-весь нативный функционал работает? Как например открыть ссылку в новой вкладке (через контекстное меню и через ctrl+click), новом окне, новом приватном окне, добавить в закладки, сохранить объект как…
sumanai
22.12.2017 20:05А почему бы и нет? Достаточно лишь того, чтобы ссылки были ссылками и ввели на реальную страницу, плюс рендеринг на стороне сервера. Тогда можно хоть JS отключать. Конечно, функционал, завязанный на скрипты, при их отключении работать не будет, но открыть письмо в отдельной вкладке- раз плюнуть.
andreymal
22.12.2017 20:05В нормально сделанном SPA всё так и есть. Правда, ненормальные попадаются как-то чаще)
dom1n1k
23.12.2017 15:15+1Так и понятно почему. Реализовать-то можно если не всё, то почти всё. Но это адовый объем работы с непонятной мотивацией — на коленке реализовать и отладить всё то, что нативно в браузере есть из коробки. Но обладает фатальным недостатком.
youROCK
21.12.2017 12:45Не стоит недооценивать человеческую лень :). Я, например, как разработчик backend'а, особенно не на PHP, с радостью полностью избавился от необходимости рендерить HTML на сервере. Когда JavaScript всё равно «знает» про то, какие элементы есть на страницах, намного удобнее сделать так, чтобы сам JavaScript его и рендерил — это сильно уменьшает необходимость в общении между фронтенд-разработчиками и бэкенд-разработчиками. Заодно можно сделать мобильные и десктопные клиенты, которые будут пользоваться тем же API — не красота ли?
Конечно же, это означает, что запросов на сервер будет делать больше, и статики нужно больше, и статья как раз о том, как не допустить того, чтобы это вызывало проблемы производительности на клиенте.
andreymal
21.12.2017 14:06Ну, и с рендерингом HTML на сервере общение между фронтом и бэком тоже можно сильно уменьшить, выдав фронтам шаблонизатор (Jinja2 какой-нибудь, учится максимум за сутки) и передавая в контекст шаблонов всё то же, что передаётся по API. Фронтам остаётся только просить передать в шаблон что-нибудь новое, что бэки забыли передать, но мне это не кажется большой проблемой (хотя у меня очень маленький опыт работы в командах и я могу быть не прав)
И да, рендеринг HTML на сервере избавляет от необходимости иметь JavaScript вообще, на радость параноикам c NoScript ;)staticlab
21.12.2017 14:30А зачем так усложнять, если изоморфный рендеринг позволяет разделить и упростить шаблонизацию? Кстати, в идеальном случае он тоже позволяет избавиться от клиентских скриптов.
andreymal
21.12.2017 14:33Я правильно понимаю, что вы подразумеваете реакт на сервере? Тогда может быть. Но мне это кажется лишь усложнением)
DistortNeo
22.12.2017 14:09Неужели проблема с рендерингом HTML настолько серьёзна? Я всегда считал, что самое тяжёлое — это работа с базой и бизнес логика, а расходы на преобразование данных в HTML малозначимы.
andreymal
22.12.2017 14:13Когда счёт идёт на миллионы запросов в секунду и больше, даже что-то малозначимое начинает означать нагрузку на несколько мощных серверов, на которых бизнес будет очень рад сэкономить)
DistortNeo
22.12.2017 14:34Если это обернётся снижением числа пользователей (нагрузка на клиенты-то возрастёт), то экономия получится сомнительной.
andreymal
22.12.2017 14:38Мне обычно рассказывают, что становится наоборот быстрее и повышеннее. Правда, в сравнении с «обычными» сайтами; сравнений с pjax-подобными вещами я не встречал
quwy
22.12.2017 03:34Много правильных слов, а тем временем средний размер страницы превысил размер популярной трехмерной игры.
Тотальная заскриптованность приводит к тормозам даже на топовых десктопах. Да еще и макаки, которые ради того, что реализуется голым HTML/CSS, тянут мегабайтные фреймворки.
jaiprakash
Иногда начинало казаться что мы идём к тому, что все сайты будут тестироваться в сетях 100+ Мбит/с, <5 мс задержки, только топовом железе и только последних браузерах.