TGViewer
Channel Public Channel
SuperOleg dev notes

SuperOleg dev notes

@super_oleg_dev

Обзоры новостей и статей из мира frontend, интересные кейсы, исследования и мысли вслух

https://github.com/SuperOleg39

https://twitter.com/ODrapeza

@SuperOleg39
Subscribers
1.75K
Photos
57
Videos
3
Links
160

Showing posts older than #102 · Back to latest

Older Posts 19 shown
Post #101 825
Баг дня - в Safari не работает проставление cookies без значения если есть дополнительные параметры, пример - test; SameSite=None; Secure

И дело тут на удивление не в SameSite - как я понимаю Safari более строго следует спеке, и записать надо либо с value, или как минимум добавить знак равенства:


test=enabled; SameSite=None; Secure`
test=; SameSite=None; Secure`

При том, что document.cookie = 'test' сработает корректно.

Не нашел похожих issue или тредов по проблеме, присылайте ссылочки есть уже сталкивались с такой проблемой
  • 👍 6
Post #100 1.01K
Обновление сервис-воркера. HMR кстати иногда работает))
Post #99 939
Пример классных логов от workbox и workbox-window при разработке, регистрация сервис-воркера
Post #98 905
Также, локально нам нужен доступ на относительный урл /sw.js или какой-там урл мы сконфигурируем с желаемым скоупом. В tramvai приложении это прощее всего сделать добавив конфиг для http-proxy-middleware, который будет смотреть на dev-сервер.

Спустя десяток-два новых тудушек в репе, базовая интеграция готова, интересно какие кейсы всплывут на приложениях коллег.

Хочется выявить и добавить какие-то общие рецепты/паттерны, может даже в виде кода для импорта в сервис-воркере, с фичами реализованными на workbox - например при генерации статических страниц (SSG) - кэшировать просмотренные для работы оффлайн. Сгенерированные в рантайме кэшировать все-таки не безопасно.
Также у нас есть механизм для работы в режиме CSR с одной страницей-заглушкой - вот ее точно можно кэшировать.

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

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

Но билдтайм, это параметры @tramvai/cli, у нас не расширяемый, пользователь не может поменять вебпак конфиг или вклиниться на другие этапы сборки. И по сути разрабатывая независимый функционал, часть которая касается сборки мне приходится зашивать в код cli, усложнять его и в целом риск поломки есть всегда.

Хороший повод поскорее реализовать систему плагинов-модулей для cli - у нас уже многое для этого готово, добавляли в cli наш dependency injection контейнер для абстракции над сущностью Builder - подготовка к возможности в будущем поддерживать разные бандлеры.

Второй инсайт, это то что у нас разделен рантайм и билдтайм.

К примеру у Nuxt.js плагины имеют доступ одновременно к параметрам сборки, и могут менять что-то в рантайме.

В моем случае, из-за того что функционал сильно связан между собой, приходится делать ряд манипуляций - передавать какие-то параметры из cli в клиентский код PWA модуля, думать куда ложить конфигурацию под разные фичи - в статичный конфиг файл tramvai.json или в рантайм через DI провайдеры (к этом у cli нет доступа).

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

А что для вас было бы идеальной интеграцией возможностей PWA в приложении?
  • 👍 4
Post #97 652
Дальше нужно генерировать manifest.json и иконки. Я решил интегрировать это в процесс сборки, причин несколько, не уверен что перевешивают сложность реализации:

- Доступ к манифесту и иконкам через наш dev server, не надо писать их на диск во время разработки
- Хочется отслеживать изменения исходного файла с иконками
- Возможно понадобится иметь их в манифесте сборки

Кстати до сих пор не знаю, нужно ли предварительное кэширование для вебманифеста и иконок)

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

У sharp простое API, но надо продумать такие кейсы как ресайз картинок только если исходник реально изменился + персистентный кэш между сборками для ускорения процесса.

Отлично, у нас есть сервис-воркер, вебманифест и иконки, пора интегрировать в приложение.

Сделал общий новый PWA модуль с декомпозицей по вложенным модулям 1 в 1 как у Nuxt.js плагина.

Для сервис-воркера - на клиенте регистрируем его через workbox-window (вынес либу в динамический чанк, уменьшает надежность но не хочется тянуть лишние килобайты кода на клиент).

