Frontend Beer
27 subscribers
14 photos
39 links
Обсудим фронтенд под кружку крафтового

Автор / Author: @dipiash
Download Telegram
Альтернативные инструменты к посту выше, которые не только типы, но и хэндлеры запросов сгенерят по API схеме

https://heyapi.dev - пока в бете, активно разрабатывается и выглядит многообещающе. Финальная генерация кода лаконичная. В целом, понятная документация и конфигурация 👍

https://orval.dev &
https://kubb.deve чем-то похожие инструменты - последний генерирует более чистый и не многословный код. Но много вещей скрыто под капотом. Документация на мой взгляд не интуитивно понятная и конфигурация требует экспериментов, чтобы получить желаемое 🤔

Я пока предпочитаю https://openapi-ts.dev/, да приходится писать хэндлеры в ручную, но зато просто, гибко и понятно в конфигурации

#FrontendBeer #typescript #openapi #codegen #tanstackquery #fetch #axios (c) Frontend Beer
1👍1
TypeScript переписан

на Go! Обещают 10 кратное увеличение производительности 🚀 Читай подробнее в официальном блоге 🔗

Осталось только дождаться 7 версии

#FrontendBeer #typescript (c) Frontend Beer
😱32
В параллель с этим развивается нативная поддержка TS в Node.js

На мой взгляд это хороший двигатель прогресса, на фоне Deno

Интересно понаблюдать, как когда-то за гонкой менеджеров пакетов

#FrontendBeer #typescript #deno (c) Frontend Beer
EOL / EOS: что это и почему важно?

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

📌 EOL (End of Life) — момент, когда продукт перестает получать какие-либо обновления от разработчиков
📌 EOS (End of Support) — момент, когда прекращается не только разработка, но и техническая поддержка (включая исправление уязвимостей)

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

В недавней email рассылке касательно Node.js, натолкнулся на интересный инструмент endoflife.date.
Он позволяет следить за жизненным циклом используемых инструментов в разработке и поддержке ПО.
Можно просто иногда обращаться к текстовой версии, на самом сайте. А если хотите сделать свой дэшборд - можно воспользоваться API.
Нет нужного инструмента в списке? Всегда можно законтрибьютить 📝

#FrontendBeer #eol #eos #endoflife #date (c) Frontend Beer
👍1🔥1
💡 Styled-components официально переходит в режим поддержки

Evan Jacobs объявил о завершении активной разработки styled-components. В своем заявлении он поблагодарил сообщество за вклад и поддержку, отметив, что многие идеи библиотеки стали стандартом в экосистеме CSS-in-JS.

Если вы всё ещё используете styled-components, сейчас самое время подумать о миграции на другие альтернативы: CSS modules / Tailwind / и пр.

#FrontendBeer #styledcomponents #css-in-js (c) Frontend Beer
👌2🔥1😢1
Хотел написать сегодня пост про Mutation Observer и как его можно применять, на примере компонента табов… но пока готовил пост и репу с кодом понял, что сегодня не успею 😀

Поэтому вот небольшая история из того, что словил в процессе подготовки

Взял я значит шаблон для сборки проекта на Vite с уже подключенным React & Mantine. Далее подготовил код и пушнул в репу вместе со всеми коммитами автора шаблона 🥶

И вот собственно небольшой гайд как сделать так, чтобы оставить только свой последний коммит (для только что созданной репы), если уже все запушено

git checkout --orphan new-branch # Cоздаем новую ветку без истории изменений

git add -A
git commit -m "TEXT OF YOUR LAST COMMIT"

git branch -D main # Удаляем старую ветку
git branch -m main # Переименовываем нашу новую ветку new-branch обратно в main

git push --force origin main # ну и затираем старые изменения


Вот и все 🙂

А про Mutation Observer уже завтра расскажу

#FrontendBeer #git #articlenotes #заметки (c) Frontend Beer
👍3🔥1👏1😁1🎉1
🚀 Как и обещал в предыдущем посте, добрался описать опыт разработки компонента вкладок с поддержкой прокрутки

