Одна из ключевых особенностей
Встроенный механизм внедрения зависимостей позволяет собирать приложений из модулей как конструктор из кубиков, и при хорошем проектировании модулей, открывает потрясающие возможности для расширения или замены возможностей в нашем приложении.
Dependency Injection все активнее используется в мире фронтенда, но как мне кажется, ассоциируется только с Angular или Nest.js.
При этом увидеть использование DI контейнеров в React приложениях можно гораздо реже.
Учитывая активное развитие SSR приложений, меня даже немного удивляет, что этот паттерн не получил широкое распространение, далее постараюсь объяснить почему.
Несколько лет назад я занимался разработкой базовой инфраструктуры для миграции PHP приложения (с классической связкой - шаблонизатор на сервере и React на клиенте) на универсальное React приложения (SSR на NodeJS), в онлайн кинотеатре ivi.ru.
Начиная запускать код существующего клиентского JS приложения на сервере, сталкиваешься с рядом проблем, основная из которых - "универсальность" кода, т.е. насколько код сильно завязан на окружение, в котором будет выполняться.
Т.е. все обращения к
Как раз в это время я читал замечательную книгу "Шаблоны проектирования Node.JS", в которой познакомился с Dependency Injection и его реализациями в виде DI container или Service Locator. К слову, раньше мне очень трудно давалось понимание этого паттерна)
В какой-то момент в голове сложились все паззлы, и пришло понимание что DI дает нам очень удобную абстракцию для универсального кода, и лучше всего рассмотреть это на примере работы с
Для начала, пример итогового использования сервиса:
Далее, напишем реализации интерфейса CookieService:
И остается только зарегистрировать нужные реализации в подходящем окружении:
Таким образом мы можем практически полностью избавиться от постоянных проверок в коде на текущее окружение.
Также, мы можем изолировать DI на разные запросы в приложение, и использовать уникальные Request и Response, создавая отдельный DI контейнер на каждый запрос.
Другая фича, которую дает DI, и которая реализована в Angular, Nest.js и tramvai - это возможность расширения приложения отдельными модулями, дополняющими приложение конкретным функционалом с помощью набора токенов и провайдеров (интерфейсы и реализации зависимостей).
Если посмотреть на другие фронтенд фреймворки, можно увидеть разные механизмы для расширения кода - системы плагинов, огромные файлы конфигураци, или паттерн middleware.
tramvai- Dependency Injection.
Встроенный механизм внедрения зависимостей позволяет собирать приложений из модулей как конструктор из кубиков, и при хорошем проектировании модулей, открывает потрясающие возможности для расширения или замены возможностей в нашем приложении.
Dependency Injection все активнее используется в мире фронтенда, но как мне кажется, ассоциируется только с Angular или Nest.js.
При этом увидеть использование DI контейнеров в React приложениях можно гораздо реже.
Учитывая активное развитие SSR приложений, меня даже немного удивляет, что этот паттерн не получил широкое распространение, далее постараюсь объяснить почему.
Несколько лет назад я занимался разработкой базовой инфраструктуры для миграции PHP приложения (с классической связкой - шаблонизатор на сервере и React на клиенте) на универсальное React приложения (SSR на NodeJS), в онлайн кинотеатре ivi.ru.
Начиная запускать код существующего клиентского JS приложения на сервере, сталкиваешься с рядом проблем, основная из которых - "универсальность" кода, т.е. насколько код сильно завязан на окружение, в котором будет выполняться.
Т.е. все обращения к
location, работа с
localStorage, чтение
cookies, не должны происходить на сервере, либо должны содержать проверки на окружение, и например читать куки не из
document.cookies, а из
req.cookies, если мы используем
expressна сервере.
Как раз в это время я читал замечательную книгу "Шаблоны проектирования Node.JS", в которой познакомился с Dependency Injection и его реализациями в виде DI container или Service Locator. К слову, раньше мне очень трудно давалось понимание этого паттерна)
В какой-то момент в голове сложились все паззлы, и пришло понимание что DI дает нам очень удобную абстракцию для универсального кода, и лучше всего рассмотреть это на примере работы с
cookies.
Для начала, пример итогового использования сервиса:
// создадим общий контейнер зависимостей
const di = createContainer();
// опишем интерфейс сервиса для работы с куками
interface CookieService {
get(key): string;
set(key, value, options): void;
}
// React компонент, который завязан только на интерфейс, но не на реализацию
const Component = ({ cookieKey }) => {
// не важно, на сервере или на клиенте будет отрендерен компонент,
// главное что бы в di была подходящая реализация сервиса
const cookieService = useDI<CookieService>('cookie service');
const cookieValue = cookieService.get(cookieKey);
return ...;
}
Далее, напишем реализации интерфейса CookieService:
// серверная реализация, работает с объектом Request
class ServerCookieService implements CookieService {
constructor(request: Request) {}
get(key) { ... }
set(key, value, options) { ... }
}
// серверная реализация, работает с document.cookie
class ClientCookieService implements CookieService {
constructor() {}
get(key) { ... }
set(key, value, options) { ... }
}
И остается только зарегистрировать нужные реализации в подходящем окружении:
// server.js
// получаем зависимость
const request = di.get('request');
// регистрируем зависимость на сервере
di.provide('cookie service', new ServerCookieService(request));
// client.js
// регистрируем зависимость на клиенте
di.provide('cookie service', new ClientCookieService());
Таким образом мы можем практически полностью избавиться от постоянных проверок в коде на текущее окружение.
Также, мы можем изолировать DI на разные запросы в приложение, и использовать уникальные Request и Response, создавая отдельный DI контейнер на каждый запрос.
Другая фича, которую дает DI, и которая реализована в Angular, Nest.js и tramvai - это возможность расширения приложения отдельными модулями, дополняющими приложение конкретным функционалом с помощью набора токенов и провайдеров (интерфейсы и реализации зависимостей).
Если посмотреть на другие фронтенд фреймворки, можно увидеть разные механизмы для расширения кода - системы плагинов, огромные файлы конфигураци, или паттерн middleware.
На мой взгляд, модульный подход дает максимальную гибкость, и позволяет сделать минимальное зацепление кода у различных модулей.
Подробнее про реализацию DI в tramvai можно почитать в документации - https://tramvai.dev/docs/concepts/overview
И ссылочки на различные DI контейнеры:
- https://github.com/inversify/InversifyJS
- https://github.com/mgechev/injection-js
- https://github.com/microsoft/tsyringe
- https://github.com/typestack/typedi
Многие из этих библиотек используют механизм рефлексии, позволяющий делать магию с DI - https://www.npmjs.com/package/reflect-metadata
Рефлексия имеет ряд плюсов и недостатков, и на данный момент не используется в tramvai.
Подробнее про реализацию DI в tramvai можно почитать в документации - https://tramvai.dev/docs/concepts/overview
И ссылочки на различные DI контейнеры:
- https://github.com/inversify/InversifyJS
- https://github.com/mgechev/injection-js
- https://github.com/microsoft/tsyringe
- https://github.com/typestack/typedi
Многие из этих библиотек используют механизм рефлексии, позволяющий делать магию с DI - https://www.npmjs.com/package/reflect-metadata
Рефлексия имеет ряд плюсов и недостатков, и на данный момент не используется в tramvai.
tramvai.dev
Introduction to tramvai | tramvai
tramvai is a lightweight web framework for building SSR applications with a modular system and DI to quickly extend the functionality of applications.
Привет!
Хочу рассказать еще про одну особенность tramvai, связанную с жизненным циклом приложения, и запросами в это приложение.
Для начала рассмотрим несколько популярных вариантов, как запускаются и конфигурируются стандартные приложения, написанные на Express или React.
Если это
Если это
И это подходит для многих приложений, но при этом несет ряд проблем, при росте кодовой базы:
- Добавление новой независимой фичи - со своими страницами, сторами, провайдерами - потребует вносить изменения в несколько разных мест
- Не связанный код зацеплен друг с другом
- Если мы говорим про middleware, не связанный код может еще и ломать друг друга, подробнее в классном докладе - https://www.youtube.com/watch?v=RS8x73z4csI
- Сайд-эффекты могут быть размазаны по всему приложению, и для серверного рендеринга, нужно постараться, что бы выполнить все асинхронные действия до
А если в нашем SSR приложении мы хотим рендерить страницу как можно быстрее?
Для этого нужно запускать асинхронные действия максимально параллельно, и иметь возможность исключить действия, выходящие за определенный таймаут.
Затем, было бы неплохо выполнить эти сайд-эффекты, которые не уложились в тайминги, повторно на клиенте.
Звучит как не тривиальная задача, и некоторые из фреймворков пытаются решить ее тем или иным способом, например
Если обобщить, помимо производительности, страдает расширяемость и модульность приложения.
Хочу рассказать еще про одну особенность tramvai, связанную с жизненным циклом приложения, и запросами в это приложение.
Для начала рассмотрим несколько популярных вариантов, как запускаются и конфигурируются стандартные приложения, написанные на Express или React.
Если это
express сервер, обычно создается одна точка входа, где создается приложение, к нему добавляются роуты и middleware, и запускается сервер через app.listenЕсли это
React приложение, мы так же в большинстве случаев увидим одну точку входа, где происходит инициализация стейт-менеджера, композиция провайдеров, и создание App компонента. Если это SSR, то в серверной реализации App дополнительно будет логика по загрузке всех данных для приложения, а в клиентской реализации - гидрация этих данных.И это подходит для многих приложений, но при этом несет ряд проблем, при росте кодовой базы:
- Добавление новой независимой фичи - со своими страницами, сторами, провайдерами - потребует вносить изменения в несколько разных мест
- Не связанный код зацеплен друг с другом
- Если мы говорим про middleware, не связанный код может еще и ломать друг друга, подробнее в классном докладе - https://www.youtube.com/watch?v=RS8x73z4csI
- Сайд-эффекты могут быть размазаны по всему приложению, и для серверного рендеринга, нужно постараться, что бы выполнить все асинхронные действия до
renderToString нашего приложенияА если в нашем SSR приложении мы хотим рендерить страницу как можно быстрее?
Для этого нужно запускать асинхронные действия максимально параллельно, и иметь возможность исключить действия, выходящие за определенный таймаут.
Затем, было бы неплохо выполнить эти сайд-эффекты, которые не уложились в тайминги, повторно на клиенте.
Звучит как не тривиальная задача, и некоторые из фреймворков пытаются решить ее тем или иным способом, например
Appolo просто рендерит наше приложение на сервере несколько раз, пока не выполнит все query - и это очень не эффективно, т.к. renderToString это самая медленная часть нашего SSR, к тому же синхронно блокирующий event loop.Если обобщить, помимо производительности, страдает расширяемость и модульность приложения.
YouTube
Node.js Middleware – никогда больше! [ru] / Тимур Шемсединов
Видео с онлайн-конференции JavaScript fwdays'20 autumn, которая прошла 22 сентября 2020 года.
Описание доклада:
Почему приложение работает нестабильно, происходит утечка памяти и процесс часто вылетает? Почему вам сложно найти ошибку и нужно долго делать…
Описание доклада:
Почему приложение работает нестабильно, происходит утечка памяти и процесс часто вылетает? Почему вам сложно найти ошибку и нужно долго делать…
Что интересного предлагает
С помощью Dependency Injection и механизма Command Line Chain, фреймворк дает возможность вклиниться на ряд этапов жизненного цикла приложения:
- Различные этапы инициализации веб-сервера (т.к. мы используем
- Различные этапы запуска
- Этапы для загрузки данных, необходимых для рендеринга страницы (один из них как раз создан для сайд-эффектов)
- Этап генерации страницы (на нем сервер делает
- Этапы для загрузки данных при SPA-переходах
Механизм Command Line Chain во-первых, предоставляет наглядный флоу жизни приложения и запросов.
Во-вторых, выполняет все действия параллельно, на каждом этапе.
Во-вторых, вместе с механизмом Actions, позволяет установить таймауты на выполнение действий, отдать ответ на клиент не дожидаясь этих действий, и выполнить их повторно на клиенте.
Пример добавления нового действия на этап получения информации о пользователе, и документация - https://coretech-frontend.pages.devplatform.tcsbank.ru/tramvai/docs/concepts/command-line-runner#resolve_user_deps
При необходимости, с помощью провайдеров можно предоставлять хуки и guard'ы для переходов роутера, например для проверки доступов пользователя к конкретной странице - https://tramvai.dev/docs/references/libs/router#router-guards
Также, есть провайдеры, позволяющие добавить скрипты, стили или meta-теги в HTML разметку итоговой страницы.
Пример такого провайдера, и документация - https://coretech-frontend.pages.devplatform.tcsbank.ru/tramvai/docs/references/modules/render#как-добавить-загрузку-ассетов-на-странице
Такая гибкость дает нам возможность создавать отдельные модули, которые по сути являются списком провайдеров, вместе реализующих определенную фичу, и подключать эти модули простым добавлением в список modules при создании нашего приложения.
tramvai, для решения этих кейсов?С помощью Dependency Injection и механизма Command Line Chain, фреймворк дает возможность вклиниться на ряд этапов жизненного цикла приложения:
- Различные этапы инициализации веб-сервера (т.к. мы используем
express под капотом, можно добавить express middleware или роуты)- Различные этапы запуска
listen сервера (можно добавить обработчики uncaughtException и unhandledRejection , запустить на другом порту мокер, или static сервер)- Этапы для загрузки данных, необходимых для рендеринга страницы (один из них как раз создан для сайд-эффектов)
- Этап генерации страницы (на нем сервер делает
renderToString, а клиент hydrate)- Этапы для загрузки данных при SPA-переходах
Механизм Command Line Chain во-первых, предоставляет наглядный флоу жизни приложения и запросов.
Во-вторых, выполняет все действия параллельно, на каждом этапе.
Во-вторых, вместе с механизмом Actions, позволяет установить таймауты на выполнение действий, отдать ответ на клиент не дожидаясь этих действий, и выполнить их повторно на клиенте.
Пример добавления нового действия на этап получения информации о пользователе, и документация - https://coretech-frontend.pages.devplatform.tcsbank.ru/tramvai/docs/concepts/command-line-runner#resolve_user_deps
// загружаем в store данные, необходимые для последующего рендеринга страницы
{
provide: commandLineListTokens.resolveUserDeps,
multi: true,
useFactory: ({ store }) => {
return async function updateCounterStore() {
await someAsyncAction();
store.dispatch(incrementEvent());
};
},
deps: {
store: STORE_TOKEN,
},
}
При необходимости, с помощью провайдеров можно предоставлять хуки и guard'ы для переходов роутера, например для проверки доступов пользователя к конкретной странице - https://tramvai.dev/docs/references/libs/router#router-guards
Также, есть провайдеры, позволяющие добавить скрипты, стили или meta-теги в HTML разметку итоговой страницы.
Пример такого провайдера, и документация - https://coretech-frontend.pages.devplatform.tcsbank.ru/tramvai/docs/references/modules/render#как-добавить-загрузку-ассетов-на-странице
// регистрируем meta viewport, который будет добавлятся на каждую страницу
{
provide: RENDER_SLOTS,
multi: true,
useValue: {
type: ResourceType.asIs,
slot: ResourceSlot.HEAD_META,
payload:
'<meta name="viewport" content="width=device-width, initial-scale=1">',
},
}
Такая гибкость дает нам возможность создавать отдельные модули, которые по сути являются списком провайдеров, вместе реализующих определенную фичу, и подключать эти модули простым добавлением в список modules при создании нашего приложения.
tramvai.dev
router | tramvai
Routing library. It can work both on the server and on the client. Designed primarily for building isomorphic applications.
Привет!
В ближайших постах хочу рассказать про устройство монорепозитория
Наш приватный репозиторий содержит около 150 npm библиотек, инфраструктуру для сборки, тестирования и публикации этих пакетов, сайт документации и различные утилиты.
Версиями пакетов, публикациями и генерацией ченджлогов управляет внутренняя разработка Тинькофф - библиотека
На каждый мерж в master ветку,
На каждый релизный тег запускается пайплайн публикации пакетов в приватный npm registry, и одновременно зеркалирование публичного кода на Github.
Для управления версиями пакетов в репозитории мы используем два важных подхода - сквозное версионирование, и хранение версий в релизных тегах.
Сквозное версионирование используется для всех пакетов, имеющий непосредственное отношение к фреймворку (cli, ядро фреймворка, многочисленные модули), этот термин означает что все unified пакеты общую версию, и должны обновляться одновременно.
Такой подход вы можете увидеть у
Основной плюс unified версионирования - гарантируется совместимость между пакетами одной версии. Это очень крутая возможность, т.к. раньше у пользователя был только один простой вариант поднять версию фреймворка, не потеряв совместимость - устанавливать все пакеты latest версии.
Один из минусов подхода - любое обновление пакета из списка unified, требует поднять версии и опубликовать все эти пакеты из списка, что значительно замедляет CI.
Для удобной работы со сквозными версиями tramvai предлагает два механизма:
- cli команда
- утилита
Хранение версий в релизных тегах само по себе не дает преимуществ, и у нас используется вместе со stub версиями пакетов в исходных
Допустим, у нас был пакет с зависимостями:
Раньше, каждый крупный Merge Request сопровождался конфликтами, если в master ветке обновлялась версии пакетов , а затронутые библиотеки в MR содержали изменения в
Теперь, наш пакет выглядит так:
Версия
Раньше я не встречался с таким подходом, и в целом не работал с монорепозиториями, но на своем опыте увидел, как это упростило разработку.
Также, для монорепы мы используем
На Github репозитории, для публикации кода в npm, версии пакетов поставляются в файле https://github.com/TinkoffCreditSystems/tramvai/blob/main/packages-versions.json
В ближайших постах хочу рассказать про устройство монорепозитория
tramvai, и про решение зеркалировать публичный код на Github, вместо полной миграции.Наш приватный репозиторий содержит около 150 npm библиотек, инфраструктуру для сборки, тестирования и публикации этих пакетов, сайт документации и различные утилиты.
Версиями пакетов, публикациями и генерацией ченджлогов управляет внутренняя разработка Тинькофф - библиотека
pvm. На каждый мерж в master ветку,
pvm определяет какие пакеты были изменены, поднимает их версии согласно conventional commits, и создает релизный тег, где и хранятся все версии. На каждый релизный тег запускается пайплайн публикации пакетов в приватный npm registry, и одновременно зеркалирование публичного кода на Github.
Для управления версиями пакетов в репозитории мы используем два важных подхода - сквозное версионирование, и хранение версий в релизных тегах.
Сквозное версионирование используется для всех пакетов, имеющий непосредственное отношение к фреймворку (cli, ядро фреймворка, многочисленные модули), этот термин означает что все unified пакеты общую версию, и должны обновляться одновременно.
Такой подход вы можете увидеть у
Angular , и с некоторыми ограничениями, в монорепозиториях использующих Lerna - https://github.com/lerna/lerna#fixedlocked-mode-defaultОсновной плюс unified версионирования - гарантируется совместимость между пакетами одной версии. Это очень крутая возможность, т.к. раньше у пользователя был только один простой вариант поднять версию фреймворка, не потеряв совместимость - устанавливать все пакеты latest версии.
Один из минусов подхода - любое обновление пакета из списка unified, требует поднять версии и опубликовать все эти пакеты из списка, что значительно замедляет CI.
Для удобной работы со сквозными версиями tramvai предлагает два механизма:
- cli команда
tramvai update для легкого обновления версий - утилита
@tramvai/tools-check-versions, которая поставляется вместе с core пакетом, и на этапе postinstall проверяет версии установленных зависимостей в приложении. Хранение версий в релизных тегах само по себе не дает преимуществ, и у нас используется вместе со stub версиями пакетов в исходных
package.json файлах.Допустим, у нас был пакет с зависимостями:
{
"name": "@tramvai/foo",
"version": "0.1.0",
"dependencies": {
"@tramvai/bar": "^1.1.0",
"@tramvai/baz": "^2.0.0"
}
}
Раньше, каждый крупный Merge Request сопровождался конфликтами, если в master ветке обновлялась версии пакетов , а затронутые библиотеки в MR содержали изменения в
dependencies. Теперь, наш пакет выглядит так:
{
"name": "@tramvai/foo",
"version": "0.0.0-stub",
"dependencies": {
"@tramvai/bar": "0.0.0-stub",
"@tramvai/baz": "0.0.0-stub"
}
}
Версия
0.0.0-stub никогда не вызовет конфликтов слияния, а вычисление реальных версий происходит только в CI - при создании нового релизного тега и публикации, внутри библиотеки pvm. Раньше я не встречался с таким подходом, и в целом не работал с монорепозиториями, но на своем опыте увидел, как это упростило разработку.
Также, для монорепы мы используем
yarn workspaces , и воркспейсы замечательно работают со stub версиями. На Github репозитории, для публикации кода в npm, версии пакетов поставляются в файле https://github.com/TinkoffCreditSystems/tramvai/blob/main/packages-versions.json
Про development и production сборку пакетов.
Все tramvai библиотеки написаны на
Для локальной разработки, мы используем
Project References дают возможность одной командой запускать сборку всех пакетов в репозитории, и при этом делать пересборку только измененного кода.
Также, при пересборке учитываются, кто зависит от изменившегося пакета, и выполняется пересборка итогового графа зависимостей - это позволяет сразу увидеть, как изменения одного пакета могут поломать другие.
Минимальное время старта команды
К сожалению, Typenoscript не дает инструмента для автоматической генерации Project References и поддержки их в актуальном состоянии - поэтому в tramvai монорепе используется самописная утилита
Вторая проблема Project References - это отсутствие сгенерированных
Мы решаем эту проблему так:
- В
- Перед публикацией, все поля
Для эффективной production сборки библиотек мы используем
- es2019 код с CommonJS модулями для запуска через старые версии NodeJS, бандл будет указан в
- es2019 код с ES modules для современных бандлеров (
- И если для пакета указано поле brow
Таким образом, на пакет, который указывает отдельные точки входа для серверной и клиентской сборки, сгенерируется три бандла - serv
Почему мы публикуем код в es20
Фронтенд библиотеки в Тинькофф (в части проектов, в основном это React экосистема) следуют принципу, когда публикуются только современный JavaScript код, а разработчики приложений транспилируют зависимости в подходящий стандарт кода.
Немного мотивации к публикации modern кода:
- https://web.dev/publish-modern-javanoscript/
- https://github.com/sindresorhus/ama/issues/446
- https://dev.to/garylchew/bringing-modern-javanoscript-to-libraries-432c
- https://babeljs.io/blog/2018/06/26/on-consuming-and-publishing-es2015+-packages
@tra
- Общий serv
- Для клиентского кода у нас есть два набора brow
Кажется это должно улучшаться в лучшую сторону с каждым обновлением существующих JS движков.
В итоге, мы получили неплохой Developer Experience при разработке в монорепе, и поставляем код библиотек минимального размера, с поддержкой tree-shaking и различных target окружений, для наших пользователей.
Все tramvai библиотеки написаны на
Typenoscript, и комфортная разработка требует тщательной настройки инфраструктуры монорепозитория. Для локальной разработки, мы используем
tsc и Project References - https://www.typenoscriptlang.org/docs/handbook/project-references.html, в этой связке watch режим имеет минимальное время старта, и очень быструю пересборку. Project References дают возможность одной командой запускать сборку всех пакетов в репозитории, и при этом делать пересборку только измененного кода.
Также, при пересборке учитываются, кто зависит от изменившегося пакета, и выполняется пересборка итогового графа зависимостей - это позволяет сразу увидеть, как изменения одного пакета могут поломать другие.
Минимальное время старта команды
tsc --build --watch достигается за счет кэширования информации о сборке каждого пакета.К сожалению, Typenoscript не дает инструмента для автоматической генерации Project References и поддержки их в актуальном состоянии - поэтому в tramvai монорепе используется самописная утилита
@tramvai-monorepo/fix-ts-references Вторая проблема Project References - это отсутствие сгенерированных
.d.ts файлов при первой сборке, и возможные ошибки этой сборки. Мы решаем эту проблему так:
- В
package.json каждого пакета поле typings смотрит на исходные .ts файлы в папке src - Перед публикацией, все поля
typings заменяются на собранные .d.ts файлы из папки libДля эффективной production сборки библиотек мы используем
rollup, поверх которого написана утилита @tramvai/build - https://tramvai.dev/docs/references/tools/build @tramvai/build собирает код каждого пакета в несколько общих бандлов: - es2019 код с CommonJS модулями для запуска через старые версии NodeJS, бандл будет указан в
"main" поле в package.json - es2019 код с ES modules для современных бандлеров (
@tramvai/cli использует webpack@5), бандл будет указан в "module" поле в package.json - И если для пакета указано поле brow
ser в package.json, собирается отдельный es2019 код с ES modules, содержащий код для браузерного окружения Таким образом, на пакет, который указывает отдельные точки входа для серверной и клиентской сборки, сгенерируется три бандла - serv
er.js, server.es.js и browser.js
Спецификацию поля browser можно посмотреть тут - https://github.com/defunctzombie/package-browser-field-spec Почему мы публикуем код в es20
19, вместо es5? Фронтенд библиотеки в Тинькофф (в части проектов, в основном это React экосистема) следуют принципу, когда публикуются только современный JavaScript код, а разработчики приложений транспилируют зависимости в подходящий стандарт кода.
Немного мотивации к публикации modern кода:
- https://web.dev/publish-modern-javanoscript/
- https://github.com/sindresorhus/ama/issues/446
- https://dev.to/garylchew/bringing-modern-javanoscript-to-libraries-432c
- https://babeljs.io/blog/2018/06/26/on-consuming-and-publishing-es2015+-packages
@tra
mvai/cli собирает бандлы приложения по такой логике: - Общий serv
er.js бандл с серверным кодом, содержащий все зависимости, и код стандарта es5.
Интересный факт, эксперименты с использованием es2019/es2020 кода на сервере показали ухудшение производительности примерно на 10%! - Для клиентского кода у нас есть два набора brow
serslist конфигов - default и modern, и es2019 код библиотек транспилируется в формат для целевых браузеров с помощью babel, генерируются отдельные чанки с default и modern кодом, и затем для браузера выбираются нужные через проверку User-Agent
Второй интересный факт, modern сборка может весить значительно меньше default, но как и в случае с серверным кодом, может работать медленнее. Кажется это должно улучшаться в лучшую сторону с каждым обновлением существующих JS движков.
В итоге, мы получили неплохой Developer Experience при разработке в монорепе, и поставляем код библиотек минимального размера, с поддержкой tree-shaking и различных target окружений, для наших пользователей.
www.typenoscriptlang.org
Documentation - Project References
How to split up a large TypeScript project
Добавлю парочку полезных ссылок:
Топовые советы в Wiki Typenoscript по ускорению сборки, об этой страничке я узнал в канале @wild_wild_web - https://github.com/microsoft/TypeScript/wiki/Performance
Например, совет по ограничению поля "types" почти в два раза ускорил нашу production сборку - https://github.com/microsoft/TypeScript/wiki/Performance#controlling-types-inclusion
Проблема была в том, что монорепозиторий содержит огромное количество тайпингов в зависимостях, и для транспиляции даже файла с одной строчкой кода, TS тратил очень много времени на резолв этих тайпингов, из которых реально нужные, без прямого импорта - это
Утилита для сборки пакетов
И мощный фреймворк для организации монорепозиториев NX - https://nx.dev/
Это достаточно сложный для освоения и интеграции инструмент, но потенциально может в разы ускорять CI процессы в больших монорепозиториях.
Пример работы инструмента Nx Cloud, с распределенным выполнением задач и кэшированием - https://www.youtube.com/watch?v=Exs64pscwxA&ab_channel=Nrwl-NarwhalTechnologiesInc.
Топовые советы в Wiki Typenoscript по ускорению сборки, об этой страничке я узнал в канале @wild_wild_web - https://github.com/microsoft/TypeScript/wiki/Performance
Например, совет по ограничению поля "types" почти в два раза ускорил нашу production сборку - https://github.com/microsoft/TypeScript/wiki/Performance#controlling-types-inclusion
Проблема была в том, что монорепозиторий содержит огромное количество тайпингов в зависимостях, и для транспиляции даже файла с одной строчкой кода, TS тратил очень много времени на резолв этих тайпингов, из которых реально нужные, без прямого импорта - это
node и jestУтилита для сборки пакетов
microbundle, наш референс при разработке @tramvai/build - https://github.com/developit/microbundleИ мощный фреймворк для организации монорепозиториев NX - https://nx.dev/
Это достаточно сложный для освоения и интеграции инструмент, но потенциально может в разы ускорять CI процессы в больших монорепозиториях.
Пример работы инструмента Nx Cloud, с распределенным выполнением задач и кэшированием - https://www.youtube.com/watch?v=Exs64pscwxA&ab_channel=Nrwl-NarwhalTechnologiesInc.
GitHub
Performance
TypeScript is a superset of JavaScript that compiles to clean JavaScript output. - microsoft/TypeScript
👍1
Привет!
Про выход tramvai в open source.
Зачем это нужна для команды разработчиков, и для Тинкофф в целом?
Одна из важнейших вещей, это еще одно громкое заявление - "В Тинькофф крутой фронтенд!"
Разработчики Тинькофф делают все больше отличных open source проектов, пишут все больше интересных статей, выступают на конференциях - из таких активностей формируется образ компании, привлекательный для новых разработчиков.
Мы активно нанимаем коллег, и у нас классные интервьюеры 😉
Для мейнтейнеров tramvai, это новый источник вдохновения для развития фреймворка, возможность получить свежий взгляд со стороны.
И один из плюсов, это быстрое прототипирование с помощью JS песочниц, недавно мы добавили базовый шаблон трамвая на Codesandbox - https://codesandbox.io/s/tramvai-new-qgk90
Далее, опишу ряд проблем, которые могут стоять на пути у проприетарной разработки, усложняющие переход в OSS:
- Код, документация и история VCS могут содержать чувствительную информацию - самый простой пример, это ссылки на внутренние ресурсы
- Часть пакетов подходит для OSS, часть предназначена только для внутреннего использования
- Пакеты публикуются в приватный регистр пакетов
- Проект использует приватный CI
- Отсутствут локализация документации
Таким образом, полный переход Github требует отделить публичную часть проекта, перенести код без сохранения истории коммитов, настроить в Github Actions линтинг, unit и интеграционное тестирование, версионирование и публикацию пакетов, перевод, сборку и деплой документации.
Например, такой переход осуществил огненный Angular ui-kit от наших коллег - https://taiga-ui.dev/
Taiga содержит ряд проприетарных компонентов в приватном репозитории внутри Тинькофф, и приватный репозитрий использует публичный с помощью git submodules.
Почему этот путь не подошел для tramvai:
- Теряется история коммитов - это важный источник информации для разработчика
- Проект имеет достаточно сложный CI, и в версионировании и публикации пакетов участвует другая внутренняя разработка Тинькофф, о которой я писал в одном из предыдущих постов
- Все tramvai пакеты объединены сквозным версионированием, отделение публичных означает, что два набора пакетов разойдутся в версиях, и вернутся проблемы, связанные с обновлением зависимостей
- Не удобно делать глобальный рефакторинг, который затрагивает все пакеты
Каждая из этих проблем кажется неизбежным злом, но вместе порождают очень много сложностей, которые мы постарались решить инкрементальным выходом в OSS.
Про выход tramvai в open source.
Зачем это нужна для команды разработчиков, и для Тинкофф в целом?
Одна из важнейших вещей, это еще одно громкое заявление - "В Тинькофф крутой фронтенд!"
Разработчики Тинькофф делают все больше отличных open source проектов, пишут все больше интересных статей, выступают на конференциях - из таких активностей формируется образ компании, привлекательный для новых разработчиков.
Мы активно нанимаем коллег, и у нас классные интервьюеры 😉
Для мейнтейнеров tramvai, это новый источник вдохновения для развития фреймворка, возможность получить свежий взгляд со стороны.
И один из плюсов, это быстрое прототипирование с помощью JS песочниц, недавно мы добавили базовый шаблон трамвая на Codesandbox - https://codesandbox.io/s/tramvai-new-qgk90
Далее, опишу ряд проблем, которые могут стоять на пути у проприетарной разработки, усложняющие переход в OSS:
- Код, документация и история VCS могут содержать чувствительную информацию - самый простой пример, это ссылки на внутренние ресурсы
- Часть пакетов подходит для OSS, часть предназначена только для внутреннего использования
- Пакеты публикуются в приватный регистр пакетов
- Проект использует приватный CI
- Отсутствут локализация документации
Таким образом, полный переход Github требует отделить публичную часть проекта, перенести код без сохранения истории коммитов, настроить в Github Actions линтинг, unit и интеграционное тестирование, версионирование и публикацию пакетов, перевод, сборку и деплой документации.
Например, такой переход осуществил огненный Angular ui-kit от наших коллег - https://taiga-ui.dev/
Taiga содержит ряд проприетарных компонентов в приватном репозитории внутри Тинькофф, и приватный репозитрий использует публичный с помощью git submodules.
Почему этот путь не подошел для tramvai:
- Теряется история коммитов - это важный источник информации для разработчика
- Проект имеет достаточно сложный CI, и в версионировании и публикации пакетов участвует другая внутренняя разработка Тинькофф, о которой я писал в одном из предыдущих постов
- Все tramvai пакеты объединены сквозным версионированием, отделение публичных означает, что два набора пакетов разойдутся в версиях, и вернутся проблемы, связанные с обновлением зависимостей
- Не удобно делать глобальный рефакторинг, который затрагивает все пакеты
Каждая из этих проблем кажется неизбежным злом, но вместе порождают очень много сложностей, которые мы постарались решить инкрементальным выходом в OSS.
Первый этап, на котором сейчас находится проект - это зеркалирование публичного кода на Github, при этом вся разработка ведется в приватном репозитории.
На этапе исследования, мы нашли мощный инструмент, созданный для аналогичных целей внутри Google,
При чем, кажется это вторая или третья итерация по созданию такого инструмента от разработчиков Google.
Кстати, для copybara есть гибкая обвязка под Github Actions - https://github.com/Olivr/copybara-action и публичный Docker образ - https://github.com/anipos/copybara-docker-image
Каждый релиз трамвая, запускается джоба с генерацией конфига и запуском copybara.
Конфиг содержит список публичных файлов и трансформации для кода - например, замена ссылок приватной документации на https://tramvai.dev
Copybara проверяет изменения с последнего коммита в Github репе, схлопывает все новые коммиты в один релизный коммит, и пушить его на Github.
Затем в Github Actions запускается сборка и деплой документации, и сборки и публикация пакетов в npm.
Я несколько раз накосячил с публикацией пакетов и откатывал их, оказалось, что npm запрещает публикацию удаленных пакетов в течение 24 часов, и запрещает публиковать ту же версию, что была удалена 😥
По итогу, мы всегда имеем актуальный код на Github, наш фреймворк доступен для публичного использования, и при этом наши процессы разработки не поменялись.
Следующий шаг миграции в OSS - это зеркалирование MR от контрибьюторов из Github в наш приватный репозиторий, на данный момент мы можем делать это только вручную.
Двусторонняя синхронизация репозиториев это потенциальный источник проблем, поэтому мы не спешим с переходом на этот этап.
В будущем хочется полностью перейти на Github, отказавшись от синхронизации репозиториев.
На этапе исследования, мы нашли мощный инструмент, созданный для аналогичных целей внутри Google,
copybara - https://github.com/google/copybara.При чем, кажется это вторая или третья итерация по созданию такого инструмента от разработчиков Google.
Кстати, для copybara есть гибкая обвязка под Github Actions - https://github.com/Olivr/copybara-action и публичный Docker образ - https://github.com/anipos/copybara-docker-image
Каждый релиз трамвая, запускается джоба с генерацией конфига и запуском copybara.
Конфиг содержит список публичных файлов и трансформации для кода - например, замена ссылок приватной документации на https://tramvai.dev
Copybara проверяет изменения с последнего коммита в Github репе, схлопывает все новые коммиты в один релизный коммит, и пушить его на Github.
Затем в Github Actions запускается сборка и деплой документации, и сборки и публикация пакетов в npm.
Я несколько раз накосячил с публикацией пакетов и откатывал их, оказалось, что npm запрещает публикацию удаленных пакетов в течение 24 часов, и запрещает публиковать ту же версию, что была удалена 😥
По итогу, мы всегда имеем актуальный код на Github, наш фреймворк доступен для публичного использования, и при этом наши процессы разработки не поменялись.
Следующий шаг миграции в OSS - это зеркалирование MR от контрибьюторов из Github в наш приватный репозиторий, на данный момент мы можем делать это только вручную.
Двусторонняя синхронизация репозиториев это потенциальный источник проблем, поэтому мы не спешим с переходом на этот этап.
В будущем хочется полностью перейти на Github, отказавшись от синхронизации репозиториев.
GitHub
GitHub - google/copybara: Copybara: A tool for transforming and moving code between repositories.
Copybara: A tool for transforming and moving code between repositories. - google/copybara
😁1
Привет!
Хочу рассказать про одно не законченное исследование, и возможно таким образом дать себе инерцию завершить это дело)
Фронтенд экосистема Тинькофф состоит из двух мощных экосистем - Angular и React, примерно 100+ разработчиков на каждый фреймворк.
Для каждой экосистемы разработан и поддерживается свой ui-kit - один для React, и второй для Angular.
Целью исследования было проверить, можно ли создать функциональный фреймворк-агностик ui-kit на одной технологии, и эффективно переиспользовать его в каждом нашем фреймворке.
Спойлер - реализации прототипа только на одной технологии мне хватило для вывода, что реализовать такой kit, не потеряв при его использовании developer experience, сравнительно с нативным китом, невозможно, или крайне сложно, но мне по прежнему очень интересно, чего можно добиться с различными технологиями.
Начнем со списка возможностей, который дает нам React UI-kit в React приложении:
- Можно передавать свойства в компонент
- Обновление свойств приведет к обновлению DOM
- В компонент можно вкладывать другие компоненты (композиция с помощью children и render props)
- Доступ к context приложения
- Поддержка SSR
Начать исследования я решил со знакомых, и на первый взгляд максимально близкий друг к другу технологий - React приложение и Preact UI-kit.
Другие потенциальные технологии - Web Components (плюс один из фреймворков с поддержкой SSR), Svelte.
Для интеграции Preact компонентов (да и любых других в будущем) в React приложение я решил использовать концепцию адаптеров - такой адаптер принимает Preact компонент из кита, и возвращает React компонент, готовый к использованию в приложении.
Код адаптера доступен тут - https://github.com/SuperOleg39/agnostik-kit/blob/master/adapters/preact-to-react/src/index.js
Для проверки возможностей, заранее создал адаптер-пустышку, обернул в него компоненты, и написал в приложении основные кейсы - с передачей пропсов, с коллбэками, пробросом в children текста, React компонентов, и других Preact компонентов, обернутых в адаптер.
Кейсы можно посмотреть тут - https://github.com/SuperOleg39/agnostik-kit/blob/master/apps/react-app/src/App.js
Рассмотрим подробнее реализацию адаптера.
На серверной стороне, мы должны отрендерить Preact компонент в строку c помощью
Вспомним требование про композицию - в children могут быть React компоненты, которые заранее надо отрендерить в строку, и так же вставить в китовый компонент.
React компоненты могут содержать другие PreactAdapter компоненты, так что по сути это рекурсивный процесс.
Оказывается, что механизм добавления произвольной разметки в React и Preact одинаковый -
Это дает нам возможность композиции, в несколько этапов:
1. Делаем ReactDOM.renderToString текущих children
2. Результат передаем в
3. Делаем Preact.renderToString этого компонента
4. Результат передаем в
В клиентском коде, в этот враппер необходимо отрендерить Preact компонент, а при наличии children, сделать их гидрацию (помним, что там будут React компоненты), используя ref на китовый компонент в качестве root.
Также, необходимо делать повторный рендеринг Preact компонента, при каждом изменении props.
Такой небольшой адаптер покрывает большую часть полноценной интеграции, и мы уже можем увидеть ряд недостатков:
- Теряем react context для вложенных React компонентов
- DOM дерево разрастается из-за врапперов для
- Нарушаем флоу работы React - вместо стандартного рендеринга дерева компонентов от самого вложенного, к родителю, и общего маунта, мы рендерим компоненты в адаптере сверху вниз, и маунтим по цепочке.
В теории, при больших поддеревьях компонентов, можно увидеть значительные задержки рендеринга.
- Нужно писать надежный механизм перерендеринга китового компонента
Хочу рассказать про одно не законченное исследование, и возможно таким образом дать себе инерцию завершить это дело)
Фронтенд экосистема Тинькофф состоит из двух мощных экосистем - Angular и React, примерно 100+ разработчиков на каждый фреймворк.
Для каждой экосистемы разработан и поддерживается свой ui-kit - один для React, и второй для Angular.
Целью исследования было проверить, можно ли создать функциональный фреймворк-агностик ui-kit на одной технологии, и эффективно переиспользовать его в каждом нашем фреймворке.
Спойлер - реализации прототипа только на одной технологии мне хватило для вывода, что реализовать такой kit, не потеряв при его использовании developer experience, сравнительно с нативным китом, невозможно, или крайне сложно, но мне по прежнему очень интересно, чего можно добиться с различными технологиями.
Начнем со списка возможностей, который дает нам React UI-kit в React приложении:
- Можно передавать свойства в компонент
- Обновление свойств приведет к обновлению DOM
- В компонент можно вкладывать другие компоненты (композиция с помощью children и render props)
- Доступ к context приложения
- Поддержка SSR
Начать исследования я решил со знакомых, и на первый взгляд максимально близкий друг к другу технологий - React приложение и Preact UI-kit.
Другие потенциальные технологии - Web Components (плюс один из фреймворков с поддержкой SSR), Svelte.
Для интеграции Preact компонентов (да и любых других в будущем) в React приложение я решил использовать концепцию адаптеров - такой адаптер принимает Preact компонент из кита, и возвращает React компонент, готовый к использованию в приложении.
Код адаптера доступен тут - https://github.com/SuperOleg39/agnostik-kit/blob/master/adapters/preact-to-react/src/index.js
Для проверки возможностей, заранее создал адаптер-пустышку, обернул в него компоненты, и написал в приложении основные кейсы - с передачей пропсов, с коллбэками, пробросом в children текста, React компонентов, и других Preact компонентов, обернутых в адаптер.
Кейсы можно посмотреть тут - https://github.com/SuperOleg39/agnostik-kit/blob/master/apps/react-app/src/App.js
Рассмотрим подробнее реализацию адаптера.
На серверной стороне, мы должны отрендерить Preact компонент в строку c помощью
preact-render-to-string, до рендеринга в строку нашего React приложения.Вспомним требование про композицию - в children могут быть React компоненты, которые заранее надо отрендерить в строку, и так же вставить в китовый компонент.
React компоненты могут содержать другие PreactAdapter компоненты, так что по сути это рекурсивный процесс.
Оказывается, что механизм добавления произвольной разметки в React и Preact одинаковый -
dangerouslySetInnerHTML: { __html: string }, правда нам потребуется создавать лишний тег - враппер, который и будет содержать эту разметку.Это дает нам возможность композиции, в несколько этапов:
1. Делаем ReactDOM.renderToString текущих children
2. Результат передаем в
dangerouslySetInnerHTML Preact компонента3. Делаем Preact.renderToString этого компонента
4. Результат передаем в
dangerouslySetInnerHTML враппераВ клиентском коде, в этот враппер необходимо отрендерить Preact компонент, а при наличии children, сделать их гидрацию (помним, что там будут React компоненты), используя ref на китовый компонент в качестве root.
Также, необходимо делать повторный рендеринг Preact компонента, при каждом изменении props.
Такой небольшой адаптер покрывает большую часть полноценной интеграции, и мы уже можем увидеть ряд недостатков:
- Теряем react context для вложенных React компонентов
- DOM дерево разрастается из-за врапперов для
dangerouslySetInnerHTML- Нарушаем флоу работы React - вместо стандартного рендеринга дерева компонентов от самого вложенного, к родителю, и общего маунта, мы рендерим компоненты в адаптере сверху вниз, и маунтим по цепочке.
В теории, при больших поддеревьях компонентов, можно увидеть значительные задержки рендеринга.
- Нужно писать надежный механизм перерендеринга китового компонента
GitHub
agnostik-kit/adapters/preact-to-react/src/index.js at master · SuperOleg39/agnostik-kit
Contribute to SuperOleg39/agnostik-kit development by creating an account on GitHub.
Взвесив все эти минусы интеграции таких близких на первый взгляд технологий, решили не продолжать исследования, но идея попробовать другие технологии осталась.
Через несколько месяцев, мне попалась статья на habr про создание общего UI-kit для Vue и React с помощью Stencil - https://habr.com/ru/company/uchi_ru/blog/543308/
Веб-компоненты в связке со Stencil кажутся перспективными для фреймворк агностик кита, из-за флоу инициализации и работы на сервере и клиенте - рендеринг web components происходит поверх уже отрендеренного приложения, что в теории позволяет сохранить общее дерево нашего React приложения.
А внешняя схожесть React и Preact на самом деле никак не упрощает интеграцию, из-за завязки рендеринга фреймворка на конкретный DOM узел.
Также, у Stencil существует интеграция для React - https://stenciljs.com/docs/react
Про не завершенную, но интересную попытку интегрировать Stencil постараюсь рассказать в следующем посте.
Через несколько месяцев, мне попалась статья на habr про создание общего UI-kit для Vue и React с помощью Stencil - https://habr.com/ru/company/uchi_ru/blog/543308/
Веб-компоненты в связке со Stencil кажутся перспективными для фреймворк агностик кита, из-за флоу инициализации и работы на сервере и клиенте - рендеринг web components происходит поверх уже отрендеренного приложения, что в теории позволяет сохранить общее дерево нашего React приложения.
А внешняя схожесть React и Preact на самом деле никак не упрощает интеграцию, из-за завязки рендеринга фреймворка на конкретный DOM узел.
Также, у Stencil существует интеграция для React - https://stenciljs.com/docs/react
Про не завершенную, но интересную попытку интегрировать Stencil постараюсь рассказать в следующем посте.
Хабр
Единый UI-кит и синхронизация дизайна в Учи.ру. Часть 1
Пожалуй, все, кто имел дело с развитием семейства сайтов, сталкивались с проблемой поддержания единого вида компонентов. Когда счет сервисов идет на десятки и со...
Привет!
Продолжаю тему создания фреймворк-агностик UI-kit, с помощью веб-компонентов и Stencil - https://stenciljs.com/
Почему нам вообще нужно использовать какой-то фреймворк для веб-компонентов?
Для этого есть ряд причин, вот несколько самых важных:
- web components не поддерживают server-side rendering
- механизм обновления DOM должен реализовывать разработчик
- много шаблонного кода
Фреймворк
Отдельная боль - это не прозрачная конфигурация, и отсутствие рецептов в документации, как интегрировать Stencil, например новый UI-kit, в уже существующую монорепу.
Пока писал этот пост, обнаружил что во время предыдущего исследования сделал не корректную интеграцию, генерируя Stencil код не в библиотеку с компонентами, а в само приложение - https://github.com/SuperOleg39/agnostik-kit/pull/1
Нашел на гитхабе хороший пример интеграции Stencil и NextJS - https://github.com/jagreehal/nextjs-stenciljs-ssr-example, его и буду использовать для нового исследования.
Вот так выглядит рендеринг в строку на сервере:
И гидрация на клиенте:
Пример достаточно старый, и не использует официальные биндинги для React - https://stenciljs.com/docs/react, вместо этого используется хук
Сгенерировал биндинги в этом PR - https://github.com/SuperOleg39/nextjs-stenciljs-ssr-example/pull/1, большая часть кода - генерируется Stencil.
Интеграция React биндингов тоже крайне не привычна - мы должны создать пакет - "пустышку" для будущих React компонентов, но при этом компилировать его содержимое будет наш пакет с web компонентами, везде сложно понять, что надо сохранять в git, а что нужно только для публикации.
Еще заметил минус у биндингов - нет поддержки SSR при генерации React компонентов, и отсутствует нужный рецепт в документации, по итогу надо использовать оба пакета - с web-компонентами и с React биндингами.
Краткое резюме, после базовой интеграции Stencil с React и NextJS:
сама интеграция - достаточно проблемная, но мы получили типизированные React компоненты и возможность передавать в свойста веб-компонентов children, функции и объекты.
children можно передавать, если в веб-компоненте используется
Далее, я начал проверять, как ведут себя
Кстати, в React биндингах Stencil есть хак -
Затем, я провел ряд экспериментов, где в web-component вставлял React ноды, другие web-components, PR с тестовыми кейсами можно посмотреть тут (файл `a.tsx`) - https://github.com/SuperOleg39/nextjs-stenciljs-ssr-example/pull/2/files
Продолжаю тему создания фреймворк-агностик UI-kit, с помощью веб-компонентов и Stencil - https://stenciljs.com/
Почему нам вообще нужно использовать какой-то фреймворк для веб-компонентов?
Для этого есть ряд причин, вот несколько самых важных:
- web components не поддерживают server-side rendering
- механизм обновления DOM должен реализовывать разработчик
- много шаблонного кода
Stencil решает эти проблемы, предоставляя поддержку SSR, vDOM + реактивные биндинги, и возможность использовать JSXФреймворк
Stencil дает много возможностей, хотя оказался для меня достаточно не комфортным в интеграции - дело в том, что аналогично Svelte, Stencil это компилируемый фреймворк, и например создавая библиотеку компонентов, перед публикацией мы должны генерировать код для рендеринга этих компонентов, который будет использовать наше приложение.Отдельная боль - это не прозрачная конфигурация, и отсутствие рецептов в документации, как интегрировать Stencil, например новый UI-kit, в уже существующую монорепу.
Пока писал этот пост, обнаружил что во время предыдущего исследования сделал не корректную интеграцию, генерируя Stencil код не в библиотеку с компонентами, а в само приложение - https://github.com/SuperOleg39/agnostik-kit/pull/1
Нашел на гитхабе хороший пример интеграции Stencil и NextJS - https://github.com/jagreehal/nextjs-stenciljs-ssr-example, его и буду использовать для нового исследования.
Вот так выглядит рендеринг в строку на сервере:
// под капотом у renderToHTML - ReactDOM.renderToString
const html = await app.renderToHTML(req, res, req.path, req.query);
// рендерим Stencil поверх результата рендеринга ReactDOM
const renderedHtml = await stencil.renderToString(html);
И гидрация на клиенте:
const {
applyPolyfills,
defineCustomElements
} = require("stencil-web-components/loader");
// все это происходит после ReactDOM.hydrate
applyPolyfills().then(() => {
defineCustomElements(window);
});
Пример достаточно старый, и не использует официальные биндинги для React - https://stenciljs.com/docs/react, вместо этого используется хук
useCustomElementСгенерировал биндинги в этом PR - https://github.com/SuperOleg39/nextjs-stenciljs-ssr-example/pull/1, большая часть кода - генерируется Stencil.
Интеграция React биндингов тоже крайне не привычна - мы должны создать пакет - "пустышку" для будущих React компонентов, но при этом компилировать его содержимое будет наш пакет с web компонентами, везде сложно понять, что надо сохранять в git, а что нужно только для публикации.
Еще заметил минус у биндингов - нет поддержки SSR при генерации React компонентов, и отсутствует нужный рецепт в документации, по итогу надо использовать оба пакета - с web-компонентами и с React биндингами.
Краткое резюме, после базовой интеграции Stencil с React и NextJS:
сама интеграция - достаточно проблемная, но мы получили типизированные React компоненты и возможность передавать в свойста веб-компонентов children, функции и объекты.
children можно передавать, если в веб-компоненте используется
<slot />Далее, я начал проверять, как ведут себя
children в веб-компоненте, и обнаружил, что Stencil по умолчанию не использует shadow DOM, и все веб-компоненты, используемые как React биндинги, сломаны на этапе ReactDOM.hydrate - для исправления проблемы нужно включать shadow DOM вручную для каждого Stencil компонента, через shadow: true, немного о проблеме в этой статье - https://leechy.dev/hide-stencil-childrenКстати, в React биндингах Stencil есть хак -
defineCustomElements вызывается сразу, т.е. до ReactDOM.hydrate - а React уже получает shadow DOM при гидрации, и не модифицирует его.Затем, я провел ряд экспериментов, где в web-component вставлял React ноды, другие web-components, PR с тестовыми кейсами можно посмотреть тут (файл `a.tsx`) - https://github.com/SuperOleg39/nextjs-stenciljs-ssr-example/pull/2/files
Stencil
Build. Customize. Distribute. Adopt.
Я был настроен скептически, но все тесты показали отличный результат:
- Сам механизм интеграции веб-компонентов устроен так, что React context не теряется
- Рендеринг на сервере и гидрация - работают корректно
- React прекрасно перерендеривает children, которые веб-компоненты рендерят через
- Веб-компоненты прекрасно обрабатывают изменение локального состояния и shadow DOM, не ломая управляемую React`ом разметку, которая приходит через
- Многократная вложенность React и веб-компонентов друг в друга ничего не ломают
Все работает корректно, если я все правильно понял, т.к. React управляет только разметкой в
Завершая исследование, могу сказать что web-components действительно отлично подходят для создания компонентов, которые могут быть использованы в любом фреймворке.
Мейнтейнеры
Для SPA приложений, интеграция веб-компонентов тривиальна, и может даже не требовать отдельных фреймворков вроде
Какие вопросы у меня остаются к
- Документация и интеграции - очень много открытых вопросов, интеграция с React рассмотрена поверхностно
- Размер генерируемого кода - для нескольких компонентов, Stencil сгенерировал 400кб исходного кода. Сделал анализа бандла, импорт и использование Stencil компонента из нашего UI-kit добавляет примерно 45кб gzip кода.
Определенно, такое маленькое приложение на Svelte сгенерировало бы гораздо меньше кода, это примерно размер react + react-dom
- Производительность рендеринга - сделал поверхностные замеры, похоже и на сервере и на клиенте все быстро - но приложение слишком небольшое
- Необходимость в React биндингах - очень уж не очевидный механизм для их генерации
- Сам механизм интеграции веб-компонентов устроен так, что React context не теряется
- Рендеринг на сервере и гидрация - работают корректно
- React прекрасно перерендеривает children, которые веб-компоненты рендерят через
<slot />, не ломая разметку веб-компонента- Веб-компоненты прекрасно обрабатывают изменение локального состояния и shadow DOM, не ломая управляемую React`ом разметку, которая приходит через
children- Многократная вложенность React и веб-компонентов друг в друга ничего не ломают
Все работает корректно, если я все правильно понял, т.к. React управляет только разметкой в
template, а веб-компоненты сразу рендерят эти template в slot, т.е. области ответственности фреймворков не пересекаются.Завершая исследование, могу сказать что web-components действительно отлично подходят для создания компонентов, которые могут быть использованы в любом фреймворке.
Мейнтейнеры
Stencil проделали огромную работу, и фреймворк дает нам возможность использовать компоненты в SSR React приложении, без каких-то серьезных ограничений в developer experience.Для SPA приложений, интеграция веб-компонентов тривиальна, и может даже не требовать отдельных фреймворков вроде
Stencil или lit-element для простых случаев.Какие вопросы у меня остаются к
Stencil:- Документация и интеграции - очень много открытых вопросов, интеграция с React рассмотрена поверхностно
- Размер генерируемого кода - для нескольких компонентов, Stencil сгенерировал 400кб исходного кода. Сделал анализа бандла, импорт и использование Stencil компонента из нашего UI-kit добавляет примерно 45кб gzip кода.
Определенно, такое маленькое приложение на Svelte сгенерировало бы гораздо меньше кода, это примерно размер react + react-dom
- Производительность рендеринга - сделал поверхностные замеры, похоже и на сервере и на клиенте все быстро - но приложение слишком небольшое
- Необходимость в React биндингах - очень уж не очевидный механизм для их генерации
Интересная статья про минусы
Но похоже это единственное хорошее решение с поддержкой SSR, в
Stencil - https://www.abeautifulsite.net/posts/moving-from-stencil-to-lit-element/Но похоже это единственное хорошее решение с поддержкой SSR, в
lit это только в экспериментах/планах - https://www.polymer-project.org/blog/2020-09-22-lit-element-and-lit-html-next-preview и https://github.com/lit/lit/tree/main/packages/labs/ssrwww.abeautifulsite.net
Moving from Stencil to LitElement
A blog about everything web. Est. 2007
В чате Reatom увидел очень интересную библиотеку Mitosis - https://github.com/BuilderIO/mitosis
Позволяет писать компоненты на синтаксисе, близком к React и Solid, и компилировать их в нативные компоненты на большинстве фреймворков.
Ещё один потенциальный кандидат на инструмент для создания фреймворк-агностик UI-kit
Позволяет писать компоненты на синтаксисе, близком к React и Solid, и компилировать их в нативные компоненты на большинстве фреймворков.
Ещё один потенциальный кандидат на инструмент для создания фреймворк-агностик UI-kit
GitHub
GitHub - BuilderIO/mitosis: Write components once, run everywhere. Compiles to React, Vue, Qwik, Solid, Angular, Svelte, and more.
Write components once, run everywhere. Compiles to React, Vue, Qwik, Solid, Angular, Svelte, and more. - GitHub - BuilderIO/mitosis: Write components once, run everywhere. Compiles to React, Vue, ...
SuperOleg dev notes pinned «Привет! Несколько дней назад на Github состоялся релиз фреймворка tramvai - https://github.com/TinkoffCreditSystems/tramvai tramvai - это фреймворк для создания SSR приложений на React, внутренняя разработка Тинькофф, и последние полтора года я работаю в…»
Привет!
Несколько дней назад прочитал статью от Shawn Wang https://twitter.com/swyx - "Why do Webdevs keep trying to kill REST?" - https://www.swyx.io/client-server-battle/
В этой статье представлен интересный взгляд на дебаты REST против GraphQL - Swyx рассказывает, что на самом деле эти споры про
Больше всего меня впечатлил широкий кругозор автора - Swyx приводит в примерах множество технологий, о которых я даже не слышал, но которые имеют прямое отношение к фронтенд разработке.
Поэтому, после свобдного перевода статьи, хочу сделать небольшие обзоры этих новых для меня технологий.
Smart Client - это подход, при котором сначала обновляется состояние на клиенте, и затем отправляется на сервер.
Рассмотрим на примерах:
- Кастомный smart client - решение на основе Redux, или сторов Svelte, где мы самостоятельно управляем координацией данных на сервере и клиенте
- Специализированная библиотека для управления состоянием и запросами - React Query, Apollo Client, RxDB, GunDB, WatermelonDB и Absurd-SQL
- Фреймворк, который абстрагирует координацию данных - Next.js или Blitz.js (тут я не совсем понял автора, т.к. Next.js например предоставляет интеграцию с библиотекой
- SDK к облачной платформе - Google's Firebase или AWS Amplify / AppSync, из коробки предоставляют интеграции с такими бэкенд ресурсами как авторизация, база данных и другие хранилища
Smart Server - обратный подход, когда обновления состояния сначала отправляются на сервер, который в свою очередь отправляет обновленный view на клиент (HTML чанки, сериализованные React компоненты или XML)
Пример актуальных технологий:
- Phoenix Liveview
- Rails Hotwire
- React Server Components
- ASP.NET Web Forms
Smart Server - это не новый подход, а эволюция традиционного серверного рендеринга.
Laravel, Django, Wordpress и ряд других фреймворков отправляют на клиент отрендеренные HTML шаблоны, а клиент добавляет интерактивность, и отправляет запросы на REST эндпоинты сервера, такое классическое разделение на фронтенд и бэкенд.
Из моего опыта, я успел поучаствовать в двух таких проектах, где
Почему же мы уходим от старой клиент-серверной парадигмы, и какой подход лучше?
Несколько дней назад прочитал статью от Shawn Wang https://twitter.com/swyx - "Why do Webdevs keep trying to kill REST?" - https://www.swyx.io/client-server-battle/
В этой статье представлен интересный взгляд на дебаты REST против GraphQL - Swyx рассказывает, что на самом деле эти споры про
Smart Servers против Smart Clients (не стал переводить в лоб, возможно тут подойдут термины тонкий / толстые клиент или сервер)Больше всего меня впечатлил широкий кругозор автора - Swyx приводит в примерах множество технологий, о которых я даже не слышал, но которые имеют прямое отношение к фронтенд разработке.
Поэтому, после свобдного перевода статьи, хочу сделать небольшие обзоры этих новых для меня технологий.
Smart Client - это подход, при котором сначала обновляется состояние на клиенте, и затем отправляется на сервер.
Рассмотрим на примерах:
- Кастомный smart client - решение на основе Redux, или сторов Svelte, где мы самостоятельно управляем координацией данных на сервере и клиенте
- Специализированная библиотека для управления состоянием и запросами - React Query, Apollo Client, RxDB, GunDB, WatermelonDB и Absurd-SQL
- Фреймворк, который абстрагирует координацию данных - Next.js или Blitz.js (тут я не совсем понял автора, т.к. Next.js например предоставляет интеграцию с библиотекой
swr, аналогом React Query, но в чистом виде ничего специфичного не дает)- SDK к облачной платформе - Google's Firebase или AWS Amplify / AppSync, из коробки предоставляют интеграции с такими бэкенд ресурсами как авторизация, база данных и другие хранилища
Smart Server - обратный подход, когда обновления состояния сначала отправляются на сервер, который в свою очередь отправляет обновленный view на клиент (HTML чанки, сериализованные React компоненты или XML)
Пример актуальных технологий:
- Phoenix Liveview
- Rails Hotwire
- React Server Components
- ASP.NET Web Forms
Smart Server - это не новый подход, а эволюция традиционного серверного рендеринга.
Laravel, Django, Wordpress и ряд других фреймворков отправляют на клиент отрендеренные HTML шаблоны, а клиент добавляет интерактивность, и отправляет запросы на REST эндпоинты сервера, такое классическое разделение на фронтенд и бэкенд.
Из моего опыта, я успел поучаствовать в двух таких проектах, где
php фреймворк отвечал за рендеринг HTML шаблонов.Почему же мы уходим от старой клиент-серверной парадигмы, и какой подход лучше?
X (formerly Twitter)
swyx 🇸🇬 (@swyx) on X
achieve ambition with intentionality, intensity, & integrity
affils:
- @dxtipshq
- @cognition
- @sveltesociety
- @aidotengineer
- @latentspacepod + @smol_ai
affils:
- @dxtipshq
- @cognition
- @sveltesociety
- @aidotengineer
- @latentspacepod + @smol_ai
👍1
Про User Experience
Smart Clients и Smart Servers имеют ряд плюсов и минусов, и нельзя однозначно выбрать лучший подход, все зависит от того, какое приложение мы разрабатываем.
Smart Clients позволяют делать offline-first приложения с оптимистичными обновлениями, т.е. могут работать без интернета и мгновенно реагировать на любые действия:
- это улучшает интуитивное впечатление пользователя о скорости работы приложения
- но раздувает JS бандлы на клиент, SDK Firebase добавляет 1mb кода в бандл, SDK Amplify около 230kb
Smart Servers напрямую уменьшают количество JS кода, т.к. делают большую часть работы на сервере. Например, при интеграции React Server Components в Facebook, рассказали про уменьшении JS кода на 29% - https://twitter.com/swyx/status/1341151070743982080
- это улучшает скорость первой загрузки сайта, и уменьшает количество JS, загружаемого на клиенте
- но нагрузка по рендерингу каждого кусочка приложения ложится на ваши серверы, и они тратят значительно больше ресурсов
Про Developer Experience
Платформенные SDK - такие платформы как Firebase и AWS Amplify могут предоставлять лучший DX на фронте, т.к. имеют полное понимание вашего бэкенда
Уменьшение бойлерплейта - вместо отдельного написания бэкенд обработчиков, и клиентских запросов к API, мы можем генерировать кастомный API клиент
- Smart Servers радикально уменьшают бойлерплейт, т.к. самостоятельно реализуют механизм синхронизации. В пример приводится LiveView - https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html
Работа offline - платформенные SDK, независимые библиотеки RxDB или Redux Offline, позволяют иметь локальную копию данных, и легко разрешать конфликты обновления этих данных
Уменьшение бойлерплейта для оптимистичных обновлений - имея локальную копию данных, мы просто записываем в нее изменение, и ждем синхронизации с сервером.
В итоге, оба подхода дают значительные улучшения DX.
Про протоколы
Хороший протокол улучшает UX и DX, например:
Типобезопасность - GraphQL проверяет типы в рантайме,
Пропускная способность сети - уменьшение передаваемых данных:
- GraphQL решает проблему запроса избыточных данных (кажется проблема не так важна, если вы не IT гигант)
-
- React Server Components отправляют сериализуемые компоненты
Real-time - возможности совместной работы над одним приложением:
- Для этого отлично подходит UDP, WebRTC и WebSockets (отмечу от себя, что WebSockets работает поверх TCP, и это можно отнести к недостаткам протокола, а WebRTC похоже позволяет выбрать транспорт самостоятельно)
- Также подходящими выглядят такие инструменты как
- UDP в целом похож на отличный фундамент для новых инновационных протоколов (HTTP/3 работает поверх UDP)
В конце статьи автор говорит о том, что заголовок немного кликбейт, и конечно REST протокол отлично подходит для большинства приложений.
Smart Clients и Smart Servers имеют ряд плюсов и минусов, и нельзя однозначно выбрать лучший подход, все зависит от того, какое приложение мы разрабатываем.
Smart Clients позволяют делать offline-first приложения с оптимистичными обновлениями, т.е. могут работать без интернета и мгновенно реагировать на любые действия:
- это улучшает интуитивное впечатление пользователя о скорости работы приложения
- но раздувает JS бандлы на клиент, SDK Firebase добавляет 1mb кода в бандл, SDK Amplify около 230kb
Smart Servers напрямую уменьшают количество JS кода, т.к. делают большую часть работы на сервере. Например, при интеграции React Server Components в Facebook, рассказали про уменьшении JS кода на 29% - https://twitter.com/swyx/status/1341151070743982080
- это улучшает скорость первой загрузки сайта, и уменьшает количество JS, загружаемого на клиенте
- но нагрузка по рендерингу каждого кусочка приложения ложится на ваши серверы, и они тратят значительно больше ресурсов
Про Developer Experience
Платформенные SDK - такие платформы как Firebase и AWS Amplify могут предоставлять лучший DX на фронте, т.к. имеют полное понимание вашего бэкенда
Уменьшение бойлерплейта - вместо отдельного написания бэкенд обработчиков, и клиентских запросов к API, мы можем генерировать кастомный API клиент
- Smart Servers радикально уменьшают бойлерплейт, т.к. самостоятельно реализуют механизм синхронизации. В пример приводится LiveView - https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html
Работа offline - платформенные SDK, независимые библиотеки RxDB или Redux Offline, позволяют иметь локальную копию данных, и легко разрешать конфликты обновления этих данных
Уменьшение бойлерплейта для оптимистичных обновлений - имея локальную копию данных, мы просто записываем в нее изменение, и ждем синхронизации с сервером.
В итоге, оба подхода дают значительные улучшения DX.
Про протоколы
Хороший протокол улучшает UX и DX, например:
Типобезопасность - GraphQL проверяет типы в рантайме,
trpc на этапе компиляцииПропускная способность сети - уменьшение передаваемых данных:
- GraphQL решает проблему запроса избыточных данных (кажется проблема не так важна, если вы не IT гигант)
-
Hotwire буквально передает HTML по проводам- React Server Components отправляют сериализуемые компоненты
Real-time - возможности совместной работы над одним приложением:
- Для этого отлично подходит UDP, WebRTC и WebSockets (отмечу от себя, что WebSockets работает поверх TCP, и это можно отнести к недостаткам протокола, а WebRTC похоже позволяет выбрать транспорт самостоятельно)
- Также подходящими выглядят такие инструменты как
Replicache и Croquet- UDP в целом похож на отличный фундамент для новых инновационных протоколов (HTTP/3 работает поверх UDP)
В конце статьи автор говорит о том, что заголовок немного кликбейт, и конечно REST протокол отлично подходит для большинства приложений.
Мой опыт разработки клиентских приложений поверх традиционной серверной модели достаточно болезненных - в HTML шаблонах (наверное на любом шаблонизаторе) отсутствует типизация, а данные, передаваемые в шаблоны, как правило глобальные, и доступны каждому вложенному темплейту, отладка шаблонов это отдельная сложность.
Наш клиентский код должен подключаться к разметке, которую отдал сервер, и добавлять ей интерактивность - это значит, что даже разделив JS код с логикой компонентов, нам придется грузить все эти компоненты на каждой странице, либо вручную реализовывать lazy loading (эту работу мог за нас сделать бандлер и code splitting)
Также, скорее всего нам придется поддерживать одинаковый набор компонентов на сервере (в шаблонах) и на клиенте (на выбранном фреймворке), а переиспользовать мы сможем максимум стили.
По этой причине, возможность писать универсальные для сервера и клиента компоненты на одном языке и фреймворке для меня выглядит даром богов, и единственным правильным способом делать фронтенд (конечно исключая кейсы, когда нам не нужен серверный рендеринг). Это дает нам гибкость, и переиспользуемый код.
Но, если посмотреть трезвым взглядом, универсальные SSR приложения на React имеют свои недостатки:
- Мы должны писать наш сервер на NodeJS (знаю, что есть решения для запуска JS кода в других языках, и есть
С другой стороны, есть такие решения как
- Это медленно.
Рендеринг и запросы происходят на сервере, что просаживает Time To First Byte.
Из-за ожидания загрузки кода и гидрации на клиенте, просаживается Time To Interactive.
Хорошая статья про виды рендеринга в вэбе от Эдди Османи - https://developers.google.com/web/updates/2019/02/rendering-on-the-web
- Много JavaScript кода (медленно v2).
В первую очередь - это код выбранного фреймворка.
Плюс, даже сложное веб приложение может иметь не так много интерактивных компонентов на странице.
Точечное добавление обработчиков событий и логики обновления DOM вместо отдельного кода на каждый статичный HTML тег позволит сильно уменьшить количество кода на странице.
Определенно, React Server Components частично помогает решить эту проблему, т.к. исключает со стороны клиента лишний код для фетчинга данных.
Наш клиентский код должен подключаться к разметке, которую отдал сервер, и добавлять ей интерактивность - это значит, что даже разделив JS код с логикой компонентов, нам придется грузить все эти компоненты на каждой странице, либо вручную реализовывать lazy loading (эту работу мог за нас сделать бандлер и code splitting)
Также, скорее всего нам придется поддерживать одинаковый набор компонентов на сервере (в шаблонах) и на клиенте (на выбранном фреймворке), а переиспользовать мы сможем максимум стили.
По этой причине, возможность писать универсальные для сервера и клиента компоненты на одном языке и фреймворке для меня выглядит даром богов, и единственным правильным способом делать фронтенд (конечно исключая кейсы, когда нам не нужен серверный рендеринг). Это дает нам гибкость, и переиспользуемый код.
Но, если посмотреть трезвым взглядом, универсальные SSR приложения на React имеют свои недостатки:
- Мы должны писать наш сервер на NodeJS (знаю, что есть решения для запуска JS кода в других языках, и есть
Deno, но кажется все эти решения не оптимальны как минимум с точки зрения производительности) - и это требует экспертизу, которой может не быть у фронтенд разработчиков на текущем проекте.С другой стороны, есть такие решения как
NextJS и tramvai, которые возьмут на себя большую часть сложностей на серверной стороне.- Это медленно.
Рендеринг и запросы происходят на сервере, что просаживает Time To First Byte.
Из-за ожидания загрузки кода и гидрации на клиенте, просаживается Time To Interactive.
Хорошая статья про виды рендеринга в вэбе от Эдди Османи - https://developers.google.com/web/updates/2019/02/rendering-on-the-web
- Много JavaScript кода (медленно v2).
В первую очередь - это код выбранного фреймворка.
Плюс, даже сложное веб приложение может иметь не так много интерактивных компонентов на странице.
Точечное добавление обработчиков событий и логики обновления DOM вместо отдельного кода на каждый статичный HTML тег позволит сильно уменьшить количество кода на странице.
Определенно, React Server Components частично помогает решить эту проблему, т.к. исключает со стороны клиента лишний код для фетчинга данных.
web.dev
Rendering on the Web | Articles | web.dev
Recommendations for implementing logic and rendering in apps.
Для меня очень интересным оказался опыт разработки
Github разработчики ушли от jQuery, и реализуют интерфейсы с помощью нескольких небольших утилит для делегирования событий, и создания качественных web-components - https://github.blog/2018-09-06-removing-jquery-from-github-frontend/ и https://github.blog/2021-05-04-how-we-use-web-components-at-github/
На бэкенде используется Ruby on Rails, и разработчики "прокачали" работу с шаблонами в Rails, добавив инкапсуляцию в шаблоны с помощью ViewComponent - https://github.blog/2020-12-15-encapsulating-ruby-on-rails-views/
Кстати, посмотреть исходный код фронта Github вы можете с легкостью, т.к. sourcemaps в открытом доступе, и достаточно открыть инструменты разработчика, вкладку Sources, и открыть там например файл
Подход Github выглядит шагом назад с точки зрения DX, но кажется ViewComponent сильно упростили фронтам работу с шаблонами на сервере.
С точки зрения UX, приведу в пример сравнение нашей главной странички https://tinkoff.ru и страничку репозитория tramvai - https://github.com/TinkoffCreditSystems/tramvai
Github, где, если я правильно понял концепцию, полноценно используется подход Smart Server.Github разработчики ушли от jQuery, и реализуют интерфейсы с помощью нескольких небольших утилит для делегирования событий, и создания качественных web-components - https://github.blog/2018-09-06-removing-jquery-from-github-frontend/ и https://github.blog/2021-05-04-how-we-use-web-components-at-github/
На бэкенде используется Ruby on Rails, и разработчики "прокачали" работу с шаблонами в Rails, добавив инкапсуляцию в шаблоны с помощью ViewComponent - https://github.blog/2020-12-15-encapsulating-ruby-on-rails-views/
Кстати, посмотреть исходный код фронта Github вы можете с легкостью, т.к. sourcemaps в открытом доступе, и достаточно открыть инструменты разработчика, вкладку Sources, и открыть там например файл
https://github.githubassets.com/assets/app/assets/modules/behaviors.tsПодход Github выглядит шагом назад с точки зрения DX, но кажется ViewComponent сильно упростили фронтам работу с шаблонами на сервере.
С точки зрения UX, приведу в пример сравнение нашей главной странички https://tinkoff.ru и страничку репозитория tramvai - https://github.com/TinkoffCreditSystems/tramvai
The GitHub Blog
Removing jQuery from GitHub.com frontend
We have recently completed a milestone where we were able to drop jQuery as a dependency of the frontend code for GitHub.com. This marks the end of a gradual, years-long…
На tinkoff.ru загружается около 250kb gzip вендор кода и кода приложения, а на github.com - около 200kb.
При этом, страница репозитория выглядит значительно более сложной и интерактивной.
tinkoff.ru разработан на фреймворке
Мы постоянно работаем над улучшением производительности, но у нас остаются достаточно хардкорные оптимизации, которые могут дать заметный профит - lazy hydration каждого блока на странице, отключение hydration для блоков, которые статические по своей природе, или например tree-shaking DI провайдеров во время компиляции приложения (пример такого подхода у Angular - https://coryrylan.com/blog/tree-shakeable-providers-and-services-in-angular)
Сравнение не очень корректное, т.к. я сравниваю разработку с фреймворком, и без фреймворка - но все же, подход Github впечатляет, и исходный код выглядит хорошо.
Есть ощущение, что фронты Github научились готовить традиционную серверную модель работы веб приложения, делают это без боли, и дают хорошие результаты.
Возможно ViewComponent - https://github.com/github/view_component - это не совсем Smart Server, а более традиционный подход, но "на стероидах", позволяющий делать более качественные серверные компоненты.
При этом, страница репозитория выглядит значительно более сложной и интерактивной.
tinkoff.ru разработан на фреймворке
tramvai, и каждый блок на странице является микрофронтендом на React.Мы постоянно работаем над улучшением производительности, но у нас остаются достаточно хардкорные оптимизации, которые могут дать заметный профит - lazy hydration каждого блока на странице, отключение hydration для блоков, которые статические по своей природе, или например tree-shaking DI провайдеров во время компиляции приложения (пример такого подхода у Angular - https://coryrylan.com/blog/tree-shakeable-providers-and-services-in-angular)
Сравнение не очень корректное, т.к. я сравниваю разработку с фреймворком, и без фреймворка - но все же, подход Github впечатляет, и исходный код выглядит хорошо.
Есть ощущение, что фронты Github научились готовить традиционную серверную модель работы веб приложения, делают это без боли, и дают хорошие результаты.
Возможно ViewComponent - https://github.com/github/view_component - это не совсем Smart Server, а более традиционный подход, но "на стероидах", позволяющий делать более качественные серверные компоненты.
Coryrylan
Tree Shakeable Providers and Services in Angular - Angular 17 | 16
Learn how to leverage tree shakable providers in Angular for better application performance.