При кэшировании бандлов приложения, они не кэшируются намертво, что заблокировало бы разработку - InjectManifest создает манифест сборки с актуальными ревизиями файлов в development режиме, это меняет содержимое воркера при пересборке, браузер после загрузки видит что SW изменился и грузит новый (принудительный update воркера позволит сделать это быстрее) - итого + одна перезагрузка страницы, но мы получаем в итоге актуальный код в браузере, кажется нормальный компромисс для разработки PWA.
Также сделал опцию на отключение фичи в dev режиме.

Для вебманифеста - просто добавляем его в head приложения, как и мета теги.

Интеграция иконок заканчивается на добавлении их в вебманифест.
  • 👍 2
Post #96 601
Один из главных челленджей в задачке - поддержка фич PWA в development режиме.
Сгенерировать SW, вебманифест и иконки на этапе сборки приложения или сразу после, значительно проще.

Один из плюсов workbox - готовые webpack плагины - для генерации SW с нуля или для внедрения манифеста сборки в существующий исходник сервис-воркера. Манифест необходим если мы хотим предварительное кэширование основных ассетов приложения.

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

С этим workbox плагином есть проблема - он не поддерживает работу в watch режиме, что оказалось легко переопределить, но осталась нерешенная проблема с Hot Module Replacement - после пары апдейтов на клиенте HMR зацикливается и я так и не добрался до причины)

Итак, добавляю поддержку исходного файла с сервис-воркером написанном на TS, на основе которого плагин InjectManifest будет генерировать sw.js с добавленным манифестом сборки.

Тут всплыл отдельный кейс с нашей modern сборкой (те же чанки с кодом в более современном формате, имеют другие хэши), простым решением оказалось генерировать sw.js и sw.modern.js, в каждом из которых свои манифесты сборки с подходящими чанками.
Chrome for Developers Precaching with Workbox | Chrome for Developers Learn how to precache assets in a service worker with Workbox.
  • 👍 5
Post #95 655
Привет!

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

У нас уже был внутренний модуль, который просто подключал со стороны Service Worker и webmanifest, а сам SW и манифест разрабатывались и загружались на s3 отдельно от приложения.

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

По фичам PWA:

- Нам нужно генерировать сервис-воркер
- Также генерировать webmanifest
- Дополнительно генерировать иконки
- Интегрировать все это в приложение

По технологиям:

- workbox помогает легко реализовать все популярные кейсы с SW (кэширование, оффлайн) + подробные логи
- workbox-window умеет правильно подключать SW и упростить общение с ним из приложения + подробные логи
- sharp как популярное решение для работы с изображениями

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

Есть ряд PWA плагинов под популярные мета-фреймворки, в качестве референса использую плагин для Nuxt.js - https://pwa.nuxtjs.org/

Почему именно ее:

- Nuxt.js один из немногих мета-фреймворков с адекватной расширяемой архитектурой, полноценно поддерживает плагины, в которых можно конфигурировать build-тайм и рантайм фичи
- Отличная декомпозиция - выделены модули по фичам: Workbox, Manifest, Icon и Meta - готовая ментальная модель для разработки похожего функционала
- Подробная документация, описаны конфигурации для каждого модуля, можно переиспользовать

Кстати этот плагин как раз поддерживает работу без workbox.
Chrome for Developers Workbox | Chrome for Developers Build progressive web apps (PWAs) with Workbox - the Service Worker library from the Chrome team
  • 👍 10
Post #94 866
Ожидаемый результат в песочнице
  • 👍 5
Post #93 930
Привет!

Закончил очередной этап переработки документации tramvai.dev

Постарался значительно улучшить доку по нашему решению для микрофронтов - Child Apps

Теперь основные возможности Child Apps вынесены на передний план, отдельно описана интеграция с хостовым приложением, отдельно как это все работает вместе.

Пользуясь случаем, порекламирую решение, и коротко расскажу почему оно крутое:
- Это SSR микрофронты, полноценная композиция на стороне сервера в виде обычных React компонентов в дереве приложения
- Это фреймворк, у микрофронта есть свой жизненный цикл, механизмы для загрузки данных, работы с роутингом, добавлению ресурсов на страницу, механизм Dependency Injection интегрированный с DI хоста
- Это CLI, решены все вопросы сборки микрофронта
- DX при разработки Child App очень похож на DX при разработке обычного tramvai приложения - буквально во многих местах используются одни и те же модули и провайдеры
- Интеграция React Query, та же самая что и для tramvai приложения
- Интеграция Module Federation (ох непростой кейс для SSR), можно шарить все основные tramvai библиотеки, тот же React Query, базовый вес микрофронта получается очень небольшой