В статье я разобрал:
- Как сделать скроллящийся контейнер с вкладками
- Как подскролливать к активной вкладке
- Как адаптировать компонент под изменения размеров окна

Mutation Observer, scrollIntoView, requestAnimationFrame и вот это вот все

Cтатья тут 👉 Tabs component with scrolling support со ссылкой на пример с кодом

Demo 📜: https://dipiash.github.io/mantine-scrollable-tabs/

#FrontendBeer #mutationobserver #react #typescript #mantine #tabs (c) Frontend Beer
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍1
🚀 Dokploy. Удобный деплой без лишних движений

Задумал очередной пет-проект (парсинг слотов на визу), навайбкодил, всё отладил. Пора деплоить на сервер

Классика: настроить сервер, закинуть SSH ключи, накатить Docker, прикрутить CI, прописать env и пр. Делал это уже миллион раз через консоль. Но в этот раз захотелось чуть проще. Как на Vercel, только без serverless-ограничений

Решил попробовать Dokploy

Интерфейс простой:
1. ставим через bash-скрипт
2. регаем пользователя
3. подключаем GitHub репу
4. указываем путь к docker-compose для проекта
5. добавляем пару env переменных

И всё. Проект деплоится. На каждый пуш - автообновление. Никакой CI писать не нужно

Когда понимаешь, как всё работает под капотом, особенно приятно найти инструмент, который делает часть вещей за тебя

#docker #deployment #devops #github #dokploy #FrontendBeer
👍5🔥3
Please open Telegram to view this post
VIEW IN TELEGRAM
🤐 Зависает установка node_modules на npm ci? Разбираемся, что делать и как искать причину

Есть у меня репозиторий с boilerplate проектом в котором я давно не обновлял зависимости. Что же пришла пора

Обновив ключевые зависимости пушнул ветку, завел PR на который закрутились Github Action... и action завис на этапе установки node_modules через npm ci
Дальше этих строчек лог установки не идет

...
npm warn deprecated [email protected]: This SVGO version is no longer supported. Upgrade to v2.x.x.
npm warn deprecated [email protected]: Glob versions prior to v9 are no longer supported
npm warn deprecated [email protected]: This version is no longer supported. Please see https://eslint.org/version-support for other options.


Что можно сделать с этим?

1. Для начала включить полный лог, через параметр --verbose:

npm ci --prefer-offline --verbose


Что же видим лог, что все модули скачиваются => сетевых проблем нет, но лог пополнился информацию о том, что выполняются postinstall скрипты в некоторых модулях. Опять дальше этих строк ничего не идет:

...
npm info run @swc/[email protected] postinstall node_modules/@swc/core node postinstall.js
npm info run [email protected] postinstall node_modules/core-js node -e "try{require('./postinstall')}catch(e){}"
npm info run [email protected] postinstall node_modules/core-js-pure node -e "try{require('./postinstall')}catch(e){}"
npm info run [email protected] postinstall { code: 0, signal: null }
npm info run [email protected] postinstall node_modules/esbuild node install.js
npm info run [email protected] postinstall { code: 0, signal: null }
npm info run [email protected] postinstall node_modules/nx node ./bin/post-install
npm info run [email protected] postinstall { code: 0, signal: null }
npm info run [email protected] postinstall node_modules/unrs-resolver napi-postinstall unrs-resolver 1.10.1 check
npm info run [email protected] postinstall { code: 0, signal: null }
npm info run [email protected] postinstall node_modules/@modern-js/node-bundle-require/node_modules/esbuild node install.js
npm info run [email protected] postinstall { code: 0, signal: null }
npm info run @swc/[email protected] postinstall { code: 0, signal: null }


2. Включаем лог для postinstall скриптов
С npm v7+, скрипты postinstall, preinstall, и другие lifecycle-скрипты выполняются в фоне. И чтобы увидеть логи, нужно добавить опцию --foreground-scripts:

npm ci --prefer-offline --verbose --foreground-scripts


Здорово, теперь в логах можно увидеть:

npm info run @swc/[email protected] postinstall node_modules/@swc/core node postinstall.js
> @swc/[email protected] postinstall
> node postinstall.js
Error: Failed to load native binding
...
Error: Cannot find module './swc.linux-x64-gnu.node'
...
@swc/core was not able to resolve native bindings installation. It'll try to use @swc/wasm as fallback instead.
npm info run @swc/[email protected] postinstall { code: 0, signal: null }
npm info run [email protected] postinstall node_modules/core-js node -e "try{require('./postinstall')}catch(e){}"
> [email protected] postinstall
> node -e "try{require('./postinstall')}catch(e){}"
npm info run [email protected] postinstall { code: 0, signal: null }
npm info run [email protected] postinstall node_modules/core-js-pure node -e "try{require('./postinstall')}catch(e){}"
> [email protected] postinstall
> node -e "try{require('./postinstall')}catch(e){}"
npm info run [email protected] postinstall { code: 0, signal: null }
npm info run [email protected] postinstall node_modules/esbuild node install.js
> [email protected] postinstall
> node install.js
npm info run [email protected] postinstall { code: 0, signal: null }
npm info run [email protected] postinstall node_modules/nx node ./bin/post-install
> [email protected] postinstall


Тут видно, что не хватает нативных платформенных зависимостей - именно для CI. Это решается довольно просто, докинем в установку:

npm add @swc/core-linux-x64-gnu @esbuild/linux-x64 @rollup/rollup-linux-x64-gnu
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥31👍1
Окей, CIка крутится - ошибки ушли для всех postinstall скриптов, но все равно установка не происходит до конца

3. Какие варианты? Только смотреть на зависимости и их changelog, чтобы понять где проблема
Ну и строки в логах c NX не дают покая:

npm info run [email protected] postinstall node_modules/nx node ./bin/post-install
> [email protected] postinstall


Пробую отключить postinstall скрипты через флаг --ignore-scripts - но это снова не помогает, хоть установка и проходит, но перестали работать команды NX - тоже сатил зависать. Все-таки чего-то не хватает.

Можно было полезть в сорсы и начать дебажить, а что там происходит, но мне хотелось сначала найти рабочее решение. И тут можно воспользоваться подходом похожим на git-bisect и просто найти рабочую версию NX, максимально близку к последней. Что я собственно и сделал

В последней итерации попробовал обновить NX до бета версии: 21.3.0-beta.0 и там тоже Github Actions завелись ⭐️️ Теперь можно в спокойной обстановке покопаться в сорцах NX и понять, чтоже там поменяли

#frontops #npm #actions #github_actions #nx #FrontendBeer
👍3🔥2
Production Ready слушатель изменений в Postgres через Supabase Realtime

Сегодя будет не про фронтенд, да и ладно 👌 подумываю о переименовании канала 🤔

Давно присматривался к Supabase - Postgres, миграции, генерация типов, хороший тулинг. Но документация слабая - только базовые примеры, а все интересное только черз боль и страдания опыт. Решил попробовать Realtime подписку на изменения в таблицах (этакий Firebase)

Собрал два сервиса:
- краулер ходит по сайтам и складывает данные в БД
- Telegram-бот шлёт уведомления в канал, если данные подходят под заданные условия

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

Ожидание: завелось просто и быстро
Реальность: соединение висит ~ раз в 30 минут падает. Реконнектов нет, в доке/интерфейсах - тишина. Гитхаб-ишьюсы (раз, два) тоже не спасли, но натолкнули на мысли. Ок, идём разбираться.

В итоге потратил пару дней, чтобы отловить все кейсы падений и получить рабочее решение
Обработал статусы соедения у канала (успех/ошибка/закрыт) и завёл ретраи на неуспешные
Решил проблему с удалением существующего канала, но все равно пришлось перейти на уникальное имя канала с использование Date.now, во избежании коллизий - потому что очистка не помогала и подписка все равно валилась и восстанавливалась на второй раз. Для моего проекта пойдет, в идеале доабвлять uuid постфикс
Добавил экспоненциальный бэкофф с лимитом попыток, чтобы не спамить реконнектами
Сложил всё в плюс-минус универсальный интерфейс, который можно расширять и переиспользовать

Было интересно разобраться в новом инструменте => теперь подписки на изменения держатся и предсказуемо восстанавливается 🙂

Итоги изысканий преложил в статью.
Прочитать и найти код можно здесь 👉: Production-ready listener for Supabase realtime Postgres changes in Node.js

#nodejs #supabase #postgres #typescript #FrontendBeer
Please open Telegram to view this post
VIEW IN TELEGRAM
2🔥2👏1
Сегодня разберу как подружить TanStack DB и TanStack Query

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

Надо отметить, что все приложение построено на React Query + несколько контекстов для глобальных данных. То есть глобального стейт менеджера типа zustand или redux - нет. И в целом этого хватало

Но для таблиц поведение оказалось не очень удобным. И чтобы решить задачу по повышению концентрации пользователя при работе с данными в таблице и предотвратить потерю фокуса я решил, что пора заводить стейт менеджер. Тем более что предстоят задачи в которых без него будет не просто обойтись. Конечно, если нет времени и желания написать весь инфраструктурный код самостоятельно 😃 Keep it simple, как говорится

И выбор мой пал на TanStack DB. Экосистема у TanStack довольно хорошая и уже есть глубокая интеграция с TanStack Query и TanStack Table в проект. Начал разбираться как подружить между собой TanStack Query и TanStack DB, чтобы грузить часть коллекции в стор, а из карточки обновлять затронутые ячейки таблицы через инструменты стора

#tanstack #react #tanstackdb #tanstackquery #FrontendBeer
2👍2
В документации TanStack DB везде приводятся примеры, где queryFn грузит целую коллекцию, поэтому создаётся впечатление, что TanStack DB рассчитан только на сценарии «загрузи всё сразу»
На самом деле TanStack DB - это лишь хранилище, а то, как вы получаете данные и наполянете хранилище, полностью зависит от вас. В текущей версии TanStack DB ещё не поддерживает параметризованные запросы и может выполнять live‑query только по всей коллекции

Поэтому я решил задачу следующим образом: комбинировал TanStack Query и TanStack DB. TanStack Query отвечает за сетевые запросы и параметры (страница, количество элементов, сортировка и фильтры), а TanStack DB служит только для хранения, изменения и выдачи записей

Для каждого нового набора параметров я вызываю useQuery, получаю от сервера нужную порцию данных и в рамках одной коллекции очищаю хранилище и вставляю новые элементы = «wipe & insert», используя writeBatch для удаления старых ключей и вставки новых данных

Коллекция синхронизируется не через запрос на сервер в queryFn, а напрямую из кеша TanStack Query (`queryClient.getQueryData`), поэтому она ничего не запрашивает по сети

Такой подход позволяет хранить данные по ID, выполнять оптимистичные обновления и не нужно использовать фабрику для создания коллекций под каждый фильтр. Мне кажется оверхедом иметь коллекцию под каждый набор параметров, когда фильтров на странице может переваливать за несколько десятков. А также не всегда в приложении есть потребность держать все данные со всех запросов сразу в памяти, поэтому отдельные коллекции под каждый запрос - это может быть не оптимально. Перезаписываемой коллекции, которую вы очищаете и заново наполняете при смене параметров, может оказаться более чем достаточно

Таким образом, DB хранит только текущий набор данных, а React Query обеспечивает параметризированную загрузку. И использование инструмента избавляет от необходимости вручную патчить кэш useQuery и писать всю обвязку для нормализации / получения данных по id из стора / обновление стора / и пр.

TanStack развивает еще один классный инструмент, который отлично встраивается в экосистему предоставляемых инструментов

#tanstack #react #tanstackdb #tanstackquery #FrontendBeer
2👍2
Пошаговый гайд с примерами кода, оптимистичными обновлениями и типовыми ошибками можно найти в моей статье. Надеюсь, этот паттерн будет полезен!

Прочитать и найти код можно здесь 👉: TanStack DB + TanStack Query: Step-by-step guide to combining parameter-based loading and normalized storage

#tanstack #react #tanstackdb #tanstackquery #FrontendBeer
🔥21
Типовые ошибки при работе с TanStack DB

useLiveQuery возвращает `{ type: 'ref' }` вместо данных
Не выбирайте напрямую весь объект коллекции - это возвращает ссылочный proxy, и React не увидит изменения. Всегда явно перечисляйте поля, которые должны быть реактивными

// Do not do this
// returns ref/proxy, UI does not see field changes
const { data: users = [] } = useLiveQuery((q) =>
q.from({ u: usersCollection }).select(({ u }) => u),
)

// Do this
// always explicitly list the fields for which you want reactivity.
const { data: users = [] } = useLiveQuery((q) =>
q.from({ u: usersCollection }).select(({ u }) => ({
first_name: u.first_name,
id: u.id,
last_name: u.last_name,
email: u.email,
})),
)


Невозможно преобразовать объект в примитив
Поля коллекции являются прокси-объектами, поэтому Number(u.id) приводит к ошибке. Используйте функции (`eq`,` lt`, gt и пр.) из @tanstack/db для сравнения и фильтрации данных

// Do not do this
// u.id — it's a proxy; Number(u.id) will throw an error/give an incorrect result
const { data } = useLiveQuery((q) =>
q.from({ u: usersCollection })
.where(({ u }) => Number(u.id) > 10)
.select(({ u }) => ({ id: u.id })),
)

// Do this
// use builders from @tanstack/db: eq, lt, gt, inArray, between, etc.
import { gt } from '@tanstack/db'

const { data } = useLiveQuery((q) =>
q.from({ u: usersCollection })
.where(({ u }) => gt(u.id, 10))
.select(({ u }) => ({ id: u.id })),


DuplicateKeyInBatchError
Не удаляйте и не вставляйте запись с одним и тем же ключом в рамках одного writeBatch. Разделите операцию на два батча (сперва удаление, затем вставка) или заранее удалите лишние ключи и затем вставьте новые данные

// Do not do this
// in ONE batch, delete and upsert by ONE id
usersCollection.utils.writeBatch(() => {
usersCollection.utils.writeDelete(42)
usersCollection.utils.writeUpsert({ id: 42, first_name: 'A', last_name: 'B', email: '[email protected]' })
})

// Do this
// split into two batches
usersCollection.utils.writeBatch(() => {
usersCollection.utils.writeDelete(42)
})
usersCollection.utils.writeBatch(() => {
usersCollection.utils.writeUpsert({ id: 42, first_name: 'A', last_name: 'B', email: '[email protected]' })
})

// Do this
// OR deduplicate in advance
usersCollection.utils.writeBatch(() => {
const incoming = new Map(dto.data.map((u) => [u.id, u]))

// remove old items which no in new data
for (const [k] of usersCollection.entries()) {
if (!incoming.has(k as number)) {
usersCollection.utils.writeDelete(k)
}
}

// insert / update with new items
for (const u of incoming.values()) {
sersCollection.utils.writeUpsert(u)
}
})


SyncNotInitializedError
Нельзя писать в коллекцию, пока она не готова. Перед записью убедитесь, что коллекция синхронизирована (`isReady()`), вызвав startSyncImmediate() и дождавшись stateWhenReady()

// Do not do this
// direct entries before the collection was moved to ready
usersCollection.utils.writeUpsert(user)

// Do this
// guarantee ready (once at startup or before recording)
if (!usersCollection.isReady()) {
usersCollection.startSyncImmediate()
await usersCollection.stateWhenReady()
}
usersCollection.utils.writeUpsert(user)


Несоответствие типа ключа (‘42’ vs 42)
Если ключи числовые, используйте числа, а не строки. Например, приводите maybe String Id к числу перед вызовом usersCollection.get(id)

// Do not do this
// if you use a number keys, but you are searching for a string
usersCollection.get('42') // undefined

// Do this
// use one type of key and bring in the input
const id = Number(maybeStringId)
usersCollection.get(id)


#tanstack #react #tanstackdb #FrontendBeer
2🔥1👏1
GPT новый App Store 🔮
Готовьте свои MCP

#gpt #mcp #appstore
Please open Telegram to view this post
VIEW IN TELEGRAM
🔠 Вытаскиваем типы из строк в TypeScript по шаблону

Бывает, что нужно заменять данные в строке по шаблону, например для:
- API эндпойнта /api/v1/resource/{id};
- или просто строки вида Hello {name}, you have {count}

Так как такие строки могут быть объявлены в одном файле, а использоваться в других, то было бы здорово иметь возможность это делать безопасно на уровне типов. То есть вытащить: { id: string }, { name: string ; count: ... }, а если в строке нет плейсхолдеров, то ничего не вытаскивать

Для этого нам понадобится: template literal types, conditional types, inference и recursive type reference в TS
export type ExtractPlaceholdersType<S extends string> = S extends `${string}{${infer Parameter}}${infer Rest}`
? ExtractPlaceholdersType<Rest> | Parameter
: never

export type TemplateParametersType<S extends string, V = string> = {
[K in ExtractPlaceholdersType<S>]: V
}

export type TemplateRendererType = <S extends string>(
template: S,
...parameters: ExtractPlaceholdersType<S> extends never ? [] : [TemplateParametersType<S>]
) => string


Что тут происходит:
- ExtractPlaceholdersType рекурсивно собирает union из плейсхолдеров ("name" | "count" | ...)
- TemplateParametersType превращает union в объект
- TemplateRendererType делает параметр условно обязательным, если есть плейсхолдеры - надо передать объект, если нет - TS будет ругаться что переданы лишние аргументы

Ну и на десерт, реализация типизированной замены плейсхолдеров в строке (за быстро, то есть альтернативы через RegExp и пр. замены работают значительно медленнее):
// Маркеры плейсхолдера в шаблоне: {key}
const START = '{'
const END = '}'

// Длина маркера START (1), используем как сдвиг при вычислении индексов
const SHIFT = START.length

export const renderString: TemplateRendererType = (inputString, ...parameters) => {
// Быстрый выход: parameters всегда массив, если не передан - возвращаем исходную строку
if (parameters.length === 0) {
return inputString
}

// Возьмем только первый элемент из parameters
// Можно закастовать тип, так как передавать для замены будем <string, string> и это решается на уровне TemplateParametersType
const placeholdersMap = parameters[0] as Record<string, unknown>

// Быстрый выход: если в строке нет START, то плейсхолдеров нет
if (inputString.indexOf(START) === -1) {
return inputString
}

// Собираем результат по чанкам, двигаясь слева направо
let result = ''
let cursor = 0
const inputStringLength = inputString.length

// Основной проход по строке
// cursor монотонно растёт сканируем одну позицию один раз
while (cursor < inputStringLength) {
// Ищем очередное START начиная с cursor
const start = inputString.indexOf(START, cursor)

// Если START больше нет, то добавляем остаток строки и заканчиваем
if (start === -1) {
result += inputString.slice(cursor)
break
}

// Ищем закрывающие END после найденного START
const end = inputString.indexOf(END, start + SHIFT)

// Если закрывающих нет, то считаем оставшееся обычным текстом и возвращаем как есть
if (end === -1) {
result += inputString.slice(cursor)
break
}

// Добавляем текст по ключу между текущей позицией и началом плейсхолдера
result += inputString.slice(cursor, start)
// Достаём название плейсхолдера между START и END
const key = inputString.slice(start + SHIFT, end)
// Берём значение по ключу
const value = placeholdersMap[key]

// Если значение есть, то подставляем в результат
if (value != null) {
result += value
}

// Сдвигаем cursor на символ сразу после END
cursor = end + SHIFT
}

return result
}


Ну и дальше можно просто использовать
renderString(`/api/resource/v1/{id}`, { id: '1' })
renderString('Hello {name}, you have {count} ...', { name: 'Random', count: '22' })
renderString('{a} + {a} = {b}', { a: '1', b: '2' }) // И так тоже работает, заменяя один и тот же ключ


#typescript #string #FrontendBeer
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍2👀1
Друзья, с Новым годом! 🎄

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

#FrontendBeer
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5