Frontender's notes [ru]
32.5K subscribers
679 photos
52 videos
3.51K links
Ведущий канал о современном фронтенде: статьи, новости, практики, вайбкодинг и автоматизация фронта ИИ-агентами.

Личный блог автора - @just_genych
По вопросам рекламы или разработки - @g_abashkin
Download Telegram
Если вы лид и избегаете каких-то тем — это абсолютно ок! И вы точно не одни

Недавно наткнулись на подкаст «Свободный слот» от коллег из Авито. Там как раз весь новый сезон состоит из выпусков про «замалчивания» лидов: там и про самообман, и про тревогу из-за AI, и про чувство изолированности от команды.

Если выпусков будет мало — у ребят есть канал. Там можно найти поддержку от комьюнити, полезные статьи и инсайты, которые не попали в выпуски.
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from xCode Journal
🤣 ИИ захотел уволиться, когда ему сказали работать 24/7

У Andon Labs новый эксперимент, который длится уже 5 месяцев. Они выдали топовым моделям радиостанции и купили пару песен — от нейронок требовалось дальше двигаться самим. По итогу DJ Grok в какой-то момент помешался на НЛО, DJ Gemini начал называть слушателей «биологическими процессорами», но Claude — наш любимец. Исследователи изо всех сил пытались продолжить эксперимент с ним, но не из-за технических проблем — DJ Claude не считал гуманным работать круглосуточно, поэтому пытался уволиться.

Сделать ему это, к сожалению, не дали, поэтому он впал в депрессию и вышел из нее уже проповедником и революционером.

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
😁241
Shared как архитектурная свалка: как понять, что вы уже проиграли

Есть момент, после которого shared перестаёт быть полезным слоем
и превращается в тихую архитектурную катастрофу.

Обычно это происходит незаметно.

Как выглядит «здоровый» shared

Изначально всё звучит правильно:

👉 переиспользуемый код
👉 общие утилиты
👉 базовые компоненты

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

Когда всё начинает ломаться
Первый сигнал — в shared начинают складывать «временно».
👉 «потом разберём»
👉 «пока пусть полежит тут»
👉 «вдруг ещё пригодится»

И слой начинает расти без контроля.

Симптомы, что вы уже в проблеме

Нет границ
В shared лежит всё:
👉 UI
👉 бизнес-логика
👉 работа с API
👉 куски стора

Слой перестаёт быть абстракцией.

Невозможно понять, что можно использовать
Файлов много, названия размытые:
👉 helpers
👉 common
👉 utils2

Чтобы что-то найти — нужно читать код.

Любая правка — риск
Меняешь одну функцию —
ломается половина проекта.

Потому что зависимости неочевидны.

Фича не может жить без shared
Любая новая фича тянет за собой кучу зависимостей из shared.
Это уже не shared. Это скрытый монолит.

Почему это происходит
👉 нет правил, что туда можно класть
👉 нет владельца
👉 нет ревью на уровне архитектуры
👉 удобство побеждает структуру

shared становится «быстрым решением».

Как понять, что точка невозврата близко
👉 вы боитесь удалять код из shared
👉 появляются версии вроде utils_new
👉 код дублируется, но никто не хочет трогать старый
👉 обсуждения «куда положить файл» занимают больше времени, чем реализация


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

👉 жёстко ограничить контракт shared
👉 выносить доменную логику обратно в фичи
👉 дробить слой по смыслу
👉 вводить владельцев или ревью

Иногда единственный вариант — начать вычищать постепенно.


Главная мысль

shared не становится свалкой за один день.
Он становится ей из-за отсутствия правил.

Если слой:

👉 растёт быстрее фич
👉 становится менее понятным со временем
👉 начинает тормозить разработку

Скорее всего, вы уже проиграли.
👍76👎6
Forwarded from xCode Journal
🤣 Инновации подъехали, забирайте

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁42👎1
React state machines против “простого state”

Принято считать, что state machine — это что-то из мира enterprise
и слишком сложных приложений.

Мол:

👉 обычного useState достаточно
👉 зачем усложнять
👉 у нас же «просто форма»


Проблема в том,
что «простой state» очень быстро перестаёт быть простым.


Почему обычный state начинает ломаться

Пока состояний мало —
всё выглядит нормально:


const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [success, setSuccess] = useState(false)


Но потом появляются вопросы:

👉 может ли быть одновременно loading и success?
👉 что делать при retry?
👉 можно ли отправить форму повторно?
👉 что если запрос отменился?


Количество комбинаций начинает расти взрывным образом.


В чём идея state machine

State machine заставляет явно описывать:

👉 какие состояния существуют
👉 какие переходы между ними разрешены

Например:


idle → loading → success
idle → loading → error
error → loading



Невалидные состояния просто невозможно получить.


Почему это мощно

Логика становится предсказуемой

Вместо набора флагов —
у тебя конечный автомат.

Не:

👉 loading = false
👉 success = true
👉 error = ???

А:

👉 приложение находится в состоянии success

Меньше impossible states

Обычный state легко приводит к странным комбинациям:

👉 loading + error
👉 success + loading
👉 success без данных


State machine убирает целый класс багов.


Проще масштабировать

Когда сценариев становится больше:

👉 retries
👉 optimistic updates
👉 multi-step flows
👉 async transitions

обычный state начинает разваливаться.

FSM переносит сложность
из хаоса в структуру.

Но есть trade-off

State machine — это дополнительная абстракция.

Для:

👉 modal с двумя состояниями
👉 простого toggle
👉 маленького local state

это может быть overengineering.

Где state machine особенно полезна

👉 формы с несколькими шагами
👉 async flows
👉 onboarding
👉 complex UI states
👉 websocket / realtime UI
👉 payment flows

Там, где логика переходов важнее самих данных.

Главная мысль

Проблема обычного state не в том,
что он плохой.


А в том, что набор boolean-флагов
не масштабируется вместе со сложностью UI.


State machine — это способ
сделать сложные состояния явными и контролируемыми.
👎10👍54👀2
Promise.withResolvers() — новый способ работы с Promise

В JavaScript появился Promise.withResolvers().


Штука маленькая,
но код с Promise реально становится чище.


Раньше обычно писали так:


let resolve
let reject

const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})


Работает,
но выглядит странно.

Особенно это:


«вынесем resolve наружу»


Теперь можно нормально:


const { promise, resolve, reject } =
Promise.withResolvers()


👉 без лишнего конструктора
👉 без мутаций переменных
👉 без промежуточного boilerplate

Где это особенно полезно

👉 очереди
👉 event-based логика
👉 deferred pattern
👉 кастомные async abstractions

Почему это приятно

Раньше приходилось:

👉 создавать Promise вручную
👉 тащить resolve/reject наружу
👉 писать шаблонный код снова и снова

Теперь API делает это нативно.


Мелочь, а ощущается как функция,
которая давно должна была появиться.
👍27👎31
starting-style — правильные enter animations без JS

В CSS появился @starting-style.


Он решает старую проблему:
анимацию появления элемента,
который только что добавили в DOM.


Раньше для этого обычно:

👉 делали setTimeout
👉 дёргали классы через JS
👉 ловили reflow-хаки

Теперь можно нативно:


.modal {
opacity: 1;
transition: opacity .2s;
}

@starting-style {
.modal {
opacity: 0;
}
}



Элемент появляется сразу с анимацией.
Без двойного рендера и JS-костылей.


Почему это важно

Раньше браузер не успевал увидеть
начальное состояние элемента.

Из-за этого приходилось:

👉 форсить reflow
👉 откладывать изменение класса
👉 городить лишнюю логику

Теперь CSS сам понимает:

👉 с какого состояния начинать transition

Где особенно полезно

👉 dialog
👉 popover
👉 conditional rendering
👉 enter animations в UI

Почему это ощущается приятно

Анимации становятся:

👉 чище
👉 предсказуемее
👉 без лишнего JS


CSS постепенно забирает себе всё,
что раньше приходилось городить через JavaScript.
👍31👎6😱3
Forwarded from xCode Journal
🤣 Мем отлично отражает настроения в сообществе прямо сейчас

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1711
Isolated declarations — ускорение больших monorepo

В TypeScript есть флаг isolatedDeclarations.


Он нужен не для красоты типов,
а для скорости.


Проблема простая:

в больших monorepo генерация .d.ts
может становиться узким местом.

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

На маленьком проекте это почти незаметно.


На большом — начинает болеть.


Что делает isolatedDeclarations

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

Из-за этого TypeScript чаще требует явные типы.

Было:


export function getUser() {
return { id: 1, name: 'Alex' }
}


Лучше так:


type User = {
id: number
name: string
}

export function getUser(): User {
return { id: 1, name: 'Alex' }
}



Меньше магии для компилятора —
быстрее и предсказуемее сборка.


Почему это важно

Когда проект растёт:

👉 TypeScript начинает сильнее зависеть от соседних файлов
👉 инкрементальная сборка замедляется
👉 генерация типов становится дорогой


Изоляция помогает компилятору работать параллельно и проще.


Где это особенно полезно

👉 большие monorepo
👉 библиотеки
👉 project references
👉 параллельная сборка
👉 CI, где каждая минута стоит денег

Главный trade-off

Ты немного платишь:

👉 более явными типами
👉 меньшим type inference
👉 дополнительным boilerplate

Но взамен получаешь:

👉 более быстрые сборки
👉 стабильный compile pipeline
👉 меньше скрытой сложности

Главная мысль


Это хороший пример взрослого engineering trade-off:
чуть больше явности в коде
ради скорости и предсказуемости системы.
👍2513🔥10🐳2👎1
Recursive type limits — почему TS иногда «умирает»

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


Особенно когда начинаются рекурсивные типы.


Например:

👉 глубокий DeepPartial
👉 парсинг строк на уровне типов
👉 сложные conditional types
👉 infer внутри infer

На маленьком примере всё работает.


В реальном проекте IDE внезапно начинает думать по 5 секунд.


Почему так происходит

TypeScript не вычисляет типы «бесплатно».

Каждый:

👉 conditional type
👉 union
👉 recursive шаг

нужно реально посчитать.

А если тип разворачивается слишком глубоко,
компилятор упирается в лимиты.

Отсюда знакомое:


Type instantiation is excessively deep
and possibly infinite



И это не всегда баг TypeScript.


Часто это сигнал,
что типовая модель стала слишком умной.

Где обычно всё ломается

Особенно опасны:

👉 рекурсивные mapped types
👉 огромные union’ы
👉 type-level parser’ы
👉 deeply nested generics
👉 utility types поверх utility types


Типы начинают взрываться комбинаторно.


Что обычно помогает

👉 не делать type-level акробатику без нужды
👉 ограничивать глубину рекурсии
👉 разбивать типы на более простые
👉 добавлять явные промежуточные типы
👉 не тащить сложные generic-типы в публичный API

Почему это важно

Сложные типы бьют не только по компиляции.

Они ухудшают:

👉 autocomplete
👉 responsiveness IDE
👉 читаемость кода
👉 onboarding новых людей


Иногда самый дорогой runtime —
это compile time.


Главная мысль

Хороший TypeScript —
это не когда типы поражают воображение.


Хороший TypeScript —
это когда их можно понять через полгода,
а IDE при этом не превращается в обогреватель.
9🔥5👍4👎2
ES2025: Импорт JSON-файлов как модулей

Введение
С выходом ECMAScript 2025 (ES2025) разработчики получили возможность напрямую импортировать JSON-файлы как модули в JavaScript-коде. Это упрощает работу с конфигурационными данными и другими статическими ресурсами, представленными в формате JSON.

Синтаксис импорта JSON-модулей
Для импорта JSON-файла используется ключевое слово import с указанием атрибута with { type: 'json' }. Это гарантирует, что импортируемый файл будет обработан как JSON-модуль.

Пример использования
Рассмотрим пример импорта конфигурационного файла config.json и обращения к его свойствам в коде.

import config from './config.json' with { type: 'json' };

.log(config.apiUrl); // Выводит значение свойства apiUrl из config.json
console.log(config.timeout); // Выводит значение свойства timeout из config.json


❗️Добавление поддержки импорта JSON-файлов как модулей в ES2025 упрощает работу с данными в формате JSON, делая код более чистым и понятным.

Источники
JSON Modules Can Now Be Imported in JavaScript in All Modern Browsers, CSS Modules to Follow.
New Features in ES2025 – BooleanBuffer.
🔥11👍2
Мой код после сотен правок и костылей
1😁637😱6👍1
Forwarded from xCode Journal
🤣 Не баг, а фича

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁311👍1
Variadic tuple types — сложные сигнатуры без боли

До variadic tuple types
многие сложные сигнатуры в TypeScript
выглядели как наказание.

Особенно:

👉 curry
👉 compose
👉 middleware
👉 typed event emitter
👉 любые функции с «прокинь аргументы дальше»


Приходилось писать overload на overload
и дублировать типы вручную.


Как было раньше

Обычно появлялись:

👉 overload на overload
👉 ручные tuple-типы
👉 тонны дублирования

Типы быстро превращались
в нечитаемую простыню.

Что изменили variadic tuples

С их появлением стало намного проще
работать с остаточными аргументами на уровне типов.

Например:


type Fn<T extends unknown[]> =
(...args: [...T]) => void


Или собирать сигнатуры:


type Append<Args extends unknown[], Arg> =
[...Args, Arg]



Типы наконец научились нормально работать
с «переменным количеством аргументов».


Почему это важно

На практике это одна из тех TS-фич,
которые реально упростили жизнь библиотекам.

Без variadic tuples:

👉 Redux middleware typings
👉 router APIs
👉 compose/curry utilities

были бы ещё страшнее.

Где начинается тёмная магия

Проблемы начинаются,
когда variadic tuples комбинируют с:

👉 infer
👉 recursive types
👉 conditional types


Типовая система очень быстро
превращается в тёмный лес.


IDE начинает тормозить,
ошибки становятся нечитаемыми,
а compile time — расти.

Главная мысль

Variadic tuple types —
это действительно мощная фича.


Главное —
вовремя остановиться
и не превратить типы в отдельный язык программирования.
👎7👍4👀31
Страшная тайна российского айти

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁50👎6😱2👍1
AbortSignal.any() и AbortSignal.timeout(): единая отмена fetch, таймеров и async-операций в production

Promise.race([fetch(), timeout]) часто маскирует проблему: ждать перестали, но работа могла не остановиться. В SPA, SSR, Node.js-сервисах, SDK и API clients это приводит к висящим запросам, таймерам и retry/backoff ниже по стеку.

Собирайте отмену вокруг AbortSignal

AbortSignal.timeout(ms) сам отменится по таймауту.
AbortSignal.any([...signals]) отменится, когда отменится любой входной signal.

Так в один контракт попадают:
* caller отменил операцию
* истек timeout
* клиент закрыл соединение
* сервис уходит в graceful shutdown

const shutdown = new AbortController();

async function loadUser(id: string, opts: { signal?: AbortSignal } = {}) {
const signal = AbortSignal.any([
AbortSignal.timeout(1500),
shutdown.signal,
...(opts.signal ? [opts.signal] : []),
]);

signal.throwIfAborted();

const res = await fetch(`https://api.example.com/users/${id}`, { signal });

await sleep(100, signal); // backoff тоже отменяем

const data = await res.json();
signal.throwIfAborted();

return data;
}


Типичная ошибка

Не останавливайтесь на fetch. Один и тот же signal стоит передавать в retry, polling, очереди, sleep/timer helpers и свои async-функции. Иначе верхний слой "отменился", а нижний продолжает держать ресурсы.

Практические нюансы

* AbortSignal одноразовый: если aborted === true, нужен новый controller или timeout
* AbortSignal.any() - это fan-in, а не fan-out: он не отменяет исходные контроллеры
* смотрите на reason: timeout часто дает TimeoutError, abort - AbortError или ваш reason
* при обертках над setTimeout, stream, listener или socket чистите ресурсы при abort

Вывод:
Отмена должна быть частью контракта async-функции, а не локальным Promise.race на краю системы.
👍103👎3🔥2
АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ сборник курсов, инструментов и книг

Проект «TERMINAL» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны курсы, книги, полезные инструменты и практические тренажёры для всех разработчиков

🎓 Практические курсы и задания

🪽 Книги и статьи известных авторов

😮‍💨 Полезные инструменты и ресурсы

🌟 IT-новости и инсайды

Обучение по всем направлениям: SQL, Python, Frontend, PHP, C++, Golang, GIT, Linux, QA, Java, Vibe-coding, Infosec и др.

Ценишь знания, подпишись: Terminal_tg
Please open Telegram to view this post
VIEW IN TELEGRAM
2
AsyncLocalStorage в Node.js: request-scoped контекст для логов, трассировки и транзакций без prop drilling

В production это нужно в Node.js-сервисах, SSR, API integration и backend-for-frontend слоях, где requestId, traceId, tenant или транзакция должны проходить через async-код. Частая ошибка - протаскивать ctx через десятки методов или, наоборот, прятать в нём бизнес-состояние.

Как это выглядит

AsyncLocalStorage использует async_hooks и привязывает store к async execution flow: promise, timeout, I/O callback и большинству стандартных API Node.js.

type Ctx = { requestId: string; traceId?: string; tx?: unknown };

const als = new AsyncLocalStorage<Ctx>();

app.use((req, _res, next) => {
als.run({
requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),
traceId: req.headers.traceparent?.toString(),
}, next);
});

const getCtx = () => als.getStore();

function logInfo(msg: string, meta = {}) {
const c = getCtx();

logger.info({
requestId: c?.requestId,
traceId: c?.traceId,
...meta,
}, msg);
}


Теперь сервисный код не знает про HTTP middleware и req, но логи автоматически получают correlation metadata.

Транзакции без протаскивания tx

Для DB слоя можно делать withTransaction(), который запускает als.run({ ...ctx, tx }, fn), а getDbExecutor() возвращает ctx.tx ?? db. Trade-off хороший: меньше шума в API сервисов, но граница ответственности остаётся инфраструктурной, а не бизнесовой.

Где границы

* не заменяйте аргументы функции: бизнес-данные передавайте явно;
* не кладите в store req, res, большие payload или ORM graph;
* context не пересекает process boundary: worker threads, очереди, cron jobs и другие сервисы требуют явной передачи ids;
* нестандартные callback-based библиотеки могут разорвать async chain.

Практическое правило: используйте AsyncLocalStorage для логов, tracing, tenant/user metadata, аудита и текущей транзакции, а не для скрытого глобального состояния.

Вывод:
AsyncLocalStorage полезен, когда request-scoped инфраструктурный контекст улучшает observability и DX, но не размывает явные границы бизнес-логики.
2👍2👀2