То есть, Child App это не про оркестрацию микрофронтов написанных на пяти разных фреймворках и запущенных на одной странице.

Это возможность создать мини-tramvai приложение, независимый блок с UI на React и своей логикой, с отдельным релизным циклом и возможностью переиспользовать его во множестве приложений.

Добавил Child App в шаблон на основе tramvai приложения на Codesandbox, что бы было легко попробовать - https://codesandbox.io/p/sandbox/romantic-sun-1r6rsg?file=%2Ftramvai.json

Для сборки микрофронта нужно запустить отдельный терминал и команду yarn child:start, а tramvai приложение должно запуститься самостоятельно на 3000 порту
tramvai.dev Overview | tramvai Micro frontends heavily integrated with tramvai framework
  • 👍 10
  • 🔥 9
  • ❤ 1
Post #92 1.04K
И еще немного про Angular и вообще - коллеги выпустили универсальную либу для маскирования полей ввода, плюс биндинги для Angular (React в ближайших планах) - Maskito

Было очень интересно почитать насколько много нюансов и проблем с нативными событиями полей ввода при реализации этой либы - https://habr.com/ru/companies/tinkoff/articles/727368/

Не знал про событие beforeinput, выглядит очень хорошо, особенно нравится event.inputType

Будет очень круто, если наконец-то появилась та самая маска, которая решит большую часть кейсов, с которыми наверное многое из нас успели пострадать)
GitHub GitHub - taiga-family/maskito: Collection of libraries to create an input mask which ensures that user types value according to… Collection of libraries to create an input mask which ensures that user types value according to predefined format. - taiga-family/maskito
  • 👍 10
Post #91 743
Привет!

У Angular ну очень классное API для HTTP Interceptors:

```
(request, next) => next(request)
```

Где next(request) возвращает response.

И для сравнения API у axios - https://axios-http.com/docs/interceptors

Это закрывает просто все необходимые кейсы:
- изменить параметры запроса
- вернуть кастомный ответ или ошибку
- модифицировать ответ или ошибку

Забираю в tramvai и депрекейчу накиданные мной на коленке страшные modifyResponse, modifyRequest и modifyError коллбэки для встроенного HTTP клиента)

Правда Angular создает инстанс Observable при вызове next, в tramvai Rx не используется, и будут обычные промисы:


