Frontend VK Hub
رفتن به کانال در Telegram
Комьюнити VK для фронтендеров. Кто и что стоит за интерфейсами, которыми пользуются миллионы пользователей, — от Почты Mail до VK Teams — рассказываем здесь 🤘
نمایش بیشتر1 116
مشترکین
+124 ساعت
+17 روز
-430 روز
در حال بارگیری داده...
کانالهای مشابه
هیچ دادهای
مشکلی وجود دارد؟ لطفاً صفحه را تازه کنید یا با مدیر پشتیبانی ما تماس بگیرید.
ابر برچسبها
اشارات ورودی و خروجی
---
---
---
---
---
---
جذب مشترکین
اوت '26
اوت '26
+11
در 2 کانالها
ژوئیه '26
+4
در 0 کانالها
Get PRO
ژوئن '26
+6
در 1 کانالها
Get PRO
مه '26
+9
در 1 کانالها
Get PRO
آوریل '26
+14
در 1 کانالها
Get PRO
مارس '26
+9
در 0 کانالها
Get PRO
فوریه '26
+7
در 0 کانالها
Get PRO
ژانویه '26
+26
در 1 کانالها
Get PRO
دسامبر '25
+429
در 19 کانالها
Get PRO
نوامبر '25
+178
در 0 کانالها
Get PRO
اکتبر '25
+836
در 20 کانالها
| تاریخ | رشد مشترکین | اشارات | کانالها | |
| 28 اوت | 0 | |||
| 27 اوت | +2 | |||
| 26 اوت | +1 | |||
| 25 اوت | 0 | |||
| 24 اوت | 0 | |||
| 23 اوت | 0 | |||
| 22 اوت | +3 | |||
| 21 اوت | 0 | |||
| 20 اوت | 0 | |||
| 19 اوت | 0 | |||
| 18 اوت | 0 | |||
| 17 اوت | 0 | |||
| 16 اوت | 0 | |||
| 15 اوت | 0 | |||
| 14 اوت | 0 | |||
| 13 اوت | 0 | |||
| 12 اوت | +1 | |||
| 11 اوت | 0 | |||
| 10 اوت | 0 | |||
| 09 اوت | +1 | |||
| 08 اوت | +1 | |||
| 07 اوت | +1 | |||
| 06 اوت | 0 | |||
| 05 اوت | 0 | |||
| 04 اوت | +1 | |||
| 03 اوت | 0 | |||
| 02 اوت | 0 | |||
| 01 اوت | 0 |
پستهای کانال
+5
Speculation Rules: как готовить страницу до того, как по ней кликнули
Между кликом по ссылке и появлением новой страницы всегда есть пауза. Сервер может ответить за 30 миллисекунд, но дальше браузеру предстоит разобрать HTML, дотянуть стили и шрифты, выполнить скрипты — и человек всё это время смотрит на старую страницу.
Speculation Rules переносят эту работу на время до клика. Пока пользователь читает текущую страницу, браузер готовит следующую в фоне, а после клика показывает уже готовую.
В карточках — чем prefetch отличается от prerender, как настраивается момент срабатывания, какие лимиты держит Chrome и что делать с аналитикой, которая успевает выстрелить раньше визита.
#frontendvkhub #html #speculation_rules
| 2 | Подсветка текста, которая не трогает DOM
Задача звучит просто: подсветить найденные слова на странице. Реализация традиционно упирается в то, что подсвечивать в вебе можно только элемент. Значит, нужно найти текстовый узел, разрезать его, обернуть кусок в <mark>, а при следующем запросе всё это разобрать обратно.
Ломается такой подход быстро. Range.surroundContents() бросает исключение, если диапазон пересекает границы тегов, а найденная фраза запросто начинается в <em> и заканчивается снаружи. Обработчики, повешенные на исходные узлы, теряются вместе с этими узлами.
Виртуализованный список перерисовывается и уносит подсветку. Получается, что мы работаем с DOM ради того, что к структуре документа отношения не имеет.
Custom Highlight API решает это иначе — диапазон описывается, но в разметку не попадает:
const range = new Range();
range.setStart(textNode, 10);
range.setEnd(textNode, 25);
CSS.highlights.set('search', new Highlight(range));
::highlight(search) {
background-color: oklch(90% 0.15 100);
color: black;
}
DOM после этого выглядит ровно так же, как до. Диапазон живёт отдельно от дерева, а браузер рисует поверх текста при отрисовке. Пересечение границ тегов перестаёт быть проблемой: Range спокойно тянется через несколько элементов, потому что ничего оборачивать не требуется.
Одна подсветка может содержать сколько угодно диапазонов. Зарегистрировать можно сколько угодно именованных подсветок — каждая со своим ::highlight(name) и своими стилями. Для совместной работы это то, что нужно: выделение каждого участника получает своё имя и свой цвет, а снимается всё через CSS.highlights.delete('name') или разом через clear().
Набор доступных свойств намеренно узкий: color, background-color, text-decoration, text-shadow и обводка. Геометрию поменять нельзя, background-image игнорируется. Ограничение осмысленное: раскладка от подсветки не зависит, поэтому браузеру не нужно пересчитывать её при каждом изменении диапазонов.
По поддержке история долгая: Chrome и Edge закрыли её ещё в 105-й версии в августе 2022, Safari подключился в 17.2 в декабре 2023, Firefox замкнул круг позже, и с марта 2026 фича считается Baseline. На 2026 год она попала в фокус Interop.
Механизм полностью зависит от JavaScript и молча ничего не делает там, где не поддерживается. Отсюда рабочая схема — оставить в разметке настоящий <mark> со стилями по умолчанию, а поверх включить ::highlight() через @supports selector(::highlight(search)). Читатель без поддержки видит семантическую подсветку, остальные — динамическую, и никто не остаётся с пустой страницей.
А вы у себя чем подсвечиваете поиск по странице? Делитесь в комментах — интересно, кто уже выкинул обёртки в <mark>.
#frontendvkhub #javascript | 260 |
| 3 | Как приоритет слоёв побеждает специфичность в CSS
Каскад выбирает победителя по цепочке шагов: важность, origin, слой, специфичность, порядок объявления. На большой кодовой базе первые шаги обычно не используются, и всё сводится к гонке специфичности: reset с нулевой, дизайн-система со средней, продукт с высокой. Потом приходит правило вида .button.primary.active, и структура рассыпается.
@layer добавляет шаг, который стоит выше специфичности. Слои объявляются один раз, от менее приоритетного к более:
@layer reset, base, components, utils;
Порядок фиксируется этим первым объявлением, дальше правила складываются в слои по имени, а место в очереди не зависит от того, где физически лежит код.
@layer reset {
* { margin: 0; padding: 0; }
}
@layer components {
.button { padding: 12px 20px; background: blue; }
}
@layer utils {
.m-0 { margin: 0; }
}
.m-0 победит любой селектор из components, включая .card .button.primary:hover. Внутри одного слоя специфичность работает как обычно, между слоями она не значит ничего.
Второй частый сценарий — сторонняя библиотека в отдельном слое. Statement @layer разрешён до @import, и ставить его первым надёжнее: порядок задан явно, а не собирается из очерёдности импортов.
@layer reset, vendor, base, components, utils;
@import url("normalize.css") layer(reset);
@import url("bootstrap.css") layer(vendor);
Bootstrap уезжает в vendor, наши компоненты лежат выше и перебивают его без !important. Всё, что написано вне слоёв, живёт в неявном верхнем слое и перебивает любой именованный, поэтому миграция идёт мягко: @layer подключается в новом коде, старый unlayered работает поверх и переезжает по частям.
С !important порядок слоёв инвертируется, important из reset перебьёт important из utils. Менее очевидная часть той же механики: unlayered important оказывается слабее любого слоя. Правка, которая годами держала переопределение поверх всего, после переезда библиотеки в слой молча перестанет работать.
Слои вкладываются друг в друга: @layer components { @layer forms, buttons, cards; }. Приоритет считается по иерархии, а весь блок components занимает одно место в глобальном порядке. Рабочая раскладка обычно укладывается в шесть слоёв: reset, tokens, base, components, utils, overrides.
Поддержка приехала в браузеры ещё в 2022, сейчас @layer в статусе Baseline widely available. Единственное — Safari 15.3 и старше выбрасывает содержимое слоя целиком, без @supports там будет пустая страница.
Порядок объявляется один раз и дальше не требует ни сборки, ни договорённостей внутри команды. А в ваших проектах слои уже разложены или порядок стилей всё ещё держится на очерёдности импортов?
#frontendvkhub #css | 408 |
| 4 | Функции и условия в CSS без препроцессора
Кастомные свойства решили половину задачи: значение можно вынести в переменную и переиспользовать. Но переменная хранит ровно то, что в неё положили, — посчитать что-то на её основе она не умеет. Отсюда Sass, который считает на этапе сборки и потому не видит того, что происходит в браузере.
Теперь CSS умеет это сам. @function описывает функцию с аргументами и типами, if() возвращает разные значения по условию — и то и другое работает во время выполнения, с живыми значениями переменных.
#frontendvkhub #css | 5 473 |
| 5 | CSS @scope — изоляция стилей без Shadow DOM и препроцессоров
Изоляция стилей в CSS долго решалась косвенно: BEM с длинными именами, CSS Modules с хешированием, scoped-атрибут в Vue, Shadow DOM в Web Components. У каждого варианта своя цена. @scope даёт нативный способ ограничить область действия правил. В Chrome 118 с октября 2023, Safari 17.4 с марта 2024, Firefox 143 с сентября 2025 — и в 2026 везде.
Идея простая. Внутри @scope правила применяются только к элементам в поддереве заданного корня:
@scope (.card) {
h2 { color: purple; }
p { line-height: 1.6; }
}
Все h2 и p внутри .card получат эти стили. Классов вроде .card__title не нужно: селектор внутри scope пишется как есть, границу задаёт сам scope.
Верхняя граница задаётся через to:
@scope (.article) to (.comments) {
p { font-size: 1.1rem; }
}
Правило применяется ко всем p внутри .article, но не проникнет в .comments и вложенное. Решает классическую задачу: статья и блоки комментариев стилизуются независимо, но рендерятся внутри одной иерархии.
Второй вариант — inline-scope через <style> внутри HTML. Тег <style> с @scope без селектора привязывается к родителю:
<div class="widget">
<style>
@scope {
h3 { color: teal; }
button { background: navy; }
}
</style>
<h3>Заголовок</h3>
<button>Кнопка</button>
</div>
Ближайший CSS-аналог <style scoped> из Vue: описываем стили прямо в компоненте, они применяются только к его поддереву. Без прекомпилятора и обёрток.
Правила внутри @scope считаются с той же специфичностью, что и снаружи, но при равной выигрывает правило, находящееся ближе к элементу по DOM. Правило из внутреннего @scope перебьёт правило из внешнего.
Практический паттерн — темизация секции:
@scope (.section--dark) {
:scope { background: #1a1a1a; color: #eee; }
a { color: #7cb; }
button { background: #333; color: #eee; }
}
:scope — сам элемент, к которому привязана область (аналог :host в Shadow DOM). Всё внутри .section--dark перекрашивается разом, без каскадных префиксов и без глобальных override.
@scope ограничивает только те правила, что внутри самого @scope. Не блокирует утечку правил снаружи: если во внешнем CSS .card p { color: red }, оно применится к p внутри scope.
Если в проекте BEM используется только ради изоляции, или CSS Modules подключены только для избежания конфликтов имён, @scope часто закрывает задачу без сборочного шага.
#frontendvkhub #css #scope | 515 |
| 6 | Promise.withResolvers() — deferred без танцев с замыканиями
Есть паттерн, который в JS годами делали руками: получить промис плюс функции resolve и reject, чтобы дёрнуть их снаружи. Каждый раз конструкция с замыканием. С 2024 есть Promise.withResolvers(): три сущности одной строкой. Baseline: Chrome 119, Safari 17.4, Firefox 121, Node 22+.
Как это выглядит
Раньше было так:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// а дальше где угодно
button.addEventListener('click', () => resolve('clicked'));
Теперь короче:
const { promise, resolve, reject } = Promise.withResolvers();
button.addEventListener('click', () => resolve('clicked'));
Кажется мелочью, но паттерн возникает часто. Рассмотрим три кейса.
Конвертация event-based API в await
Ждём одно событие и продолжаем:
async function waitForClick(button) {
const { promise, resolve } = Promise.withResolvers();
button.addEventListener('click', resolve, { once: true });
return promise;
}
const evt = await waitForClick(saveButton);
Раньше — new Promise с замыканием, теперь одна строка настройки и return promise.
Промис, которым управляют извне модуля
Экспортируем промис, резолвящийся при готовности: инициализация SDK, авторизация, connection ready:
const { promise: ready, resolve: markReady } = Promise.withResolvers();
export function initSDK(config) {
setup(config).then(() => markReady());
}
export { ready };
Другие модули просто await ready перед использованием SDK. Без withResolvers пришлось бы либо экспортировать промис отдельно, либо городить lazy-инициализацию.
Обёртка над колбэками
Много старых API отдают результат через колбэк, обёртка с withResolvers чище:
function toPromise(fn) {
const { promise, resolve, reject } = Promise.withResolvers();
fn((err, result) => {
if (err) reject(err);
else resolve(result);
});
return promise;
}
const data = await toPromise(cb => oldApi.load(cb));
То же, что делает util.promisify в Node, только локально и без зависимостей.
Один нюанс с TypeScript. Раньше тип для deferred писали руками. С withResolvers TypeScript выводит тип автоматически, дженерик указывается при вызове:
const { promise, resolve } = Promise.withResolvers<string>();
resolve('hello'); // ok
resolve(42); // error
Promise.withResolvers() создаёт промис, у которого resolve и reject доступны снаружи. Удобно, но может быть опасно: любой, кто получил ссылку на resolve, может завершить промис из произвольного места. Не пробрасывайте эти функции наружу модуля без нужды — держим их приватными, отдаём только сам промис.
Если в кодовой базе есть паттерн let resolve; new Promise(r => resolve = r) — время заменить на Promise.withResolvers. Одна строка, читается очевидно, TypeScript выводит типы сам. | 420 |
| 7 | Temporal API
Почти в каждом проекте найдётся место, где дату надо сдвинуть на месяц вперёд или посчитать разницу между двумя днями. И почти всегда ради этого в зависимостях лежит date-fns или dayjs, потому что штатный Date для такой работы не приспособлен: он мутабельный, нумерует месяцы с нуля и знает ровно одну зону, системную.
Temporal переносит эту арифметику в сам язык, и за последний год он перестал быть предметом ожидания. В Firefox поддержка появилась ещё в 139, в январе 2026 её добрали Chrome и Edge в версии 144, а в марте предложение дошло до Stage 4 и вошло в ES2026. Поэтому разговор сместился к переводу существующего кода, и об этом расскажем в карточках: какие в Temporal типы, как он считает даты и зоны и как всё это стыкуется со старым Date.
#frontendvkhub #temporal | 569 |
| 8 | 🔵Chrome 150: новые возможности CSS
Chrome в июле раскатал 150 в стейбл на основную массу пользователей — и это, пожалуй, главное событие месяца, потому что там сразу пачка возможностей CSS, ради которых давно писали костыли.
text-fit автоматически подгоняет размер шрифта под ширину контейнера — до сих пор это делалось через ResizeObserver с пересчётом font-size на каждый resize, теперь одна строка CSS.
background-clip: border-area наконец сделал возможными градиентные и image-fill бордеры без псевдоэлементов и хитрых box-shadow — фичу ждали примерно с эпохи CSS Gradients.
focusgroup — декларативный атрибут для клавиатурной навигации стрелками. В каждом кастомном селекте или меню раньше приходилось руками писать keydown-обработчики и следить за фокусом.
🔵Chrome 151: доступ к анимациям упростили
Через несколько дней после 150 подоспел 151 в бете. animation теперь доступен прямо на AnimationEvent и TransitionEvent: раньше в обработчике звали getAnimations() и матчили нужное по имени, теперь объект достаётся из ивента. Плюс ruby-overhang для CJK. Из breaking — 151 больше не запустится на macOS 12, минимум теперь 13, так что если пользователи на старых Mac ещё есть, стоит глянуть свою статистику.
🔵Firefox: AI-ассистент в широком доступе и нативная поддержка Containers
Firefox параллельно занят пользовательским опытом. 152.0.6 от 14 июля докатил Smart Window до широкого rollout: выделили текст на странице, всплывает встроенный AI-ассистент с опциями summarize, explain, rewrite. То, что раньше делали сторонние расширения, теперь встроено. А 21 июля вышел 153 ESR с превью нативных Containers — вместо Multi-Account Containers extension теперь родная функциональность. Плюс частичная поддержка ::-webkit-scrollbar в CSS для web compat со старыми сайтами.
🔵Vue: патчи для hydration, reactivity и SSR
Vue шёл по мелким патчам — hydration edge cases, reactivity в scope stop, preserving textarea resize, deferred mount для teleport. Ничего headline-фичного, но если проект на Nuxt и SSR-heavy, свежую минорку стоит поймать.
#frontendvkhub #дайджест | 472 |
| 9 | CSS Anchor Positioning — tooltip и меню без Popper.js
Позиционирование tooltip относительно кнопки годами было болью. Popper.js и Floating UI берут на себя всё: координаты, scroll, resize, переполнение viewport, разворот тултипа при нехватке места. С 2024 CSS сам умеет привязывать элементы. Chrome 125, Safari 26, Firefox 147 — везде Baseline.
Идея очень простая. Один элемент помечается как anchor-name: --my-button, другой ссылается через position-anchor: --my-button и вычисляет позицию через anchor():
<button id="menu-btn" style="anchor-name: --menu-btn">
Меню
</button>
<div popover id="menu">
Пункт 1
Пункт 2
</div>
#menu {
position-anchor: --menu-btn;
top: anchor(bottom);
left: anchor(left);
}
anchor(bottom) возвращает координату нижней границы якоря, anchor(left) — левой. Меню оказывается ровно под кнопкой у её левого края. Никакого JS, никакого пересчёта на scroll — браузер держит связь сам.
Второй важный кусок — position-try. Что, если снизу не хватает места, и меню обрежется viewport? Попробовать другую позицию:
@position-try --menu-flip-up {
top: auto;
bottom: anchor(top);
}
#menu {
position-anchor: --menu-btn;
top: anchor(bottom);
left: anchor(left);
position-try-fallbacks: --menu-flip-up;
}
Браузер сначала пробует основную позицию, при выходе за viewport применяет --menu-flip-up и разворачивает меню наверх, привязываясь к верху кнопки. Именно это Popper.js делал через flip middleware. Теперь в платформе.
Для типичных сценариев есть шорткат position-area. Вместо явных top/left задаётся направление:
#tooltip {
position-anchor: --btn;
position-area: top; /* сверху над якорем */
/* или: top left, bottom right, center, start end */
}
position-area: top автоматически ставит bottom: anchor(top) и правильное горизонтальное выравнивание. Закрывает большинство кейсов для тултипов.
Отдельно — приятная работа с Popover API. Тултипы и меню ставятся в top layer через popover, не воюют с overflow: hidden и z-index родителей. Anchor Positioning работает с поповерами прозрачно:
<button popovertarget="hint" style="anchor-name: --hint-btn">?</button>
<div id="hint" popover style="position-anchor: --hint-btn; position-area: top">
Подсказка
</div>
Клик на ? открывает поповер, он сам позиционируется над кнопкой, Escape и клик снаружи закрывают. Без JS.
Anchor работает только с position: absolute или fixed (для popover автоматически). Если anchor-name у нескольких элементов, работает первый в DOM-порядке. И anchor() нельзя прописать в animation и transition — только в свойствах позиции.
Если Popper.js или Floating UI в проекте ради простых тултипов и dropdown — самое время посмотреть, что закрывается нативно. Библиотеки остаются нужны для сложной физики (виртуальные якоря, драг), но 80% типичных случаев уходят в CSS.
А вы уже пробовали Anchor Positioning?
#frontendvkhub #css | 531 |
| 10 | useEffect: семь случаев, когда он не нужен
React 19 сужает область useEffect до одной задачи: синхронизация с внешними системами. Деривация UI из пропсов и стейта происходит в рендере, обработка действий — в обработчиках событий, загрузка данных уходит во фреймворк. В карточках рассмотрим семь случаев, когда useEffect не нужен.
#frontendvkhub #react #useeffect | 625 |
| 11 | TypeScript 7.0 — это не только новый синтаксис. Главное изменение в том, что команда Microsoft перенесла компилятор с TypeScript и JavaScript на Go. Какие новые настройки действительно стоит учитывать при переходе, рассказывает старший фронтенд-разработчик VK Марат Исаев.
Новый компилятор на Go
До TypeScript 7.0 компилятор был на TypeScript и выполнялся как JavaScript-код в Node.js. Для языка это было удобно, ведь TypeScript развивался на самом себе. Но в больших проектах такая архитектура влияла на производительность: проверка типов — тяжёлая вычислительная задача, а старый компилятор не мог полноценно использовать несколько ядер.
В TypeScript 7.0 существующую реализацию перенесли на Go. Логика проверки типов остаётся совместимой с TypeScript 6.0, но сам компилятор теперь работает как нативная программа и использует параллельную обработку активнее.
Microsoft говорит об увеличении скорости примерно в 10 раз в отдельных сценариях. В реальных же проектах эффект будет зависеть от размера кодовой базы, структуры проекта, количества пакетов, сложности типов и ресурсов машины.
Режим наблюдения за файлами
tsc --watch тоже переписали. В TypeScript 7.0 он вдохновлён сборщиком зависимостей Parcel и перенесён на Go.
Microsoft говорит, что простые решения на опросе файловой системы были слишком дорогими для крупных проектов. И новый наблюдатель должен стабильнее работать и меньше нагружать систему.
Ограничения
TypeScript 7.0 уже можно проверять в проектах, но стабильного программного API пока нет. Его обещают не раньше TypeScript 7.1.
Поэтому Microsoft выпустила пакет @typescript/typescript6. Он позволяет держать TypeScript 6.0 для инструментов, которым нужен старый API, и отдельно запускать TypeScript 7.0.
Нюансы при переходе
TypeScript 7.0 совместим с TypeScript 6.0 по проверке типов, но конфигурация настроек обновлена и есть новые ограничения:
🔵 strict включён по умолчанию
🔵 module по умолчанию равен esnext
🔵 noUncheckedSideEffectImports включён по умолчанию
🔵 stableTypeOrdering включён и больше не отключается
🔵 rootDir по умолчанию указывает на ./
🔵 types по умолчанию равен []
Также есть старые настройки, которые больше не поддерживаются:
🔵 target: es5
🔵 downlevelIteration
🔵 moduleResolution: node, node10 и classic
🔵 module: amd, umd, systemjs, none
🔵 baseUrl (paths можно указать относительно корня проекта)
🔵 esModuleInterop и allowSyntheticDefaultImports больше нельзя выставлять в false
🔵 alwaysStrict считается включённым и больше не отключается
#frontendvkhub #go #typescript | 641 |
| 12 | Четыре boolean-флага isLoading, isLoaded, hasError, isSuccess порождают 16 комбинаций. Валидны шесть, остальные десять компилятор пропустит, а бизнес-логика — нет.
Разрыв между типом и доменом — источник багов. Тип говорит компилятору, что все 16 комбинаций возможны. Домен говорит, что загрузка и успех одновременно невозможны. Ограничения нигде не закодированы, поэтому компилятор их не проверяет.
// 16 комбинаций, 6 валидных
type RequestState = {
isLoading: boolean;
isLoaded: boolean;
hasError: boolean;
isSuccess: boolean;
data?: string;
error?: Error;
};
Тип разрешает isLoading: true и isSuccess: true одновременно. Каждый потребитель защищается условными проверками от комбинаций, которые не должны существовать: error перекрывает loading, success означает, что data существует. Это бизнес-правила, замаскированные под код. Мартин Фаулер называет это Flag Argument: boolean прячет несколько поведений за одним битом.
Discriminated Unions
Union кодирует инварианты в самом типе. Один литеральный дискриминант — источник правды для активного варианта:
// 4 варианта, 4 комбинации
type RequestState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error"; error: Error };
data существует только в ветке success, error — только в error. switch (state.status) даёт точное сужение типов: компилятор знает, какой вариант активен, и запрещает доступ к полям других вариантов. Добавили "reloading" — каждый необработанный switch становится ошибкой компиляции, а не скрытым багом в рантайме.
Проверка полноты
function handle(state: RequestState) {
switch (state.status) {
case "idle": return null;
case "loading": return <Spinner />;
case "success": return <Data data={state.data} />;
case "error": return <Error error={state.error} />;
default: {
const _: never = state; // ошибка при добавлении нового варианта
return _;
}
}
}
Компилятор становится детектором изменений. Если добавили или переименовали состояние, то он укажет, где логика устарела. UI-компоненты — самое очевидное место для замены. Состояния часто взаимоисключающие: кнопка не может одновременно грузиться, быть отключённой из-за валидации и показывать ошибку как независимые режимы.
#frontendvkhub #boolean | 554 |
| 13 | scheduler.yield() — нормальный способ разбивать длинные задачи
Длинные JavaScript-задачи — главная причина плохого INP. Браузер не может обработать клик или показать спиннер, пока выполняется блок дольше 50 мс. До 2024 разбивать тяжёлую работу можно было только костылями вроде setTimeout(fn, 0) или await new Promise(r => setTimeout(r, 0)). Сейчас есть scheduler.yield() — нормальный API с приоритетной очередью.
Базовая проблема:
function processItems(items) {
for (const item of items) {
process(item); // ~1 мс
}
}
// 5 000 элементов = 5 секунд блокировки main thread
Старый способ — setTimeout(fn, 0):
async function processItems(items) {
for (const item of items) {
process(item);
await new Promise(r => setTimeout(r, 0));
}
}
Работает, но с двумя минусами. Первый — минимум 4 мс на каждый yield (минимум setTimeout по спецификации). Второй, и главный — continuation попадает в самый низ очереди, после всех остальных тасков. Если параллельно дёргается чужой код или сторонний скрипт, наш цикл встанет за ним.
Если параллельно дёргается чужой код или сторонний скрипт — наш цикл встанет за ним.
Новый способ — scheduler.yield():
async function processItems(items) {
for (const item of items) {
process(item);
await scheduler.yield();
}
}
scheduler.yield() возвращает управление браузеру, чтобы тот обработал input и рендеринг, и продолжает выполнение с того же места. Continuation попадает в высокоприоритетную очередь, не в конец. То есть наш цикл возобновляется раньше других тасков и при этом не блокирует кнопки и анимации.
Для постановки тасков с приоритетом есть scheduler.postTask():
scheduler.postTask(() => doImportant(), { priority: 'user-blocking' });
scheduler.postTask(() => doBackground(), { priority: 'background' });
Три приоритета: 'user-blocking' (для реакции на клик), 'user-visible' (по умолчанию), 'background' (для аналитики и работы в idle). Нормальный планировщик с приоритетами вместо «что-то в очередь и забыли».
Где применять
Циклы обработки больше 50 мс, гидратация тяжёлых компонентов, парсинг больших JSON, инициализация трекеров и аналитики при загрузке.
Поддержка: Chrome 129 (сентябрь 2024), пока нет в Safari и Firefox. По данным веба — 71,5% пользователей. Полифилл scheduler-polyfill падает на setTimeout(0) там, где нативного нет, так что в проде уже можно.
Если до сих пор используете setTimeout(0) или await new Promise(r => setTimeout(r, 0)) для разбиения тяжёлых циклов — самое время посмотреть на scheduler.yield(). Меньше задержек, приоритет не теряется, прогресс-бары и анимации не страдают.
А какие тяжёлые задачи вы разбиваете на куски в проде?
#frontendvkhub #javascript | 608 |
| 14 | Форматирование и локали в платформе Intl
Intl — встроенный набор классов для форматирования и сравнения с учётом локали. Большинство фронтов тащит date-fns ради «2 дня назад», lodash для сортировки, ручные функции для денег и процентов. Почти всё это уже есть в браузере, причём заметно мощнее, чем кажется.
В карточках рассмотрим шесть классов — каждый со своим случаем, и почти каждый поднимает вопрос «А что, так можно было?»
#frontendvkhub #intl | 547 |
| 15 | Продолжаем рубрику «Знакомство с командой». Сегодня свою историю расскажет Денис Гордеев, руководитель подразделения Core Frontend ВКонтакте.
➡️ От школьного фана к фронтенду
Свой первый веб-проект я сделал ещё в школе: это была система для проведения олимпиад по программированию. Поступил в МГУПИ на специальность АСОИУ («автоматизированные системы обработки информации и управления») и со второго курса начал параллельно работать фулстек-разработчиком в разных компаниях. С каждым новым опытом перекос в сторону фронтенда становился всё сильнее.
В итоге я решил, что хочу заниматься именно фронтендом. Мне нравится эта связь между инженерными решениями, качеством продукта и пользовательским опытом.
➡️ Над чем работаешь
Сейчас я руковожу подразделением Core Frontend ВКонтакте. Мы отвечаем за весь фронтенд vk.com, m.vk.com и за библиотеки компонентов VKUI и VKCOM kit.
Работаем по нескольким направлениям: повышаем стабильность и надёжность платформы, развиваем архитектуру и инфраструктуру, BFF, серверный рендеринг, UI-тестирование и тестовые домены.
Эти слои фронтенда не всегда видны пользователю, но они сильно влияют на скорость разработки фич и удобство работы внутри большой кодовой базы.
➡️ Почему роль лида — это постоянный баланс
В своей работе лид должен соблюдать баланс между людьми, процессами и продуктом. Одновременно ему нужно учитывать текущие потребности команды и бизнеса. Иногда нужно глубже погрузиться в архитектуру продукта, иногда — помочь коллегам. Моя задача — вовремя перераспределять внимание и ресурс туда, где это даст максимальный эффект.
➡️ Что сложно
При переходе из роли разработчика в роль лида самым сложным было принять, что я уже не могу и не должен тащить руками тот же объём, что раньше.
Достаточно долго я продолжал работать как полноценный разработчик и как руководитель одновременно. В итоге это приводило к огромным переработкам и полному отсутствию баланса между работой и личной жизнью.
Переход в лидерскую роль требует перестройки мышления. Ты уже не только сам решаешь задачи, но и создаёшь условия, в которых команда может решать их лучше и быстрее.
➡️ Что заряжает
Меня заряжает моя команда. У нас настоящие профессионалы своего дела. Ребята настроены на амбициозные стройки и сильные результаты, все на одной волне и двигаются в одном направлении. При этом у нас тёплая атмосфера, поддержка и взаимовыручка. Для меня это вторая семья.
➡️ Как не выгораешь
У меня есть фраза: «Гори, чтобы светить». Мне важно получать удовольствие от работы. Интерес и ощущение смысла позволяют мне не выгорать. Я вижу результат и понимаю, как он помогает пользователям.
Если устаю, то лучше всего переключает смена деятельности. Например, после большого количества встреч очень приятно поработать руками: набросать MVP для какой-нибудь задачи из бэклога или провести исследование по проблеме, на которую у команды пока не хватает времени.
➡️ Как отдыхаешь
Люблю настолки и компьютерные игры — они отлично помогают переключиться и разгрузить голову.
Ещё учусь играть на гитаре. Это хороший способ расслабиться и отвлечься, особенно когда ощущаешь прогресс.
➡️ Что бы ты сказал себе в начале карьеры
Я бы посоветовал себе как можно раньше попасть в энтерпрайз. Небольшие локальные компании — это тоже круто, там можно получить важный опыт. Но именно в бигтехе появляются уникальные вызовы и масштабные проекты, которые сложно заменить чем-то другим. Здесь быстрее понимаешь, как устроены большие системы, как работают команды, процессы, инфраструктура и продукт, которым пользуются миллионы людей.
#frontendvkhub #команда | 573 |
| 16 | 🔵 Спецификация современного сайта
Вышел чек-лист из 128 пунктов для аудита современных веб-приложений: производительность, доступность, безопасность, SEO и готовность к работе с AI-агентами. Хороший ориентир для оценки зрелости продукта и инженерных процессов.
🔵Chrome 150 Beta
Новая бета-версия Google Chrome продолжает расширять возможности веб-платформы. Традиционно именно такие релизы задают направление развития браузерных API на ближайшие месяцы.
🔵WebGPU продолжает развиваться
Обновления в WebGPU для Chrome 149–150 расширяют возможности высокопроизводительной графики и вычислений в браузере. Технология постепенно становится реальным фундаментом для сложных визуализаций, редакторов и AI-задач на клиенте.
🔵CSS @function — движение к программируемому CSS
Новый механизм позволяет создавать переиспользуемые функции прямо в CSS, уменьшая зависимость от препроцессоров и JavaScript. Ещё один шаг к тому, чтобы логика интерфейса оставалась внутри платформы.
🔵State of CSS 2026
Ежегодный опрос остаётся одним из главных индикаторов того, какие возможности CSS реально используются в индустрии и куда смещается внимание фронтенд-сообщества.
🔵React Compiler меняет подход к оптимизации
Автоматическая мемоизация постепенно снижает необходимость в useMemo, useCallback и React.memo для многих сценариев. Это может заметно повлиять на практики разработки и код-ревью в React-проектах.
#дайджест #frontendvkhub | 493 |
| 17 | @starting-style — анимация появления и исчезновения через чистый CSS
Годами анимация появления элемента в CSS решалась через JS: то принудительный reflow, то отложенный setTimeout, то двойной requestAnimationFrame.
С исчезновением было ещё хуже — display:none убивал любую exit-анимацию мгновенно. В 2024-м это починили двумя штуками: @starting-style и transition-behavior: allow-discrete. Baseline, поддержка везде.
Разберём, почему обычный transition ломается на появлении, как это чинит @starting-style, как анимировать <popover> и <dialog>, и один неочевидный подвох со специфичностью.
#frontendvkhub #css | 700 |
| 18 | Самая недооценённая штука в современном JS
У итераторов в JavaScript теперь есть .map, .filter, .take и ещё десяток методов — те же, что у Array, но ленивые. Никаких промежуточных массивов, никакой материализации. Спецификация ES2025, поддерживается везде с весны 2025: Chrome 122, Firefox 131, Safari 18.4, Node 22+. Большинство фронтов про них не знают.
Зачем это вообще нужно
Array-методы материализуют каждую промежуточную коллекцию:
// Каждый шаг создаёт новый массив
const result = bigArray
.filter(x => x.active) // массив N байт
.map(x => x.id) // ещё один
.slice(0, 10); // и ещё, хотя нужны 10 элементов
Iterator-методы работают лениво и не аллоцируют ничего лишнего:
const result = bigArray.values()
.filter(x => x.active)
.map(x => x.id)
.take(10)
.toArray();
Тут мы пробегаем по входу ровно столько, сколько нужно, чтобы набрать 10 подходящих, и останавливаемся. Никаких промежуточных коллекций.
Бесконечные потоки наконец работают
С генераторами разница совсем резкая:
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const first10Even = fibonacci()
.filter(n => n % 2 === 0)
.take(10)
.toArray();
С Array.from(fibonacci()) это бы повисло — массив бесконечный. С итератором .take(10) дёргает источник ровно до десятого совпадения.
Iterator.from превращает что угодно в итератор
Любой iterable можно обернуть и получить доступ к хелперам:
const tags = new Set(['react', 'vue', 'svelte']);
const upper = Iterator.from(tags)
.map(t => t.toUpperCase())
.toArray();
// ['REACT', 'VUE', 'SVELTE']
То же работает с Map.entries(), NodeList, генераторами, async iterables. NodeList особенно приятно:
document.querySelectorAll('li').values().filter(...).take(5) — без обхода всего DOM.
Когда выигрывают, а когда нет
Большие коллекции, из которых берём небольшой кусок — выигрывают. Генератор или бесконечный поток — выигрывают. Потоковая обработка ReadableStream — выигрывает.
А для обычного массива на 50–100 элементов разницы почти нет, Array-методы даже чуть быстрее за счёт оптимизаций движка.
В итоге Iterator helpers — это про память и про возможность не материализовать промежуточные результаты. Если в коде встречаются цепочки вида .filter().map().slice() на больших данных или ручные циклы для досрочного выхода из обработки — это первые кандидаты на переписывание через .values().
А вы где используете итераторы, кроме for...of? Делитесь кейсами в комментариях..
#frontendvkhub #javascript | 596 |
| 19 | Синхронизация состояния между вкладками — пять браузерных API
Когда у пользователя несколько вкладок одного приложения, обычный state не работает. Логин в одной будет подхвачен в других. Открытый сокет в одной — лишний груз в остальных.
В карточках разберём, как пять браузерных API закрывают разные части этой задачи.
#frontendvkhub #api | 769 |
| 20 | Несколько лет работа с React выглядела шаблонно: открыть компонент, обвесить значения useMemo, функции — useCallback, «ребёнка» завернуть в React.memo. Половина обвесов стояла на всякий случай, другая не работала из-за inline-объекта в пропсе. Ревью часто перерастало в спор о стабильности ссылок.
React Compiler 1.0 вышел в октябре 2025 и закрыл эту тему. К 2026 году он стабилен в Next.js 16 и Vite. От разработчика требуется только включить флаг.
Корень проблемы в каскадном ре-рендере. Меняется состояние родительского компонента, React по умолчанию рендерит и его, и всё поддерево, даже если пропсы «детей» не изменились: корректность важнее производительности. Три классических escape hatch — useMemo, useCallback, React.memo — держались на одной хрупкой штуке, идентичности ссылки. Inline-объект в пропсе или забытая зависимость — и мемоизация молча обнулялась.
Компилятор подходит с другой стороны. Это build-time tool на babel transform: разбирает компонент целиком, строит граф зависимостей и там, где они стабильны, сам подставляет эквивалент useMemo, useCallback и React.memo. На выходе — обычный JS, runtime-кода не прибавляется.
В Next.js 16 компилятор включается одним параметром:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactCompiler: true,
}
export default nextConfig
С Next.js 16 reactCompiler перешёл из разряда экспериментальных в стабильную top-level-настройку. По умолчанию он выключен, команда next.js собирает данные о времени сборки. Дополнительно ставится babel-plugin-react-compiler, в Vite та же связка выполняется через babel-конфиг. Отдельно eslint-plugin-react-compiler — он подсветит места, где компилятор откажется работать.
Откажется там, где код нарушает Rules of React: мутация объекта во время рендера, чтение mutable external store без useSyncExternalStore, неидемпотентная логика, динамический property access. В таких компонентах оптимизации просто не будет. Поэтому ESLint-плагин — обязательный первый шаг: без чистки нарушений компилятор молча обойдёт половину компонентов.
useMemo и useCallback при этом не исчезают. Они остаются как escape hatch для явного контроля: например, если мемоизированное значение идёт с зависимостью useEffect, и эффект не должен пересрабатывать на каждое перевычисление. Эвристики компилятора иногда мемоизируют чуть шире или чуть уже, чем нужно конкретному эффекту.
В новых проектах компилятор включают с первого коммита и не разбрасывают useMemo заранее. В существующих кодовых базах порядок другой: сначала прогон ESLint-плагином, фикс нарушений Rules of React, потом флаг, и наконец постепенная зачистка избыточных мемо. Главное изменение в практике: идентичность ссылок перешла в детали реализации компилятора, и обсуждать её на ревью больше не имеет смысла.
#frontendvk #react | 788 |
