Привет!
В последнее время много занимались проблемой качественного tree-shaking кода библиотек, которые участвуют в сборке
Нашли в пакетах tramvai репозитория много проблемных кейсов, которые не позволяют
Частные случаи таких проблем можно решить, например с помощью комментария
Мой коллега нашел удачное и общее решение проблемы tree-shaking в рамках трамвай репозитория - переложить больше работы на плечи
Раньше, трамвайные пакеты собирались в один файл, и для терсера доходил практически весь код пакетов, который он оптимизировал по своим возможностям.
Теперь, доработав нашу утилиту
Благодаря этому, вебпак может не включать в граф модулей не используемые файлы, что автоматически предотвращает попадание этого кода в бандл, даже если терсер бы не смог его вырезать.
В итоге получился такой набор рекомендаций для создания библиотек, хорошо поддающихся оптимизациям:
- Не используйте декораторы
- Не используйте статические свойства для
- Делайте отдельную сборку с ES modules
- Разделяйте логику в пакетах по небольшим модулям
- Сохраняйте файловую структуру этих пакетов при сборке
- Добавляйте в
- Транспилируйте исходники в ES2019 код для ES modules и браузерной сборок (немного мотивации в статье по ссылке)
- Обязательно проверяйте tree-shaking ваших пакетов на дефолтной webpack production сборке, добавляйте комментарий
Утилита
Кстати, референсом для
Большую часть этих советов можно найти в статьях:
- Гайд по tree-shaking в Webpack документации
- Статья "How To Make Tree Shakeable Libraries"
В последнее время много занимались проблемой качественного tree-shaking кода библиотек, которые участвуют в сборке
tramvai приложений.Нашли в пакетах tramvai репозитория много проблемных кейсов, которые не позволяют
terser вырезать не используемый код - декораторы после транспиляции, статические свойства у функций, использование функций из сторонних пакетов (любой compose, createToken и вызовы других методов будут для terser потенциальным side-effect).Частные случаи таких проблем можно решить, например с помощью комментария
/* @__PURE__ */, но это либо ручная работа, либо магия babel плагинов.Мой коллега нашел удачное и общее решение проблемы tree-shaking в рамках трамвай репозитория - переложить больше работы на плечи
webpack.Раньше, трамвайные пакеты собирались в один файл, и для терсера доходил практически весь код пакетов, который он оптимизировал по своим возможностям.
Теперь, доработав нашу утилиту
@tramvai/build мы собираем трамвайные пакеты с сохранением их структуры, по типу транспиляции через tsc.Благодаря этому, вебпак может не включать в граф модулей не используемые файлы, что автоматически предотвращает попадание этого кода в бандл, даже если терсер бы не смог его вырезать.
В итоге получился такой набор рекомендаций для создания библиотек, хорошо поддающихся оптимизациям:
- Не используйте декораторы
- Не используйте статические свойства для
react компонентов- Делайте отдельную сборку с ES modules
- Разделяйте логику в пакетах по небольшим модулям
- Сохраняйте файловую структуру этих пакетов при сборке
- Добавляйте в
package.json поле "sideEffects": false (или указывайте массив файлов, которые нельзя вырезать - "sideEffects": ["some-global.css"])- Транспилируйте исходники в ES2019 код для ES modules и браузерной сборок (немного мотивации в статье по ссылке)
- Обязательно проверяйте tree-shaking ваших пакетов на дефолтной webpack production сборке, добавляйте комментарий
/* @__PURE__ */ при необходимостиУтилита
@tramvai/build из коробки дает вам сборку CJS и ES модулей, modern JS код, сохранение файловой структуры, поддержку отдельной browser сборки, и рекомендуется для наших внутренних команд для сборки любых пакетов, используемых SSR приложениями.Кстати, референсом для
@tramvai/build была отличная тулза microbundleБольшую часть этих советов можно найти в статьях:
- Гайд по tree-shaking в Webpack документации
- Статья "How To Make Tree Shakeable Libraries"
GitHub
GitHub - Tinkoff/tramvai: A modular framework for universal JS applications
A modular framework for universal JS applications. Contribute to Tinkoff/tramvai development by creating an account on GitHub.
👍10❤1🔥1
Привет!
Пытаюсь вернуться к записям, пока не много своих мыслей, больше похоже на обзор статьи.
Раз в месяц провожу общекомандные встречи на тему Web Performance, и попадаются интересные статьи, хочется делать обзоры на некоторые из них.
Тема на сегодня - Web Vitals метрика Largest Contentful Paint, и парочка отличных статей, которые мы рассмотрим:
- Способы улучшения LCP от Calibre - https://calibreapp.com/blog/largest-contentful-paint
- Интересный подход к измерению клиентских метрик на примере LCP - https://csswizardry.com/2022/08/measure-what-you-impact-not-what-you-influence/
Пытаюсь вернуться к записям, пока не много своих мыслей, больше похоже на обзор статьи.
Раз в месяц провожу общекомандные встречи на тему Web Performance, и попадаются интересные статьи, хочется делать обзоры на некоторые из них.
Тема на сегодня - Web Vitals метрика Largest Contentful Paint, и парочка отличных статей, которые мы рассмотрим:
- Способы улучшения LCP от Calibre - https://calibreapp.com/blog/largest-contentful-paint
- Интересный подход к измерению клиентских метрик на примере LCP - https://csswizardry.com/2022/08/measure-what-you-impact-not-what-you-influence/
Calibre - Site Speed Tools for Teams
How To Improve Largest Contentful Paint for Faster Load Times
Optimising Largest Contentful Paint has an outsized impact on when most critical content appears. These four proven methods will help you find and correct performance issues holding your page back.
👍2🔥2
LCP - метрика скорости отрисовки самого значимого контента на странице, и в теории полезного для пользователя.
Как правило это некое самое большое изображение в видимой области страницы.
Первый инсайт из статьи - LCP помимо изображения может быть video элементом, или текстом, как правило это
Хороший пример - это Smashing Magazine, у которых в мобильной версии именно заголовок статьи является LCP, а до оптимизации была аватарка автора (и заодно отличная статья как Smashing Magazine улучшали Web Vitals - https://www.smashingmagazine.com/2021/12/core-web-vitals-case-study-smashing-magazine/).
Второй инстайт - LCP это комплексная метрика, и ее оптимизация состоит из множества этапов.
В первую очередь, для ускорения надо уменьшить LCP элемент, и соответственно уменьшить время до его отрисовки.
Что мы можем сделать для изображения:
- Выбираем лучший формат,
- Прогоняем через оптимизаторы для уменьшения размера (сервис
- Отдаем оптимальный размер с помощью
Для видео:
- Формат
- Компрессия и удаление аудио дорожки
- Используем CDN
Для текста:
- Формат
- Self-hosted хранение шрифтов
- Очищаем от неиспользуемых начертаний и вариаций
- В идеале используем только системные шрифты
Следующая важная метрика для оптимизации, составная LCP - Time To First Byte, время до ответа нашего сервера:
- Используем более мощный сервер/хостинг по возможности
- Используем CDN
- Кэшируем все что можно с помощью cache-control
- Используем Server-Side Rendering / Static-Site Generation / Incremental Static Regeneration
- Мониторим производительность нашего SSR (время запросов к API, время на рендер, etc.)
- Обязательно измеряем TTFB метрику (с помощью https://zizzamia.github.io/perfume/ или https://github.com/GoogleChrome/web-vitals и таких сервисов как `Calibre`)
Затем проводим аудит приоритетов загрузки ресурсов нашего приложения:
- Добавляем
-
- Используем подходящий
- Повышаем приоритет важных ресурсов (используем Priority Hints - https://web.dev/priority-hints/, инлайн критичных ресурсов в HTML, добавляем
И последнее, ускоряем Critical Rendering Path за счет ускорения блокирующих ресурсов:
- Минификация JS и CSS
-
- Critical CSS и code splitting
- Избавляемся от third-party скриптов, либо максимально откладываем их запуск. Также, можно вынести их в Web Workers с помощью
Как правило это некое самое большое изображение в видимой области страницы.
Первый инсайт из статьи - LCP помимо изображения может быть video элементом, или текстом, как правило это
h1 или h2 заголовок.Хороший пример - это Smashing Magazine, у которых в мобильной версии именно заголовок статьи является LCP, а до оптимизации была аватарка автора (и заодно отличная статья как Smashing Magazine улучшали Web Vitals - https://www.smashingmagazine.com/2021/12/core-web-vitals-case-study-smashing-magazine/).
Второй инстайт - LCP это комплексная метрика, и ее оптимизация состоит из множества этапов.
В первую очередь, для ускорения надо уменьшить LCP элемент, и соответственно уменьшить время до его отрисовки.
Что мы можем сделать для изображения:
- Выбираем лучший формат,
webp или avif (тут поможет сервис imgproxy или тег picture)- Прогоняем через оптимизаторы для уменьшения размера (сервис
squoosh или imgproxy)- Отдаем оптимальный размер с помощью
srcsetДля видео:
- Формат
mp4 и h264 кодек- Компрессия и удаление аудио дорожки
- Используем CDN
Для текста:
- Формат
woff2- Self-hosted хранение шрифтов
- Очищаем от неиспользуемых начертаний и вариаций
- В идеале используем только системные шрифты
Следующая важная метрика для оптимизации, составная LCP - Time To First Byte, время до ответа нашего сервера:
- Используем более мощный сервер/хостинг по возможности
- Используем CDN
- Кэшируем все что можно с помощью cache-control
- Используем Server-Side Rendering / Static-Site Generation / Incremental Static Regeneration
- Мониторим производительность нашего SSR (время запросов к API, время на рендер, etc.)
- Обязательно измеряем TTFB метрику (с помощью https://zizzamia.github.io/perfume/ или https://github.com/GoogleChrome/web-vitals и таких сервисов как `Calibre`)
Затем проводим аудит приоритетов загрузки ресурсов нашего приложения:
- Добавляем
preload нашего LCP изображения и/или файла шрифтов-
loading=”lazy” подходит почти для всех изображений, кроме LCP элемента, это уменьшит приоритет загрузки- Используем подходящий
font-display, рекомендуется swap, но может быть скачок контента при смене шрифтов, выбирайте лучший для вас вариант- Повышаем приоритет важных ресурсов (используем Priority Hints - https://web.dev/priority-hints/, инлайн критичных ресурсов в HTML, добавляем
defer для большинства скриптов)И последнее, ускоряем Critical Rendering Path за счет ускорения блокирующих ресурсов:
- Минификация JS и CSS
-
defer и async скрипты- Critical CSS и code splitting
- Избавляемся от third-party скриптов, либо максимально откладываем их запуск. Также, можно вынести их в Web Workers с помощью
partytown - https://partytown.builder.io/Smashing Magazine
Improving Core Web Vitals, A Smashing Magazine Case Study
How to improve Core Web Vitals, a Smashing Magazine case study on how we detected and fixed the bottlenecks, and how we ended up with green scores, all the way.
🔥5👍3⚡1
Плавно переходим к следующей статье!
При измерении веб перформанса, и метрики LCP в частности, можно наблюдать большие погрешности, т.е. скорость работы сайт не детерменированная.
Это большая боль синтетических измерений перформанса в CI например с помощью
Мне кажется, что такие замеры в принципе не жизнеспособны, но есть отличный пример Netflix, которые смогли, но это было очень не просто в реализации - https://netflixtechblog.com/fixing-performance-regressions-before-they-happen-eab2602b86fe
Отдельно отмечу про Real User Monitoring, метрики клиентский производительности тоже будут невероятно шумные, и имеют кучу нюансов (https://www.youtube.com/watch?v=OqbCNprhrlA&ab_channel=Фронтенд), но при наличии большого количества пользователей можно получить значимые результаты на каком-то большом временном отрезке.
Также, основные метрики Web Performance не атомарны, и состоят из множества других метрик, как мы уже разобрали на примере LCP.
Автор статьи “Measure What You Impact, Not What You Influence” выделяет несколько важных пунктов при улучшении и измерении перформанса.
Косвенные изменения
Для улучшения LCP можно сделать многое, даже не думая про LCP - ускорить редиректы, TTFB, critical rendering path, оптимизации изображений, self-hosted ресурсы.
При этом из-за различных погрешностей, мы скорее всего не увидим явное улучшение LCP после конкретной оптимизации, даже если на самом деле его улучшили.
Поэтому важно измерять именно то, что мы изменяем.
Изолированные изменения
С помощью User Timing API можно делать очень точные и изолированные измерения.
Пример, как измерять время загрузки наших стилей, что косвенно улучшит LCP:
Похожим образом можно измерять весь тег
Сигналы vs. Шумы
Поводом для этой статьи стала попытка автора по оптимизации LCP с помощью увеличения приоритета загрузки LCP элемента через
После множества замеров, LCP имел большую погрешность, и увидить эффект от оптимизации не получилось.
Но, на что вообще влияет приоритет загрузки ресурса?
Браузер не может загружать параллельно все ресурсы на страницы по ряду причин, и ставит их в очередь.
Приоритет влияет именно на время нахождения запроса в очереди.
В Network вкладке можно увидеть время
Таким образом, измерив именно то, что мы улучшили, мы можем быть уверены в этом изменении, точно так же, как уверены в том, что эта косвенная оптимизация улучшит другую метрику, в данном случае LCP!
Улучшайте метрики ваших приложений, измеряйте именно то, что вы изменяете, и используйте удобные инструменты.
При измерении веб перформанса, и метрики LCP в частности, можно наблюдать большие погрешности, т.е. скорость работы сайт не детерменированная.
Это большая боль синтетических измерений перформанса в CI например с помощью
lighthouse-cli.Мне кажется, что такие замеры в принципе не жизнеспособны, но есть отличный пример Netflix, которые смогли, но это было очень не просто в реализации - https://netflixtechblog.com/fixing-performance-regressions-before-they-happen-eab2602b86fe
Отдельно отмечу про Real User Monitoring, метрики клиентский производительности тоже будут невероятно шумные, и имеют кучу нюансов (https://www.youtube.com/watch?v=OqbCNprhrlA&ab_channel=Фронтенд), но при наличии большого количества пользователей можно получить значимые результаты на каком-то большом временном отрезке.
Также, основные метрики Web Performance не атомарны, и состоят из множества других метрик, как мы уже разобрали на примере LCP.
Автор статьи “Measure What You Impact, Not What You Influence” выделяет несколько важных пунктов при улучшении и измерении перформанса.
Косвенные изменения
Для улучшения LCP можно сделать многое, даже не думая про LCP - ускорить редиректы, TTFB, critical rendering path, оптимизации изображений, self-hosted ресурсы.
При этом из-за различных погрешностей, мы скорее всего не увидим явное улучшение LCP после конкретной оптимизации, даже если на самом деле его улучшили.
Поэтому важно измерять именно то, что мы изменяем.
Изолированные изменения
С помощью User Timing API можно делать очень точные и изолированные измерения.
Пример, как измерять время загрузки наших стилей, что косвенно улучшит LCP:
<head>
<noscript>performance.mark('CSS Start');</noscript>
<link rel="stylesheet" href="app.css" />
<noscript>
performance.mark('CSS End');
performance.measure('CSS Time', 'CSS Start', 'CSS End');
console.log(performance.getEntriesByName('CSS Time')[0].duration)
</noscript>
</head>
Похожим образом можно измерять весь тег
head, т.к. по сути он весь блокирует Critical Rendering Path.Сигналы vs. Шумы
Поводом для этой статьи стала попытка автора по оптимизации LCP с помощью увеличения приоритета загрузки LCP элемента через
fetchpriority="high".После множества замеров, LCP имел большую погрешность, и увидить эффект от оптимизации не получилось.
Но, на что вообще влияет приоритет загрузки ресурса?
Браузер не может загружать параллельно все ресурсы на страницы по ряду причин, и ставит их в очередь.
Приоритет влияет именно на время нахождения запроса в очереди.
В Network вкладке можно увидеть время
Queueing запроса, и повышение приоритета с полутора секунд уменьшило его почти до нуля.Таким образом, измерив именно то, что мы улучшили, мы можем быть уверены в этом изменении, точно так же, как уверены в том, что эта косвенная оптимизация улучшит другую метрику, в данном случае LCP!
Улучшайте метрики ваших приложений, измеряйте именно то, что вы изменяете, и используйте удобные инструменты.
Medium
Fixing Performance Regressions Before they Happen
How the Netflix TVUI team implemented a robust strategy to quickly and easily detect performance anomalies before they a
🔥4👍2⚡1
Привет!
TLDR: Ищу коллегу в техническую команду!
Уже почти три года я работаю в Тинькофф в технической команде над SSR мета-фреймворком https://tramvai.dev
Мне всегда было интересно работать над классным продуктом, делать сложный UI, улучшать производительность и думать над архитектурой приложения.
В какой-то момент работы в платформенной команды онлайн-кинотеатра ivi произошел перекос в техническом направлении, во многом когда мы разрабатывали основу для серверного рендеринга React приложения ivi и писали первые страницы на новом стеке.
При поиске нового места работы я уже знал, что ищу техническую вакансию, что-то самое сложное и интересное из всех возможных вариантов.
Один из инсайтов при поиске - технические (или "core") команды это частое решение в больших компаниях, где надо масштабировать общие решения на сотни разработчиков, и редкость в компаниях где количество разработчиков под конкретную платформу не превышает один или два десятка человек.
Но такие компании есть, я все-таки попал в core команду, и мне до сих пор это очень нравится:
- практически каждый день приходится решать не тривиальные задачи
- мы поддерживаем фреймворк, которым пользуется больше двухсот фронтенд разработчиков - это крутое коммьюнити, Inner Source, совсем другие требования к обратной совместимости, тестированию и документированию кода
- на фреймворке работают более трех десятков приложений на сайте tinkoff - это налагает большие требования к поддержке и производительности, но при этом дает большое количество данных, метрик, возможность быстро получать результаты от экспериментов (но и цена ошибок высока, к сожалению)
- очень зрелые процессы в компании - активно пишем и обсуждаем ADR и RFC, полноценный Inner Source, проводим такие общие встречи как Web Performance и специфичные для React / Angular направлений
- возможность расти (в том числе прозрачная система грейдов) в сторону Tech, а не только Team - про это очень хорошо написано в докладе моего коллеги Саши Поломодова - https://apolomodov.medium.com/how-to-grow-if-you-are-a-senior-software-engineer-6ddd8edbebae
Оказывается, не так много разработчиков хотят пробовать свои силы в core командах, и мы все еще ищем сильного и заинтересованного коллегу для работы в первую очередь над двумя проектами:
- Фреймворк
- Консольная утилита
Оба проекта подразумевают поддержку большого количества пользователей - других разработчиков в Тинькофф, написание автоматических тестов и документации, и конечно же решения не тривиальных задач.
Достаточно подробно про наши процессы собеседований - https://github.com/Tinkoff/career/blob/main/interview/README.md
Если вас заинтересовала вакансия, пишите мне @SuperOleg39 или всегда можете оставить заявку на сайте tinkoff.ru/career/it
TLDR: Ищу коллегу в техническую команду!
Уже почти три года я работаю в Тинькофф в технической команде над SSR мета-фреймворком https://tramvai.dev
Мне всегда было интересно работать над классным продуктом, делать сложный UI, улучшать производительность и думать над архитектурой приложения.
В какой-то момент работы в платформенной команды онлайн-кинотеатра ivi произошел перекос в техническом направлении, во многом когда мы разрабатывали основу для серверного рендеринга React приложения ivi и писали первые страницы на новом стеке.
При поиске нового места работы я уже знал, что ищу техническую вакансию, что-то самое сложное и интересное из всех возможных вариантов.
Один из инсайтов при поиске - технические (или "core") команды это частое решение в больших компаниях, где надо масштабировать общие решения на сотни разработчиков, и редкость в компаниях где количество разработчиков под конкретную платформу не превышает один или два десятка человек.
Но такие компании есть, я все-таки попал в core команду, и мне до сих пор это очень нравится:
- практически каждый день приходится решать не тривиальные задачи
- мы поддерживаем фреймворк, которым пользуется больше двухсот фронтенд разработчиков - это крутое коммьюнити, Inner Source, совсем другие требования к обратной совместимости, тестированию и документированию кода
- на фреймворке работают более трех десятков приложений на сайте tinkoff - это налагает большие требования к поддержке и производительности, но при этом дает большое количество данных, метрик, возможность быстро получать результаты от экспериментов (но и цена ошибок высока, к сожалению)
- очень зрелые процессы в компании - активно пишем и обсуждаем ADR и RFC, полноценный Inner Source, проводим такие общие встречи как Web Performance и специфичные для React / Angular направлений
- возможность расти (в том числе прозрачная система грейдов) в сторону Tech, а не только Team - про это очень хорошо написано в докладе моего коллеги Саши Поломодова - https://apolomodov.medium.com/how-to-grow-if-you-are-a-senior-software-engineer-6ddd8edbebae
Оказывается, не так много разработчиков хотят пробовать свои силы в core командах, и мы все еще ищем сильного и заинтересованного коллегу для работы в первую очередь над двумя проектами:
- Фреймворк
tramvai - работа над которым прокачает экспертизу в SSR, React, сборке frontend приложений, web производительности, мониторинге и производительности Node.js приложений, CI/CD процессах, организации сборки, публикации и версионирования сотни npm библиотек- Консольная утилита
unic - инструмент для простого деплоя приложений в k8s, использующийся десятками команд за рамками нашей фронтенд экосистемы. Это экспертиза в Kubernetes, Docker, инфраструктуре, тот широкий набор навыков, которого зачастую не хватает даже очень опытным frontend разработчикамОба проекта подразумевают поддержку большого количества пользователей - других разработчиков в Тинькофф, написание автоматических тестов и документации, и конечно же решения не тривиальных задач.
Достаточно подробно про наши процессы собеседований - https://github.com/Tinkoff/career/blob/main/interview/README.md
Если вас заинтересовала вакансия, пишите мне @SuperOleg39 или всегда можете оставить заявку на сайте tinkoff.ru/career/it
Medium
Как и куда развиваться, если ты уже Senior Software Engineer
С таким докладом я выступил на dotNext 2022 Spring 27 июня. Кажется, что вопросам обучения уделяют много времени, но основной фокус на тех…
👍8❤2😢2
Привет!
После релиза Next.js 13 было очень много интересных обсуждений, и одна из немногих фич, про которую я не слышал никакой критики - обновленный компонент next/image - https://nextjs.org/docs/api-reference/next/image.
Работать с изображениями в 2022 году действительно сложно, но и возможности у нас очень широкие.
По поводу сложности - когда мне понадобилось ознакомиться со всеми современными возможностями, в первую очередь это
Но список возможностей впечатляет:
- Мы можем отдать пользователю изображение в оптимальном формате с помощью
- Мы можем отдать оптимальное изображения для экранов разного размера и даже разной плотности пикселей с помощью
- Самая сложная для меня часть, мы даже можем подсказать браузеру, на экране какого размера, какую часть этого экрана будет занимать изображение, с помощью
- Наконец-то, с очень хорошей браузерной поддержкой, мы можем легко объявить пропорции изображения передав атрибуты
Важный рецепт для хорошей метрики Cumulative Layout Shift.
- Адаптивная верстка стала проще, во многом благодаря
- Ленивая загрузка без сторонних либ с помощью атрибута
Также есть ряд техник, как сделать placeholder изображение, например blur заглушка в base64, сгенерированная из исходной картинки.
Но не могу это отнести к развитию возможностей веба, кажется такие техники существуют уже давно, и зависят от инструментария сборки.
После релиза Next.js 13 было очень много интересных обсуждений, и одна из немногих фич, про которую я не слышал никакой критики - обновленный компонент next/image - https://nextjs.org/docs/api-reference/next/image.
Работать с изображениями в 2022 году действительно сложно, но и возможности у нас очень широкие.
По поводу сложности - когда мне понадобилось ознакомиться со всеми современными возможностями, в первую очередь это
srcset и sizes, беглым поиском нашел около пяти гайдов, все подробные и с хорошей визуализацией (один из подробнейших - https://css-tricks.com/a-guide-to-the-responsive-images-syntax-in-html/), но даже изучив все я откровенно не разобрался во всех нюансах.Но список возможностей впечатляет:
- Мы можем отдать пользователю изображение в оптимальном формате с помощью
picture - форматы .webp и особенно .avif позволяют очень сильно уменьшить размер изображения- Мы можем отдать оптимальное изображения для экранов разного размера и даже разной плотности пикселей с помощью
srcset- Самая сложная для меня часть, мы даже можем подсказать браузеру, на экране какого размера, какую часть этого экрана будет занимать изображение, с помощью
sizes! Условно, на мобилках, весь экран, а на десктопе половину - это напрямую повлияет на выбор оптимального изображения из srcset- Наконец-то, с очень хорошей браузерной поддержкой, мы можем легко объявить пропорции изображения передав атрибуты
width и height или CSS свойство aspect-ratio. Т.е. больше никаких хаков с padding (https://css-tricks.com/aspect-ratio-boxes/), дополнительными невидимыми блоками, пустым noscript изображением нужного размера (смотри исходники legacy next image компонента :) ).Важный рецепт для хорошей метрики Cumulative Layout Shift.
- Адаптивная верстка стала проще, во многом благодаря
aspect-ratio, object-position и object-fit- Ленивая загрузка без сторонних либ с помощью атрибута
loading="lazy"Также есть ряд техник, как сделать placeholder изображение, например blur заглушка в base64, сгенерированная из исходной картинки.
Но не могу это отнести к развитию возможностей веба, кажется такие техники существуют уже давно, и зависят от инструментария сборки.
nextjs.org
Components: Image Component
Optimize Images in your Next.js Application using the built-in `next/image` Component.
👍7
Не увидительно, что фреймворки стараются упростить эти вещи для пользователей, и различные Image компоненты можно увидеть у Next.js, Nuxt.js, Remix, SvelteKit, Gatsby, и даже в каком-то ограниченном виде в Angular (ограниченном, т.к. в рантайме у SSR фреймворков больше возможностей, чем у Angular на этапе сборки)
Такой компонент мы решили добавить и для нашего фреймворка
Документация к next/image во многом является спецификацией компонента, покрывает множество кейсов, и стала для меня отличным референсом.
Важная часть работы с изображениями - это оптимизация и конвертация в оптимальный формат.
В самом простом случае, можно сделать это вручную, с помощью таких сервисов как
Более продвинутый способ - оптимизация на этапе сборки, например с помощью
Но что делать, когда изображений много, или они приходят с внешних ресурсов, или мы еще не знаем, какие вариации размеров и качества этих картинок нам понадобятся?
А скорее всего, все эти кейсы будут актуальны одновременно.
Решить эти проблемы проще всего в рантайме, и тут я знаю два варианта:
- Использовать отдельный сервис для оптимизации изображений - например в Тинькофф мы используем замечательный https://imgproxy.net/
- Использовать библиотку для оптимизации на сервере самого приложения - например https://github.com/GoogleChromeLabs/squoosh
Работа с изображениями - это тяжелый вычислительный процесс, и кажется не очень разумным делать такое в рантайме SSR сервера, которому и React в HTML отрендерить не так просто.
Но на самом деле, проблема нагрузки актуальна и для отдельного сервиса, и для нее есть отличное решение - CDN.
Обработанные изображения можно агрессивно кэшировать, и в разы снизить нагрузку на приложение.
Важный момент по поводу кэширования - CDN должен учитывать кодировку изображения, если по одной и той же ссылке к примеру
Для этого надо передавать заголовок
Next.js обрабатывает изображений в рантайме, т.е. вся нагрузка приходится на приложение.
В первую очередь это удобно для разработчиков - не нужно поднимать и поддерживать отдельный сервис вроде
Второй момент, при деплое Next.js приложений через Vercel, ваши приложения будут находится за CDN, что позволяет кэшировать и страницы, и статику, и изображения, которые отдает приложение.
Для self-hosted деплоя придется проксировать запросы за картинками через CDN на приложение самостоятельно.
Скорее всего, Next.js также сохраняет изображения и страницы в файловой системе, и CDN тут только ускорит доставку, так далеко в исследовании я не заходил.
Хочется рассказать, что интересного было в разработке аналогичного решения.
Такой компонент мы решили добавить и для нашего фреймворка
tramvaiДокументация к next/image во многом является спецификацией компонента, покрывает множество кейсов, и стала для меня отличным референсом.
Важная часть работы с изображениями - это оптимизация и конвертация в оптимальный формат.
В самом простом случае, можно сделать это вручную, с помощью таких сервисов как
Squoosh.Более продвинутый способ - оптимизация на этапе сборки, например с помощью
image-webpack-loader.Но что делать, когда изображений много, или они приходят с внешних ресурсов, или мы еще не знаем, какие вариации размеров и качества этих картинок нам понадобятся?
А скорее всего, все эти кейсы будут актуальны одновременно.
Решить эти проблемы проще всего в рантайме, и тут я знаю два варианта:
- Использовать отдельный сервис для оптимизации изображений - например в Тинькофф мы используем замечательный https://imgproxy.net/
- Использовать библиотку для оптимизации на сервере самого приложения - например https://github.com/GoogleChromeLabs/squoosh
Работа с изображениями - это тяжелый вычислительный процесс, и кажется не очень разумным делать такое в рантайме SSR сервера, которому и React в HTML отрендерить не так просто.
Но на самом деле, проблема нагрузки актуальна и для отдельного сервиса, и для нее есть отличное решение - CDN.
Обработанные изображения можно агрессивно кэшировать, и в разы снизить нагрузку на приложение.
Важный момент по поводу кэширования - CDN должен учитывать кодировку изображения, если по одной и той же ссылке к примеру
imgproxy отдаст .avif для Chrome или .webp для Safari.Для этого надо передавать заголовок
Vary: Accept в ответе с изображением, и настроить CDN на поддержку этого заголовка, в этом случае кэш будет сохраняться отдельно для каждого формата, и CDN будет отдавать подходящий формат с учетом заголовка Accept запроса из браузера.Next.js обрабатывает изображений в рантайме, т.е. вся нагрузка приходится на приложение.
В первую очередь это удобно для разработчиков - не нужно поднимать и поддерживать отдельный сервис вроде
imgproxy, все работает из коробки.Второй момент, при деплое Next.js приложений через Vercel, ваши приложения будут находится за CDN, что позволяет кэшировать и страницы, и статику, и изображения, которые отдает приложение.
Для self-hosted деплоя придется проксировать запросы за картинками через CDN на приложение самостоятельно.
Скорее всего, Next.js также сохраняет изображения и страницы в файловой системе, и CDN тут только ускорит доставку, так далеко в исследовании я не заходил.
Хочется рассказать, что интересного было в разработке аналогичного решения.
imgproxy.net
imgproxy: fast and secure on-the-fly image processing
imgproxy is a fast and secure standalone server for resizing and converting remote images. The guiding principles behind imgproxy are security, speed, and simplicity
👍4🔥2
Учитывая уже развернутый сервис
Еще не решенная проблема этого выбора - невозможность использовать сервис при разработке, для изображений на localhost.
Оказалось, что для достижения сравнимого качества изображения, для AVIF надо указывать значительно более низкий уровень качества при оптимизации, хорошее сравнение форматов можно найти тут - https://www.industrialempathy.com/posts/avif-webp-quality-settings/
В итоге, после точечной настройки, мы подключили AVIF, и в среднем, с примерно одинаковым качеством, это снизило размер итоговых изображений на 30-50% по сравнению с webP.
Следующий кейс - автоматическое сохранение пропорций, вычисление aspect-ratio.
На примере некста, логику получения размера картинки можно встроить в сборку - https://github.com/vercel/next.js/blob/canary/packages/next/build/webpack/loaders/next-image-loader.js.
В
По аналогии с next/image, добавил в компонент несколько вариантов верстки (и без необходимости поддерживать старые браузеры и наличии aspect-ratio, верстка невероятно простая):
- адаптивная, но не больше размера изображения (intrinsic)
- резиновая под размер контейнера (responsive)
- фиксированный размер (fixed)
- заполняющая доступное пространство (fill)
Если для intrinsic и fixed изображений достаточно учесть только плотность пикселей на экране, и передать в
По этой причине, отдельной задачей было исследовать аналитику посещения наший приложений на tinkoff.ru для создания минимального но в то же время полного списка экранов, которые мы будем учитывать в
Пример размеров из этого списка:
- 720px - популярная ширина мобилок 360px плюс 2x плотность пикселей
- 1366px - популярная ширина на десктопе
- 3840px - еще одна популярная ширина на десктопе, 1920px плюс 2x плотность пикселей
Таким образом, пользователи большинства устройств получат изображение минимально необходимого размера и оптимального качества.
Еще из интересного, простые и удобные абстракции:
- Свойство
- Свойство
Это был очень интересный опыт, и интересно как дальше будет развиваться новый Image компонент.
Например, в будущем я планирую отвязать его от
Также, надо будет выбрать хороший способ создания blur плейсхолдера для изображений.
imgproxy, я решил делать Image компонент поверх этого сервиса - это закрыло вопросы лишней нагрузки на SSR приложения, оптимизации под разные форматы, отдачи нужного формата в рантайме без тега picture (что позволяет сделать очень простую разметку).Еще не решенная проблема этого выбора - невозможность использовать сервис при разработке, для изображений на localhost.
.avif формат у нас был отключен из-за высокой нагрузки при генерации изображений в этом формате, было интересно разобраться в проблеме, даже помогли немножко улучшить сервис - https://github.com/imgproxy/imgproxy/issues/938.Оказалось, что для достижения сравнимого качества изображения, для AVIF надо указывать значительно более низкий уровень качества при оптимизации, хорошее сравнение форматов можно найти тут - https://www.industrialempathy.com/posts/avif-webp-quality-settings/
В итоге, после точечной настройки, мы подключили AVIF, и в среднем, с примерно одинаковым качеством, это снизило размер итоговых изображений на 30-50% по сравнению с webP.
Следующий кейс - автоматическое сохранение пропорций, вычисление aspect-ratio.
На примере некста, логику получения размера картинки можно встроить в сборку - https://github.com/vercel/next.js/blob/canary/packages/next/build/webpack/loaders/next-image-loader.js.
В
tramvai я добавил простой лоадер, который переиспользует логику file-loader (компромисс, т.к. по хорошему надо использовать Asset Modules), но при именованном импорте отдает объект с данными об изображении, которые необходимы для сохранения пропорций:jsx
import { image } from './image.png';
const { src, width, height } = image;
<img src={src} width={width} height={height} />
По аналогии с next/image, добавил в компонент несколько вариантов верстки (и без необходимости поддерживать старые браузеры и наличии aspect-ratio, верстка невероятно простая):
- адаптивная, но не больше размера изображения (intrinsic)
- резиновая под размер контейнера (responsive)
- фиксированный размер (fixed)
- заполняющая доступное пространство (fill)
Если для intrinsic и fixed изображений достаточно учесть только плотность пикселей на экране, и передать в
srcset 1x и 2x изображения, то для оптимальной responsive и fill верстки srcset должен содержать изображения под совершенно разные размеры экранов.По этой причине, отдельной задачей было исследовать аналитику посещения наший приложений на tinkoff.ru для создания минимального но в то же время полного списка экранов, которые мы будем учитывать в
srcset при генерации изображений.Пример размеров из этого списка:
- 720px - популярная ширина мобилок 360px плюс 2x плотность пикселей
- 1366px - популярная ширина на десктопе
- 3840px - еще одна популярная ширина на десктопе, 1920px плюс 2x плотность пикселей
Таким образом, пользователи большинства устройств получат изображение минимально необходимого размера и оптимального качества.
Еще из интересного, простые и удобные абстракции:
- Свойство
quality - позволяет при необходимости сконфигурировать оптимизацию через imgproxy более высокого качества, или наоборот более низкого- Свойство
priority - по умолчанию делаем все изображения c loading="lazy", но можем повысить приоритет и убрать этот атрибут, а с самым высоким приоритетом автоматически добавляем preload link на страницу для этой картинки (тривиальная задача для SSR фреймворка), что идеально подходит для Largest Contentful Paint изображенияЭто был очень интересный опыт, и интересно как дальше будет развиваться новый Image компонент.
Например, в будущем я планирую отвязать его от
imgproxy, и перенести в список публичных пакетов, которые публикуются в open-source.Также, надо будет выбрать хороший способ создания blur плейсхолдера для изображений.
GitHub
Error: assignment to entry in nil map · Issue #938 · imgproxy/imgproxy
Hello! imgproxy@3.7.0, latest Docker image. Try to use format_quality:jpeg:70:avif:45:webp:70 preset on images, and imgproxy crashed with stacktrace: panic: assignment to entry in nil map goroutine...
👍7
Отдельно открыл для себя паттерн разработки, которым раньше пользовался неосознанно:
- Начинаю с ADR на новую фичу
- Далее создаю минимальное example приложение со всеми возможными кейсами
- Реализация фичи, вся разработка наглядно сразу в example приложении
- Практически из коробки получаем интеграционные тесты (в самом пр
- И также есть готовые примеры кода, на основе которых легко сделать документацию
Спасибо за внимание!
Делитесь вашим опытом и мыслями в комме
- Начинаю с ADR на новую фичу
- Далее создаю минимальное example приложение со всеми возможными кейсами
- Реализация фичи, вся разработка наглядно сразу в example приложении
- Практически из коробки получаем интеграционные тесты (в самом пр
остом случае, снапшоты на HTML страничек example)- И также есть готовые примеры кода, на основе которых легко сделать документацию
Спасибо за внимание!
Делитесь вашим опытом и мыслями в комме
нтариях.👍10❤1
SuperOleg dev notes pinned «Привет! TLDR: Ищу коллегу в техническую команду! Уже почти три года я работаю в Тинькофф в технической команде над SSR мета-фреймворком https://tramvai.dev Мне всегда было интересно работать над классным продуктом, делать сложный UI, улучшать производительность…»
Привет!
Столкнулись с интересным кейсом, для меня это просто утечка памяти года -
Этот простой код в Node.js до 18 версии вызывает небольшую утечку памяти, примерно 128 байт на вызов, которые не очищаются с помощью Garbage Collector.
Проблема описана в этом issue, и как будто бы исправлена в актуальных мажорках ноды, но фактически утечка присутствует в 14.x и 16.x версиях, по результатам наших проверок.
Почему утечка года:
- она маленькая и медленная, нужны хорошие нагрузки что бы была заметна
- совершенно неожиданная при профилировании (просто посмотрите на этот "undefined" на скриншоте, я просто игнорировал его когда встречал)
- очень легко воспроизвести, у нас оказалось несколько мест где парсим те cookies, которые часто
Очень рад работать с такими крутыми коллегами, один из которых смог это раскопать)
Основной причиной, почему начали смотреть утечки памяти, оказался другой код, связанный с LRU кэшами, но в любом случае это хороший повод обновить Node.js
Столкнулись с интересным кейсом, для меня это просто утечка памяти года -
JSON.parse(undefined)Этот простой код в Node.js до 18 версии вызывает небольшую утечку памяти, примерно 128 байт на вызов, которые не очищаются с помощью Garbage Collector.
Проблема описана в этом issue, и как будто бы исправлена в актуальных мажорках ноды, но фактически утечка присутствует в 14.x и 16.x версиях, по результатам наших проверок.
Почему утечка года:
- она маленькая и медленная, нужны хорошие нагрузки что бы была заметна
- совершенно неожиданная при профилировании (просто посмотрите на этот "undefined" на скриншоте, я просто игнорировал его когда встречал)
- очень легко воспроизвести, у нас оказалось несколько мест где парсим те cookies, которые часто
undefinedОчень рад работать с такими крутыми коллегами, один из которых смог это раскопать)
Основной причиной, почему начали смотреть утечки памяти, оказался другой код, связанный с LRU кэшами, но в любом случае это хороший повод обновить Node.js
🔥25
Привет!
Давно хотел поделиться накопленным за последний год опытом оптимизаций и масштабирования SSR приложений.
Думал уложиться в несколько telegram постов, но меня немножечко прорвало, и получилась почти полноценная статья.
В статье расскажу про основные проблемы SSR (спойлер - React и Node.js) и рассмотрю ряд возможных оптимизаций:
- Static Site Generation
- Rendering at the Edge
- Микросервисы
- Оптимизациия кода
- Кэширование компонентов
- Кэширование запросов
- Rate Limiting
- Fallback кэш страниц
- Client-side rendering fallback
- Кластеризация и воркеры
Ссылка на статью в моем Notion - https://superoleg39.notion.site/SSR-04ad1ab46de346edb244a1112bd357a3
Очень жду ваш фидбек в комментарии)
Давно хотел поделиться накопленным за последний год опытом оптимизаций и масштабирования SSR приложений.
Думал уложиться в несколько telegram постов, но меня немножечко прорвало, и получилась почти полноценная статья.
В статье расскажу про основные проблемы SSR (спойлер - React и Node.js) и рассмотрю ряд возможных оптимизаций:
- Static Site Generation
- Rendering at the Edge
- Микросервисы
- Оптимизациия кода
- Кэширование компонентов
- Кэширование запросов
- Rate Limiting
- Fallback кэш страниц
- Client-side rendering fallback
- Кластеризация и воркеры
Ссылка на статью в моем Notion - https://superoleg39.notion.site/SSR-04ad1ab46de346edb244a1112bd357a3
Очень жду ваш фидбек в комментарии)
superoleg39 on Notion
Масштабирование SSR приложений | Notion
Привет!
🔥35👍5👏1
Привет!
Про обработку ошибок рендеринга при SSR.
Сразу начну с примеров обработки таких ошибок.
Во многих мета-фреймворках (Next.js, Remix, SvelteKit, Nuxt.js) есть простой способ отобразить UI при ошибках в компонентах. Там где поддерживается вложенный роутинг, этот error компонент можно добавлять на уровне конкретных страниц, и таким образом рендерить error UI локально (поиграться с примером на Next.js можно тут - https://app-dir.vercel.app/error-handling/books). Как правило, дополнительно всегда есть общий для всех дефолтный компонент - страница ошибки, который будет использован в том числе если ошибка произошла вне React (например в любом месте в серверном обработчике запроса).
Какие именно ошибки вообще можно и нужно обрабатывать? Если мы говорим про роуты, вот два основных кейса:
- expected ошибки, связанные с загрузкой данных для роута - когда надо программно выбросить 404 или 500 ошибку и отобразить подходящий UI, это область ответственности meta-фреймворка, ошибку легко поймать
- unexpected ошибки, по сути любой
Кажется, что кейсы unexpected ошибок должен решать React, но тут очень много подводных камней.
У нас же есть Error Boundary и все ок? - к сожалению нет, он поймает только ошибки в рантайме браузера, если например
Остаются ошибки при серверном рендеринге и ошибки гидрации.
Так, мы копнули глубже (https://github.com/reactjs/rfcs/blob/main/text/0215-server-errors-in-react-18.md, и узнали что с 18 версии реакта Suspense тоже обрабатывает ошибки, как раз на сервере и при гидрации.
Если коротко, Suspense при ошибке вложенного компонента на сервере отрисует fallback компонент, сериализует ошибку для передачи на клиент, затем на клиенте, если этот же компонент снова выкинет исключение, Suspense не будет его обрабатывать, и оно попадет в ближайший Error Boundary.
Тут тоже есть нюансы:
- Если используем
- С
- Не все пользователи перешли на React 18, мета-фреймворки еще не могут завязаться на этот механизм
- В идеале хочется рендерить один и тот же fallback и в Suspense, и в Error Boundary - но как минимум у Suspense фаллбэка нет доступа к ошибке, уже не получится отрисовать аналогичный UI
По итогу, Suspense в связке с Error Boundary вполне подойдет для повышения надежности и производительности (Selective Hydration) каких-нибудь островков на страницах, таких как микрофронтенды (хотя и тут много чего надо учитывать вне реакта, например падение запроса за JS кодом микрофронта на сервере - надо отрендерить фаллбэк, перезапросить на клиенте, будет много кастомного кода), но реализовать полноценный Page Error Boundary только с этими механизмами не получится.
Так как же устроен этот механизм у Next.js, или у Remix?
Я первый раз исследовал эту тему когда добавлял Page Error Boundary в
Про обработку ошибок рендеринга при SSR.
Сразу начну с примеров обработки таких ошибок.
Во многих мета-фреймворках (Next.js, Remix, SvelteKit, Nuxt.js) есть простой способ отобразить UI при ошибках в компонентах. Там где поддерживается вложенный роутинг, этот error компонент можно добавлять на уровне конкретных страниц, и таким образом рендерить error UI локально (поиграться с примером на Next.js можно тут - https://app-dir.vercel.app/error-handling/books). Как правило, дополнительно всегда есть общий для всех дефолтный компонент - страница ошибки, который будет использован в том числе если ошибка произошла вне React (например в любом месте в серверном обработчике запроса).
Какие именно ошибки вообще можно и нужно обрабатывать? Если мы говорим про роуты, вот два основных кейса:
- expected ошибки, связанные с загрузкой данных для роута - когда надо программно выбросить 404 или 500 ошибку и отобразить подходящий UI, это область ответственности meta-фреймворка, ошибку легко поймать
- unexpected ошибки, по сути любой
throw внутри React компонента страницы, и такую ошибку достаточно сложно обработатьКажется, что кейсы unexpected ошибок должен решать React, но тут очень много подводных камней.
У нас же есть Error Boundary и все ок? - к сожалению нет, он поймает только ошибки в рантайме браузера, если например
throw произошел при обработке клика от пользователя.Остаются ошибки при серверном рендеринге и ошибки гидрации.
Так, мы копнули глубже (https://github.com/reactjs/rfcs/blob/main/text/0215-server-errors-in-react-18.md, и узнали что с 18 версии реакта Suspense тоже обрабатывает ошибки, как раз на сервере и при гидрации.
Если коротко, Suspense при ошибке вложенного компонента на сервере отрисует fallback компонент, сериализует ошибку для передачи на клиент, затем на клиенте, если этот же компонент снова выкинет исключение, Suspense не будет его обрабатывать, и оно попадет в ближайший Error Boundary.
Тут тоже есть нюансы:
- Если используем
renderToString на сервере - никак не сможем залогировать ошибку, изменить код ответа, все произойдет “молча”. Только на клиенте, если использовать hydrateRoot, можно залогировать серверную ошибку в onRecoverableError- С
renderToPipeableStream все уже лучше, есть обработчики ошибок, у нас почти полный контроль, но мы все-равно не знаем в каком именно компоненте произошла ошибка (парсить стек-трейс этой ошибки неблагодарное дело), и в fallback UI не имеем прямого доступа к объекту этой ошибки- Не все пользователи перешли на React 18, мета-фреймворки еще не могут завязаться на этот механизм
- В идеале хочется рендерить один и тот же fallback и в Suspense, и в Error Boundary - но как минимум у Suspense фаллбэка нет доступа к ошибке, уже не получится отрисовать аналогичный UI
По итогу, Suspense в связке с Error Boundary вполне подойдет для повышения надежности и производительности (Selective Hydration) каких-нибудь островков на страницах, таких как микрофронтенды (хотя и тут много чего надо учитывать вне реакта, например падение запроса за JS кодом микрофронта на сервере - надо отрендерить фаллбэк, перезапросить на клиенте, будет много кастомного кода), но реализовать полноценный Page Error Boundary только с этими механизмами не получится.
Так как же устроен этот механизм у Next.js, или у Remix?
Я первый раз исследовал эту тему когда добавлял Page Error Boundary в
tramvai в прошлом году - https://tramvai.dev/docs/features/error-boundaries#specific-fallback, и смотрел как раз в исходники Remix, которые порадовали своей простотой и комментариями, в отличие например от некста.app-dir.vercel.app
Next.js App Directory Playground
A playground to explore new Next.js 13 app directory features such as nested layouts, instant loading states, streaming, and component level data fetching.
👍10🔥1
Алгоритм работы механизма отображения error UI в Remix не сложный, изящный, с важным минусом который кажется никак не решить снаружи реакта:
- В основе лежит Error Boundary, но скорее его можно назвать Universal Error Boundary - важное отличие в том, что компонент может принимать объект ошибки
- Что бы избежать ошибок гидрации из-за рассинхрона разметки, эту
- Самое главное, и тут как раз скрывается не приятный но простой хак, нам нужно получить эту ошибку. Единственный универсальный способ сделать это - try/catch для
При повторном рендере, мы где-то уже храним ошибку, и можем передать ее в пропсы Error Boundary, и не рендерить компонент второй раз.
Важный момент! Мы только предполагаем, что ошибка была где-то в компоненте страницы, но этот механизм никак не гарантирует, что второй рендер тоже не упадет - и тут как раз надо иметь некий Root Error Boundary, который мы бы отдали в ответ на любую неперхваченную ошибку.
Почему только сейчас вспомнил про эту тему - вчера мне стало интересно, что там нового у Next.js 13 и Remix по обработке ошибок, оба перешли на API для стримингового рендеринга реакта, и копаясь в исходниках Remix увидел что много всего изменилось, в том числе по Error Boundaries.
Сначала решил, что нашли какой-то способ, возможно с Suspense, не делать повторный рендер - но нет, докопался до мест, где рендер делается второй и где-то даже и третий раз)
Из интересного, оба фреймворка поддерживают вложенный роутинг, и вот для того что бы под ошибку найти самый ближайший Error Boundary, они кажется трекают какой именно роут рендерился на данный момент. Но может быть и просто берется самый вложенный текущий роут, дальше копать не стал.
Ждем, что предложит нам React в будущем, или какие-то новые крутые паттерны от сообщества, как сделать обработку ошибок проще и производительнее.
Очень интересен ваш фидбек, как обрабатываете ошибки, или может быть кто-то глубоко понимает механизмы того-же некста, и сможет раскрыть тему?
Спасибо за внимание!
- В основе лежит Error Boundary, но скорее его можно назвать Universal Error Boundary - важное отличие в том, что компонент может принимать объект ошибки
error снаружи, как пропс. Пример в репе трамвая - https://github.com/Tinkoff/tramvai/blob/main/packages/tramvai/react/src/error/UniversalErrorBoundary.tsx- Что бы избежать ошибок гидрации из-за рассинхрона разметки, эту
error надо сериализовать и передавать на клиент. Пример простых хэлперов что бы перегнать объект ошибки в простой объект и обратно - https://github.com/Tinkoff/tramvai/blob/main/packages/modules/render/src/shared/pageErrorStore.tsx#L11- Самое главное, и тут как раз скрывается не приятный но простой хак, нам нужно получить эту ошибку. Единственный универсальный способ сделать это - try/catch для
renderToString или хэндлеры ошибок у renderToPipeableStream. Но получение этой ошибки означает, что рендер упал - и его придется делать повторно.При повторном рендере, мы где-то уже храним ошибку, и можем передать ее в пропсы Error Boundary, и не рендерить компонент второй раз.
Важный момент! Мы только предполагаем, что ошибка была где-то в компоненте страницы, но этот механизм никак не гарантирует, что второй рендер тоже не упадет - и тут как раз надо иметь некий Root Error Boundary, который мы бы отдали в ответ на любую неперхваченную ошибку.
Почему только сейчас вспомнил про эту тему - вчера мне стало интересно, что там нового у Next.js 13 и Remix по обработке ошибок, оба перешли на API для стримингового рендеринга реакта, и копаясь в исходниках Remix увидел что много всего изменилось, в том числе по Error Boundaries.
Сначала решил, что нашли какой-то способ, возможно с Suspense, не делать повторный рендер - но нет, докопался до мест, где рендер делается второй и где-то даже и третий раз)
Из интересного, оба фреймворка поддерживают вложенный роутинг, и вот для того что бы под ошибку найти самый ближайший Error Boundary, они кажется трекают какой именно роут рендерился на данный момент. Но может быть и просто берется самый вложенный текущий роут, дальше копать не стал.
Ждем, что предложит нам React в будущем, или какие-то новые крутые паттерны от сообщества, как сделать обработку ошибок проще и производительнее.
Очень интересен ваш фидбек, как обрабатываете ошибки, или может быть кто-то глубоко понимает механизмы того-же некста, и сможет раскрыть тему?
Спасибо за внимание!
GitHub
tramvai/packages/tramvai/react/src/error/UniversalErrorBoundary.tsx at main · Tinkoff/tramvai
A modular framework for universal JS applications. Contribute to Tinkoff/tramvai development by creating an account on GitHub.
🔥11👍8
Привет!
Интересный кейс, про подходы мета-фреймворков для загрузки данных, и немного мыслей по теме.
В Next.js улучшают поддержку SEO, если надо будет формировать мета данные динамически - для этого будет отдельная функция - загрузчик.
При этом, в нексте уже есть возможность загрузки данных в самих компонентах, благодаря Server Components (серверные компоненты могут быть async, клиентские могут использовать экспериментальный хук use) и подходу render-as-you-fetch.
Это обсудили в твиттере, что делать если нужны данные из одного и того же запроса в компоненте и загрузчике SEO данных.
Решение - экспериментальное cache апи реакта - https://twitter.com/leeerob/status/1619812802453188608?t=W_QqKVZeKSFPTeORs8XBlg&s=19
Remix кстати эту проблему решает по другому, прокидывая данные из загрузчика для страницы в meta функцию.
Этот cache под капотом дедуплицирует вызов функции по аргументам.
И для render-as-you-fetch с server components это наверное нормальное решение что бы избежать дубликатов и водопадов запросов, хотя и решаемое на уровне http клиентов (например tinkoff-request https://tinkoff.github.io/tinkoff-request/docs/plugins/cache-deduplicate.html)
Но в целом для мета-фреймворков, которые пытаются дать хороший DX и побольше сахара для пользователей, это показательная проблема архитектуры, когда изначальное отсутствие гибкости заставляет какие-то лишние движения с cache (некст) или запросом данных для меты в загрузчике данных для самой страницы (ремикс), что как минимум связывает потенциально независимый код и заставляет параллелить запросы вручную.
Есть ощущение что функционал большинства фреймворков подходит только для очень простых кейсов использования (что наверное и плюс одновременно, если этот механизм легко понять новому пользователю).
Гораздо лучше все эти проблемы решают стейт-менеджеры, с готовыми механизмами для сайд-эффектов, основной минус что их всё-таки надо интегрировать поверх мета-фреймворка, и можно опять же упереться в его гибкость.
Также есть взгляд немного с другой стороны, в tramvai основной механизм загрузки данных - экшены, которых можно сколько угодно добавить для страницы, и они будут выполнены параллельно.
Прямой связи между данными из экшена и компонентом страницы нет, и основным транспортным каналом как раз является встроенный в трамвай стейт-менеджер.
Конечно минусы и тут есть, например это лишний бойлерплейт для простых кейсов, в ряде случаев можно решить использованием react-query вместо стейт-менеджера.
Также встречал сложные кейсы, когда экшены вложены друг в друга многократно (как правило связано со сложностью логики этого кейса)
Если говорить про кейс с SEO, благодаря DI, в трамвайных экшенах можно менять мету вручную, используя созданный для этого сервис (привет хорошим практикам из Angular) - то есть гибче просто уже некуда.
По итогу, не могу сделать какие-то полезные выводы - все делают по разному, и все эти механизмы сделаны не просто так, имеют и плюсы и минусы.
Очень интересно как вы решаете сложные кейсы бизнес-логики в приложениях написанных на мета-фреймворках.
Интересный кейс, про подходы мета-фреймворков для загрузки данных, и немного мыслей по теме.
В Next.js улучшают поддержку SEO, если надо будет формировать мета данные динамически - для этого будет отдельная функция - загрузчик.
При этом, в нексте уже есть возможность загрузки данных в самих компонентах, благодаря Server Components (серверные компоненты могут быть async, клиентские могут использовать экспериментальный хук use) и подходу render-as-you-fetch.
Это обсудили в твиттере, что делать если нужны данные из одного и того же запроса в компоненте и загрузчике SEO данных.
Решение - экспериментальное cache апи реакта - https://twitter.com/leeerob/status/1619812802453188608?t=W_QqKVZeKSFPTeORs8XBlg&s=19
Remix кстати эту проблему решает по другому, прокидывая данные из загрузчика для страницы в meta функцию.
Этот cache под капотом дедуплицирует вызов функции по аргументам.
И для render-as-you-fetch с server components это наверное нормальное решение что бы избежать дубликатов и водопадов запросов, хотя и решаемое на уровне http клиентов (например tinkoff-request https://tinkoff.github.io/tinkoff-request/docs/plugins/cache-deduplicate.html)
Но в целом для мета-фреймворков, которые пытаются дать хороший DX и побольше сахара для пользователей, это показательная проблема архитектуры, когда изначальное отсутствие гибкости заставляет какие-то лишние движения с cache (некст) или запросом данных для меты в загрузчике данных для самой страницы (ремикс), что как минимум связывает потенциально независимый код и заставляет параллелить запросы вручную.
Есть ощущение что функционал большинства фреймворков подходит только для очень простых кейсов использования (что наверное и плюс одновременно, если этот механизм легко понять новому пользователю).
Гораздо лучше все эти проблемы решают стейт-менеджеры, с готовыми механизмами для сайд-эффектов, основной минус что их всё-таки надо интегрировать поверх мета-фреймворка, и можно опять же упереться в его гибкость.
Также есть взгляд немного с другой стороны, в tramvai основной механизм загрузки данных - экшены, которых можно сколько угодно добавить для страницы, и они будут выполнены параллельно.
Прямой связи между данными из экшена и компонентом страницы нет, и основным транспортным каналом как раз является встроенный в трамвай стейт-менеджер.
Конечно минусы и тут есть, например это лишний бойлерплейт для простых кейсов, в ряде случаев можно решить использованием react-query вместо стейт-менеджера.
Также встречал сложные кейсы, когда экшены вложены друг в друга многократно (как правило связано со сложностью логики этого кейса)
Если говорить про кейс с SEO, благодаря DI, в трамвайных экшенах можно менять мету вручную, используя созданный для этого сервис (привет хорошим практикам из Angular) - то есть гибче просто уже некуда.
По итогу, не могу сделать какие-то полезные выводы - все делают по разному, и все эти механизмы сделаны не просто так, имеют и плюсы и минусы.
Очень интересно как вы решаете сложные кейсы бизнес-логики в приложениях написанных на мета-фреймворках.
tinkoff.github.io
Cache Plugin - Deduplicate · @tinkoff/request
Deduplicate requests with equal cache keys before making a request. If plugin is executed it will check all currently running requests, all requests with equal cache key will transform into single request, and resolve or reject accordingly to that single…
👍11
Привет!
Достаточно давно смотрел на бенчмарк фреймворков от builderIO, и был очень удивлен насколько быстро по ним работает marko.js - https://github.com/BuilderIO/framework-benchmarks#ssr-times
Наконец-то добрался попрофилировать что там происходит во время рендеринга на сервере.
Сравнивал marko.js, и remix, затем добавил tramvai в бенчмарк, смотрел все на дефолтном примере с /dashboard, потом на эту же страничку добавил очень много компонентов что бы посмотреть и на тяжелом рендере что происходит.
tramvai и remix - оба делают минимум действий вокруг реакта, можно сказать что это чистый
Что интересного по итогу:
Достаточно давно смотрел на бенчмарк фреймворков от builderIO, и был очень удивлен насколько быстро по ним работает marko.js - https://github.com/BuilderIO/framework-benchmarks#ssr-times
Наконец-то добрался попрофилировать что там происходит во время рендеринга на сервере.
Сравнивал marko.js, и remix, затем добавил tramvai в бенчмарк, смотрел все на дефолтном примере с /dashboard, потом на эту же страничку добавил очень много компонентов что бы посмотреть и на тяжелом рендере что происходит.
tramvai и remix - оба делают минимум действий вокруг реакта, можно сказать что это чистый
renderToString плюс работа серверного фреймворка (конечно фреймворк-специфичные детали есть, но все не так плохо как у next.js, там свой прикол с post-процессингом HTML перед отправкой клиенту, чем она больше тем хуже - https://github.com/vercel/next.js/issues/35797)Что интересного по итогу:
GitHub
GitHub - BuilderIO/framework-benchmarks: Test each framework for it's performance cost
Test each framework for it's performance cost. Contribute to BuilderIO/framework-benchmarks development by creating an account on GitHub.
У marko вообще ряд оптимизаций на уровне компилятора, плюс в документации описано отличие от реакта https://markojs.com/docs/why-is-marko-fast/#high-performance-server-side-rendering:
- react создаем vDOM, затем проходит по нему еще раз и рендерит HTML в строку
- marko рендерит HTML за один проход
И действительно во всех моих тестах marko примерно в 2 и более раза быстрее рендерит HTML, чем react.
При это по RPS marko выигрывает не меньше чем в 3 раза у tramvai и remix - и это вторая часть оптимизации - он делает минимум лишней работы на сервере.
Как я понял из ответа мейнера фреймворка - https://twitter.com/dylan_piercey/status/1561505479347449858?t=dOKFEf405DOPwOrFlqT6Pw&s=19 - они на этапе сборки компилируют нативный node.js сервер (то есть не используют фреймворк) и также в каком-то виде инлайнят роутинг.
И результаты очень крутые!
- react создаем vDOM, затем проходит по нему еще раз и рендерит HTML в строку
- marko рендерит HTML за один проход
И действительно во всех моих тестах marko примерно в 2 и более раза быстрее рендерит HTML, чем react.
При это по RPS marko выигрывает не меньше чем в 3 раза у tramvai и remix - и это вторая часть оптимизации - он делает минимум лишней работы на сервере.
Как я понял из ответа мейнера фреймворка - https://twitter.com/dylan_piercey/status/1561505479347449858?t=dOKFEf405DOPwOrFlqT6Pw&s=19 - они на этапе сборки компилируют нативный node.js сервер (то есть не используют фреймворк) и также в каком-то виде инлайнят роутинг.
И результаты очень крутые!
Markojs
Why is Marko Fast? | Marko
Marko is a friendly (and fast!) UI library that makes building web apps fun.
👍3