[
// меняем параметры запроса
(req, next) => next({ ...req, timeout: 1000 }),

// модифицируем ответ
(req, next) => next(req)
.then((res) => ({ ...res, payload: 'Intercepted' }),

// модифицируем ошибку
(req, next) => next(req)
.catch((reason) => {
Object.assign(reason, { code: 'INTERCEPTED_ERROR' });
throw reason;
},
]

Мелкая фича, но всегда очень приятно встретить и позаимствовать лаконичное и функциональное API
tramvai.dev HTTP Client | tramvai Explanation
  • 🔥 12
  • 👍 3
Post #90 1.04K
Следующее, это касается и загрузки страницы, и Early Hints - это нюансы работы сети. Даже с HTTP/2 и мультиплексированием запросов, канал пользователя ограничен, и чем больше ресурсов мы запрашиваем, тем сильнее они конкурируют между собой.

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

Самое важное для отрисовки - это CSS (блокирующий ресурс) и шрифты. Есть много гайдов по оптимизации, тут лишь коротко отмечу несколько полезных вещей:
- инлайн небольших стилей в HTML разметку
- теоретически critical CSS (ни разу не встречал серьезных кейсов использования)
- не использовать веб шрифты или хотя бы выбрать оптимальный font-display
- для предотвращения CLS использовать новые крутые свойства size-adjust и прочие для того что бы fallback шрифт был максимально похож размером на нужный вам - https://screenspan.net/fallback

Следующее - это LCP изображение (если оно изображение, а не текст).
Стоит предусмотреть механизм, который будет автоматически добавлять preload тег с нужным изображением на страницу.
При использовании srcset - обратите внимание на нюансы из этого гайда - https://www.stefanjudis.com/today-i-learned/how-to-preload-responsive-images-with-imagesizes-and-imagesrcset/, самый важный - нельзя добавлять src атрибут для прелоад тега, иначе будет двойная загрузка, кажется в Safari.

Когда наши CSS файлы загружены, хотя бы один из них - можно начинать грузить JS. Такое можно провернуть например через onLoad обработчик для link тега со стилями, tramvai добавляет preload теги для скриптов таким образом.
Но даже если мы хотим пораньше получить интерактивное приложение, при использовании SSR пользователь уже получить какой-то полезный контент, и не стоит блокировать рендеринг приложения нашим джаваскриптом - и всегда использовать атрибут defer.
Да, что-то может сильно задержать событие DOM interactive и соответственно выполнение скриптов, но атрибут async мне не нравится, так как скрипт будет выполнен по завершении не блокирующей загрузки, а выполнение JS это блокирующая операция, и могут быть конфликты к примеру с загрузкой стилей, или доступом к еще не обработанным DOM элементам.

Различную аналитику надо стараться загружать и выполнять настолько поздно, насколько это возможно.
Также очень круто выглядит Partytown, умеющий выполнять скрипты в воркере с полным доступом к DOM.

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

Расскажите про оптимизации флоу загрузки ресурсов на ваших приложениях, какие-то интересные подходы и лайфхаки, детали о которых я забыл рассказать?
screenspan.net Fallback Font Generator Reduce Cumulative Layout Shift (CLS) by adjusting web fonts and system font fallbacks using @font-face
  • 👍 11
  • 🔥 3
  • ❤ 1
Post #89 762
Привет!

Хочется подвести какой-то полезный итог всей истории по preconnect и по загрузке ресурсов в целом.

Лучшее что мы можем сделать - это вообще не использовать CDN.
А точнее, сделать так что бы CDN был на домене нашего приложения - например такую инфраструктуру предлагает Vercel.
Отказываться от преимуществ CDN не хочется и не нужно)
Тогда все основные ресурсы приложения можно раздавать с того же доменного имени, при использовании HTTP/2 будет создан только один TCP сокет при запросе самого HTML документа, и никаких дополнительных накладных расходов на установку соединения в тот момент, когда браузер перейдет к скачиванию CSS и прочих ресурсов.

Это большие изменения инфраструктуры, и компромиссом будет иметь максимум один домен для CDN, но тут мы можем столкнуться с проблемой разных значений атрибута crossorign - и все-равно получим лишнее TCP соединение, установка которого может отложить загрузку наших критичных скриптов или например LCP изображения.

crossorign="anonymous" - действительно важный атрибут для скриптов со сторонних доменов, если мы хотим качественный мониторинг ошибок.
И раз мы вынуждены использовать его для скриптов и шрифтов, кажется что лучше всего так же по умолчанию добавлять его и для стилей и для изображений.
Также, не забывать и про preload теги, если атрибут crossorign не совпадает, то предзагруженный контент не будет переиспользован.

Но допустим мы не контролируем в приложении отрисовку изображений или добавление части ресурсов.
Для решения этой проблемы может помочь <link rel="preconnect" href="https://example.com" /> для CDN, которому также будет важен crossorign - это зависит от вашего водопада критичных запросов за ресурсами, какой preconnect стоит использовать.

Еще больше ускорить загрузку помогут Early Hints - если вы рендерите приложение на сервере, и делаете запросы в сторонние API, блокирующие рендер, отдать все необходимые preconnect и preload теги в самом начале обработки запроса - в теории бесценно.
На практике, есть нюансы, и для нас пока главный инфраструктурный - 103 код ответа не уходит из нашего k8s кластера (research in progress).
Хотя по документации Envoy балансер (используется в k8s) поддерживает 103 код, хоть и с ограничением - отдает только первый, но по спецификации можно отдать сколько угодно 103 ответов до 200 финального.
В другой инфраструктуре балансер вообще не смог распарсить и проксировать Early Hints и не отдавал страничку, хорошо что мы с нее переезжаем и не надо копать в эту сторону.
MDN Web Docs 103 Early Hints - HTTP | MDN The HTTP 103 Early Hints informational response may be sent by a server while it is still preparing a response, with hints about the sites and resources that the server expects the final response will link to. This allows a browser to preconnect to sites…
  • 👍 3
  • 🔥 1
Post #88 677
Второе - это особенность HTTP/2 по отношению к поддоменам.

Я не уверен что это общая особенность, скорее наша специфика.

Допустим, у нас на странице есть несколько поддоменов:
- www.cdn-tinkoff.ru
- imgproxy.cdn-tinkoff.ru
- unic-cdn-prod.cdn-tinkoff.ru

Они резолвятся в один и тот же IP нашего CDN провайдера.

С них загружается JS и шрифты с crossorigin="anonymous" и CSS с изображениями без CORS, это дефолтное поведение любого запроса за ресурсами.

И для запросов с CORS (это наши JS и шрифты) по HTTP/2 браузер переиспользует один и тот же сокет, и тут не большая проблема во множестве поддоменов.

А вот для наших CSS и изображений это уже не работает, запросы без CORS и переиспользуется только резолв DNS, но не установка TCP+TLS соединения.

И в итоге можно наблюдать вот такую картину, и поначалу сложно разобраться по какой логике переиспользуются соединения
Post #86 640
И сразу пример работы preload тега, в Chrome Devtools никак не увидеть, что TCP + TLS происходит раньше
Post #85 641
Привет!

Продолжая тему с preconnect, еще несколько интересных вещей.

Во-первых, не нашел ни одного способа проверить, работает ли добавленный на страницу <link rel="preload" />, кроме сервиса Webpagetest.

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

Сделал демку с ассетами с разных доменов, и добавил на каждый домен preload обычный и c crossorigin (preload будет работать только если совпадает CORS правила для конкретного запроса за ресурсом)

Кстати, почему у нас для каждого скрипта проставлен crossorigin="anonymous" - без этого стектрейс ошибки скрипта с другого домена очень ограничен.
  • 👍 1
Post #84 799
И визуализация проблемы - оба файла расположены на одном CDN
  • 👍 6
  • 👌 2
Post #83 874
Привет!

Интересный кейс с HTTP/2 и preconnect к ресурсам с CDN.

В preconnect входит резолв DNS и установка TLS соединения, на один домен используется как правило один TCP сокет, и этот преконнект по сути переиспользуется для множества запросов за ресурсами.

Какое-то время назад заметил что на tinkoff.ru преконнекты к ресурсам с одного CDN происходят дважды - один сокет для тегов link и изображений, один для шрифтов и JS файлов.

Наконец-то раскопал проблему, для шрифтов и стилей у нас по дефолту проставляется атрибут crossorigin="anonymous", и для них другие правила установки TLS соединения, поэтому браузер выделяет отдельный сокет.

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

Почитать про это подробнее можно по ссылкам:
- https://groups.google.com/a/chromium.org/g/net-dev/c/1tBj5tEoL4g
- https://github.com/w3c/resource-hints/issues/32
- https://crenshaw.dev/preconnect-resource-hint-crossorigin-attribute/

Похоже что в нашем случае имеет смысл делать crossorigin="anonymous" дефолтом и для стилей и для изображений, а вообще в идеале надо раздавать статику с основного домена - тогда отсутствуют расходы на preconnect кроме первого (за HTML) и нет проблем с CORS - кстати это реализовано на avito, ребята большие молодцы.
GitHub CORS Anonymous requests complicates preconnect · Issue #32 · w3c/resource-hints I'm running into a few issues in the Chrome implementation that are causing preconnect to not be as effective as we'd like, specifically when it comes to preconnecting for CORS anonymous re...
  • 🔥 14
Post #82 953
В том же анонсе увидел что у SvelteKit есть универсальные и server-only методы загрузки данных - https://kit.svelte.dev/docs/load#universal-vs-server

Это интересный кейс, что файл с load методом в .server.js файле автоматически становится API роутом, на которое приложение само сходит при SPA-переходе.

Пример запроса для posts страницы - posts/__data.json

И вот для возможности стримить эти JSON данные используется формат NdJSON, в демке видно что даже Content-Type свой - text/sveltekit-data

Понял что звучит очень похоже на то как передаются данные для React Server Components.

Интересная фича, так как более привычные способы загрузки данных чисто на сервере - это делать отдельно API роуты, но может быть я путаю и кто-то из мета-фреймворков уже имеет такие интеграции?
svelte.dev Loading data • SvelteKit Docs Loading data • SvelteKit documentation
  • 👍 2
  • 🔥 2
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →