1119
Комьюнити VK для фронтендеров. Кто и что стоит за интерфейсами, которыми пользуются миллионы пользователей, — от Почты Mail до VK Teams — рассказываем здесь 🤘
Frame-sizing в Chrome 154: iframe сам подстраивает высоту под контент
Виджет комментариев, форма оплаты или дашборд во фрейме почти всегда упираются в высоту. Хост не знает, сколько места нужно странице внутри, поэтому она меряет себя сама и шлёт размер через postMessage, а родитель ловит сообщение и меняет высоту фрейма. Этот мост каждая команда пишет заново, и пока первое сообщение не пришло, фрейм стоит с фиксированной высотой и прокруткой внутри.
Chrome 154, вышедший 22 сентября, справляется без скриптов. Нужны две вещи: CSS-свойство frame-sizing на стороне хоста и метатег с согласием в документе внутри фрейма.
.embed {
width: 100%;
height: 500px; /* для браузеров без поддержки */
}
@supports (frame-sizing: content-height) {
.embed {
height: auto;
frame-sizing: content-height;
max-height: 80vh;
}
}<meta name="responsive-embedded-sizing"
content="allow-origins=https://publisher.example">
allow-origins через пробел перечисляются домены, которым можно сообщать размер, либо ставится *. Схема работает и для фреймов с другого домена и дополняет frame-ancestors из CSP. Тег должен быть в документе с самого начала, добавить его скриптом после загрузки не выйдет.content-height, у свойства есть content-width, логические content-inline-size и content-block-size, а также auto, которое оставляет прежнее поведение. Для виджетов, растущих вниз, подходит content-height.if ("requestResize" in window) {
window.requestResize();
}postMessage пока выбрасывать рано, потому что в Firefox и Safari поддержки нет. Зато метатег во встраиваемые страницы можно добавлять уже сейчас, а на хосте включать frame-sizing под @supports и оставить прежний код запасным путём.postMessage или живёте с фиксированной?
🔵STP 252: @supports научился проверять сами условия
11 сентября в Safari Technology Preview 252 добавили CSSConditionRule.supports и named-feature() в @supports — проверку поддержки фич без детектируемого синтаксиса, например anchor positioning. Заодно распрефиксовали user-select — теперь это стандартное свойство без -webkit-.
🔵Safari 27 — новые возможности CSS и HTML
14 сентября вышел Safari 27 с кастомизацией <select>, псевдоклассом :heading, sizes="auto" и расширениями color-mix(). WebKit также обновил загрузчик ES-модулей, улучшив поддержку top-level await.
🔵Node.js 26.9 — быстрые Worker Threads и FFI
16 сентября Node.js ускорил запуск Worker Threads до 2 раз и снизил их потребление памяти. Экспериментальный node:ffi теперь включён по умолчанию — вызывать функции нативных библиотек можно без отдельного модуля C++.
🔵Chrome 154: браузер переходит на двухнедельный релиз-цикл
22 сентября вышел Chrome 154 — первый релиз по новому двухнедельному расписанию вместо прежнего четырёхнедельного. Из фич для вёрстки:
• scroll-marker-group получил режимы links/tabs, которые задают фокус-порядок и ARIA-семантику для ::scroll-marker-group
• iframe теперь можно сделать самоподстраивающимся под контент через frame-sizing: content-height — без ручного измерения высоты и postMessage
🔵Next.js: серия security-апдейтов закрывает критическую уязвимость в апстриме
22 сентября Vercel выпустил внеплановый патч 16.3.6 и 15.5.26 из-за критической уязвимости в сторонней зависимости. А 30 сентября — плановый сентябрьский security-релиз 16.3.8 и 15.5.27, закрывающий ещё 7 уязвимостей: 1 высокой степени критичности, 5 средних и 1 низкую. Если вы ещё не обновили проект на Next.js — не откладывайте это.
🔵Safari Technology Preview 253: sibling-index() добрался до @container
23 сентября в STP 253 sibling-index() и sibling-count() стало можно использовать прямо в условиях @container. То есть раскладка контейнера теперь может зависеть не только от его размера, но и от позиции элемента среди соседей. WebKit также обновил безопасное выравнивание (safe/unsafe) для абсолютно позиционированных боксов, чтобы они не вылетали за пределы исходного содержащего блока.
#frontendvkhub #дайджест
URLPattern: маршруты, которые не нужно переписывать в регулярки
В любом приложении со своей маршрутизацией рано или поздно заводится файл с регулярками. /^\/users\/(\d+)\/posts\/([\w-]+)$/ работает, но читается тяжело, номера групп приходится помнить наизусть, а проверка домена и протокола живёт отдельным условием рядом. Обычный выход состоит в том, чтобы притащить path-to-regexp и получить ещё одну зависимость ради разбора адреса.
Разбирать адреса умеет сама платформа. URLPattern принимает шаблон в знакомом по роутерам синтаксисе и сопоставляет с ним URL целиком, а не только путь:
const route = new URLPattern({ pathname: "/users/:id/posts/:slug" });
route.test("https://app.example.com/users/42/posts/hello"); // true
const match = route.exec("https://app.example.com/users/42/posts/hello");
match.pathname.groups; // { id: "42", slug: "hello" }:name, доступны звёздочка для любого остатка, модификаторы ? и +, фигурные скобки для необязательных кусков пути и собственные регулярки прямо внутри шаблона: /v:version(\\d+)/ поймает только числовую версию.const routes = [
[new URLPattern({ pathname: "/api/v:v(\\d+)/*" }), handleApi],
[new URLPattern({ hostname: "cdn.example.com" }), handleAssets],
];
self.addEventListener("fetch", (event) => {
const route = routes.find(([pattern]) => pattern.test(event.request.url));
if (route) event.respondWith(route[1](event.request));
});
new URLPattern("https://example.com/books/:id"), пропущенные части приравниваются к пустой строке, поэтому адрес с параметрами мимо такого шаблона пройдёт, пока не дописать ?*#* вручную.ignoreCase.
Архитектурные векторы: альтернативы
В предыдущем посте мы рассмотрели мейнстримную линейку: от серверных шаблонов до серверных компонентов. Параллельно с ней живут подходы, которые отвергают само допущение, что веб-приложение обязано быть JS/TS SPA с клиентским состоянием и JSON API. Разбираем в карточках альтернативные архитектурные подходы к фронтенду.
#frontendvkhub #обзор #эволюция_веба
Архитектурные векторы: мейнстрим
Сегодня в продакшене одновременно работают разные архитектурные подходы: серверные шаблоны, jQuery, SPA, SSG, SSR, islands и Server Components. Эволюция веба — это не смена победителей, ведь каждая модель отвечала на свои вызовы. Разбираемся, как происходило развитие архитектур веба.
#frontendvkhub #обзор #эволюция_веба
Customizable select: нативный дропдаун, который наконец стилизуется<select> больше двадцати лет оставался чёрным ящиком. Шрифт поменять можно, иногда — фон, дальше начинается территория операционной системы: своя стрелка, свои отступы, свой список опций, который рисуется вне окна браузера и никакому CSS не подчиняется.
Дизайнер просит фирменный шеврон и скруглённые опции — разработчик идёт за Choices.js или Select2. Библиотека выкидывает нативный элемент и собирает вместо него стопку из div, после чего клавиатурную навигацию, ARIA-атрибуты и работу со скринридерами восстанавливают вручную. Тридцать килобайт и постоянная поддержка — цена за одну стрелку.
Customizable select убирает этот обмен. Команда Safari показала его на WWDC 2026, работает он в Safari 27 и Chrome 135, Firefox прототипирует за флагом.
Включается одним значением, но объявить его нужно дважды — на самом элементе и на выпадающем списке:
.custom-select {
&,
&::picker(select) {
appearance: base-select;
}
}<select> в обычное дерево элементов. Список опций уезжает в top layer, как поповер, и позиционируется через anchor(), а значит, больше не обрезается родителем с overflow: hidden.::picker(select) — сам список, ::picker-icon — стрелка, ::checkmark — галочка у выбранной опции, :open — состояние раскрытого списка, :checked — текущая опция. Обычные селекторы, к которым применяются переходы и анимации.<selectedcontent> — он зеркалит содержимое выбранной опции внутрь кнопки. В той же опции теперь можно класть настоящую разметку: флаг картинкой, иконку, вложенный span с описанием. Раньше <option> принимал только текст, остальное браузер молча выбрасывал.<select class="custom-select">
<button>
<selectedcontent></selectedcontent>
</button>
<option value="ru">
<img src="/flags/ru.svg" alt=""> Россия
</option>
</select>
<select>. Фолбэков писать не нужно, а если хочется явно развести стили — есть проверка через @supports (appearance: base-select).
Подсветка текста, которая не трогает 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;
}
Range спокойно тянется через несколько элементов, потому что ничего оборачивать не требуется.::highlight(name) и своими стилями. Для совместной работы это то, что нужно: выделение каждого участника получает своё имя и свой цвет, а снимается всё через CSS.highlights.delete('name') или разом через clear().color, background-color, text-decoration, text-shadow и обводка. Геометрию поменять нельзя, background-image игнорируется. Ограничение осмысленное: раскладка от подсветки не зависит, поэтому браузеру не нужно пересчитывать её при каждом изменении диапазонов.<mark> со стилями по умолчанию, а поверх включить ::highlight() через @supports selector(::highlight(search)). Читатель без поддержки видит семантическую подсветку, остальные — динамическую, и никто не остаётся с пустой страницей.<mark>.
Функции и условия в CSS без препроцессора
Кастомные свойства решили половину задачи: значение можно вынести в переменную и переиспользовать. Но переменная хранит ровно то, что в неё положили, — посчитать что-то на её основе она не умеет. Отсюда Sass, который считает на этапе сборки и потому не видит того, что происходит в браузере.
Теперь CSS умеет это сам. @function описывает функцию с аргументами и типами, if() возвращает разные значения по условию — и то и другое работает во время выполнения, с живыми значениями переменных.
#frontendvkhub #css
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'));async function waitForClick(button) {
const { promise, resolve } = Promise.withResolvers();
button.addEventListener('click', resolve, { once: true });
return promise;
}
const evt = await waitForClick(saveButton);return promise.const { promise: ready, resolve: markReady } = Promise.withResolvers();
export function initSDK(config) {
setup(config).then(() => markReady());
}
export { ready };await ready перед использованием SDK. Без withResolvers пришлось бы либо экспортировать промис отдельно, либо городить lazy-инициализацию.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));const { promise, resolve } = Promise.withResolvers<string>();
resolve('hello'); // ok
resolve(42); // errorPromise.withResolvers() создаёт промис, у которого resolve и reject доступны снаружи. Удобно, но может быть опасно: любой, кто получил ссылку на resolve, может завершить промис из произвольного места. Не пробрасывайте эти функции наружу модуля без нужды — держим их приватными, отдаём только сам промис.let resolve; new Promise(r => resolve = r) — время заменить на Promise.withResolvers. Одна строка, читается очевидно, TypeScript выводит типы сам.
Читать полностью…
🔵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 #дайджест
Форматирование и локали в платформе Intl
Intl — встроенный набор классов для форматирования и сравнения с учётом локали. Большинство фронтов тащит date-fns ради «2 дня назад», lodash для сортировки, ручные функции для денег и процентов. Почти всё это уже есть в браузере, причём заметно мощнее, чем кажется.
В карточках рассмотрим шесть классов — каждый со своим случаем, и почти каждый поднимает вопрос «А что, так можно было?»
#frontendvkhub #intl
🔵 Спецификация современного сайта
Вышел чек-лист из 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
Самая недооценённая штука в современном 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 элементов
const result = bigArray.values()
.filter(x => x.active)
.map(x => x.id)
.take(10)
.toArray();
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) дёргает источник ровно до десятого совпадения.const tags = new Set(['react', 'vue', 'svelte']);
const upper = Iterator.from(tags)
.map(t => t.toUpperCase())
.toArray();
// ['REACT', 'VUE', 'SVELTE']
document.querySelectorAll('li').values().filter(...).take(5) — без обхода всего DOM.for...of? Делитесь кейсами в комментариях..
Несколько лет работа с 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
reactCompiler перешёл из разряда экспериментальных в стабильную top-level-настройку. По умолчанию он выключен, команда next.js собирает данные о времени сборки. Дополнительно ставится babel-plugin-react-compiler, в Vite та же связка выполняется через babel-конфиг. Отдельно eslint-plugin-react-compiler — он подсветит места, где компилятор откажется работать.useMemo и useCallback при этом не исчезают. Они остаются как escape hatch для явного контроля: например, если мемоизированное значение идёт с зависимостью useEffect, и эффект не должен пересрабатывать на каждое перевычисление. Эвристики компилятора иногда мемоизируют чуть шире или чуть уже, чем нужно конкретному эффекту.useMemo заранее. В существующих кодовых базах порядок другой: сначала прогон ESLint-плагином, фикс нарушений Rules of React, потом флаг, и наконец постепенная зачистка избыточных мемо. Главное изменение в практике: идентичность ссылок перешла в детали реализации компилятора, и обсуждать её на ревью больше не имеет смысла.
🔹 Любой фронтендер может сделать приложение для VK Mini Apps и заработать на нём. Без команды, без инвестиций, в одиночку.
Дима Сединкин рассказал Коду Дурова, как запустил конструктор лид-форм для сообществ ВКонтакте. Доход растёт в среднем на 110% в месяц, среди клиентов — федеральные фитнес-сети и клиники по всей России.
В статье Дима подробно разбирает путь от универсального конструктора форм к узкой нише, почему «мыслить как разработчик» оказалось ловушкой, и какие возможности VK Mini Apps реально конвертят в выручку — автозаполнение, фиксация упущенных лидов, подписка на сообщество из формы.
🔹 Читайте кейс целиком: https://kod.ru/kak-frontend-developer-zapustil-svoi-pervyi-business
#frontenvkhub #miniapps
Vite+ — не новая версия Vite и не его замена. Это отдельная утилита, которая объединяет инструменты вокруг Vite в один CLI: Vite 8, Vitest 5, Oxlint, Oxfmt, Rolldown и tsdown.
28 сентября VoidZero выпустила первый стабильный релиз. Разбираемся вместе с Петром Гилевичем, руководителем веб-разработки контентных сервисов ВКонтакте, как там всё устроено.
В чём идея
Фронтенд-проект требует набор инструментов: сборщик, тесты, линтер, форматтер, сборка npm-пакетов. Каждый обновляется сам по себе, у каждого своя конфигурация, а локальное окружение и CI расходятся — привет, worked on my machine.
Заявленные кратные ускорения касаются линтинга и нового ядра сборки в Vite 8. Версии всех инструментов подобраны и протестированы вместе, обновляются одной командой и гарантированно работают в связке.
Как устроено
Глобальная утилита vp — отдельная программа, ставить через npm не нужно. Она смотрит, какая версия Node.js нужна проекту, скачивает её и переключает при переходе между проектами.
В самом проекте вместо отдельных Vite, Vitest и Oxlint в зависимостях остаётся один пакет vite-plus — версии инструментов зашиты в него. Есть нюанс: если доставить какой-то инструмент отдельно, можно случайно получить вторую его копию и сломать связку — документация предупреждает об этом.
Vite Task в монорепозитории и кеширование сборки
Если вы разрабатываете несколько пакетов в одном репозитории, рутину — прогоны тестов и другие пайплайны — берёт на себя движок Vite Task. Он умеет то, ради чего раньше отдельно ставили Turborepo.
Во-первых, он видит, какой пакет от какого зависит, и запускает сборки в правильном порядке: сперва библиотека, потом зависящее от неё приложение. Внутри одной сборки порядок модулей и так определяет сборщик — а вот очерёдность сборок между пакетами одного репозитория как раз зона ответственности Vite Task.
Во-вторых, он кеширует сборки: vp run build --cache снимает fingerprint зависимостей модуля. Запустите команду снова, не меняя код, — сборка полностью пропустится, и результат мгновенно вернётся из кеша. Пока кеш хранится локально, но у Vite+ в планах удалённый — как у конкурентов, в том числе для использования на CI.
Как Vite+ стал открытым
Изначально Vite+ планировали сделать платным. Но до публичного релиза от платной модели отказались: код открыли под MIT. Почему передумали — в секции MIT License на сайте VoidZero прокомментировали: «Мы устали спорить, какие функции должны быть платными и как их ограничивать». Летом 2026 Cloudflare купила VoidZero — инструменты остаются открытыми.
Что в итоге
Получаем управление всей экосистемой вокруг Vite одной командой: один апгрейд вместо синхронизации пяти инструментов.
Из коробки есть миграция: vp migrate пытается влить все конфигурации — линтера, форматтера, тестов — в один vite.config.ts, переписывает скрипты package.json на vp-команды и перед изменениями показывает план. Миграция рассчитана на свежие Vite 8+ и Vitest 4.1+; прехуки на Husky (утилита для Git-хуков) остаются как были — их подключают к Vite+ вручную.
Отдельный CLI — знак того, что проект растят как целую экосистему для JS-проектов без привязки к пакетному менеджеру и предустановленному Node. Можно предположить, что дальше мы увидим поддержку других сред выполнения и возможность миграции между ними, но обещаний нет.
Старт мощный: 2,4 млн скачиваний в неделю на начало октября, мейнтейнер с мировым именем. Небольшим проектам, которые уже на Vite, пробовать можно. Крупным, вероятно, надо повременить: недавно Vite сменил ядро сборки на Rolldown, и это был очень серьёзный апдейт.
Непонятно, будут ли подобные обновления в ближайшие год-два. Экспериментальный Bundled Dev Mode может быть причиной новых мажорных изменений — тогда мигрировать придётся не один раз.
#frontendvkhub #vite #что_нового
CSS в 2026 году: где JavaScript уже не обязателен
Открыть поповер, подсветить карточку с выбранным чекбоксом, растянуть textarea под текст — раньше на каждую такую мелочь заводился скрипт, состояние и подписка на события. За два года CSS забрал у JavaScript целый пласт таких задач, и часть фич уже прошла порог Baseline.
Максим Кузнецов из команды веб-версий ВКонтакте разобрал, что из этого можно брать в продакшен прямо сейчас, что подключать осторожно, а за чем пока просто следить.
➡️В карточках — короткая версия с актуальными статусами поддержки. А подробнее читайте на Хабре.
#frontendvkhub #css
Как поделиться данными с воркером и не копировать их
Воркер получает данные через postMessage, и браузер по умолчанию копирует всё, что вы туда положили. На массиве в 100 МБ копирование само по себе подвешивает главный поток — ровно там, где воркер должен был его разгрузить.
Обойти это можно двумя путями. Первый — отдать буфер воркеру насовсем: делается одной строкой и работает везде. Второй — открыть память, в которую оба потока пишут одновременно.
Второй путь мощнее, но браузер за него просит дорого. Придётся отгородить сайт от чужих скриптов, видео и виджетов — и разбираться, что из этого после отвалится. Подробнее рассказываем в карточках.
#frontendvkhub #воркер
VK Frontend Meetup SPB — про веб-анимации, AI и автоматизацию
30 сентября в Санкт-Петербурге встречаемся с фронтенд-разработчиками на VK Frontend Meetup SPB. В этот раз разберём три слоя фронтенда: то, что видит пользователь, то, как мы создаём интерфейсы, и то, как масштабируем разработку.
Поговорим о практических задачах, которые возникают в больших продуктах: как стабильно рендерить много анимаций, как научить AI верстать по правилам проекта, а не «примерно похоже», и как автоматизировать типовые задачи, чтобы запускать сотни промо без постоянного участия разработчиков.
В программе:
🔵Артем Стукалов, МАХ — слой интерфейса и производительности. Расскажет, как команда пришла к собственной архитектуре для веб-анимации стикеров. Разберём, почему одного Lottie оказалось недостаточно, что изменилось после перехода на собственное решение и как удалось стабильно рендерить большое количество анимаций без лагов.
🔵 Эмиль Янгиров, Одноклассники — слой создания интерфейсов и AI. Поговорим о том, почему AI пока не умеет по-настоящему верстать production-код. Обсудим, как подготовить Figma и дизайн-систему к работе с AI-агентом, передать ему правила проекта, не дать выдумывать компоненты и автоматически проверять адаптивность, доступность и визуальное соответствие.
🔵 Александр Сарафанников, Почта Mail — слой масштабирования и автоматизации. Покажет, как запускать 300 промо в год за 10 минут. Это история о том, как команда прошла путь от единого места с коммуникациями и ручной работы разработчика до платформенного решения, которое позволяет запускать типовые промо сразу в нескольких проектах — без отдельной задачи для разработчика.
Митап полностью офлайн — записей докладов не будет. Зато после основной программы останемся на фуршет и неформальное общение: можно будет задать вопросы спикерам, обсудить доклады и обменяться опытом с коллегами.
Митап бесплатный, но для участия обязательна регистрация.
Приходите — разберём фронтенд слой за слоем.
#frontendvkhub #митап
🔵18 августа вышел Firefox 154 — самый заметный для вёрстки релиз месяца.
Появились text-box-trim, text-box-edge и шорткат text-box. Проблема, которую они решают, знакома любому, кто верстал заголовки по макету.
Шрифт несёт с собой пространство над и под строкой, причём у каждой гарнитуры своё: у одной сверху остаётся два пикселя, у другой шесть. Дизайнер в Figma видел текст, посаженный по оптическому краю, а в браузере тот же блок съезжал. Фиксилось это отрицательными марджинами, подобранными вручную, а при смене шрифта подбор начинался заново. Новые свойства срезают лишнее на уровне движка, и блок встаёт туда, где его нарисовали.
Вторая пара — sibling-count() и sibling-index(). Первая возвращает число соседних элементов вместе с самим элементом, вторая — порядковый номер, счёт с единицы, а не с нуля.
Раньше для стилизации по позиции шаблонизатор или скрипт проставлял каждому элементу инлайновый --index, от которого уже считались задержка анимации, толщина, прозрачность — список из полусотни карточек означал полсотни инлайновых стилей в разметке. Теперь это делает сам CSS, без nth-child-арифметики.
🔵Что зреет за флагамиline-clamp заработал без вендорного префикса, правда пока без значения no-ellipsis, то есть обрезка с многоточием работает, а без многоточия ещё нет. Функция progress() считает значение по положению между минимумом и максимумом — заготовка для плавных зависимостей без акробатики с вложенными clamp().
В Nightly отдельно подъехала CSS Typed Object Model: работа со стилями из JS через типизированные объекты вместо разбора строк. До стабильной ветки ей далеко, но направление понятное.
🔵25 августа Chrome 152 ушёл в стейбл.::backdrop, ::scroll-marker и ::view-transition теперь доступны как полноценные объекты CSSPseudoElement, а не только как селекторы — закрыть диалог по клику на фон можно без ручного сравнения координат. Плюс появился relative alpha colors (alpha() из CSS Color 5) для точечного управления прозрачностью существующего цвета.
Свойство window-drag размечает области установленного веб-приложения, за которые окно двигается как за заголовок. Кастомный тайтлбар в PWA перестал требовать возни с обработчиками мыши и вычислением дельты курсора.
#frontendvkhub #дайджест
Speculation Rules: как готовить страницу до того, как по ней кликнули
Между кликом по ссылке и появлением новой страницы всегда есть пауза. Сервер может ответить за 30 миллисекунд, но дальше браузеру предстоит разобрать HTML, дотянуть стили и шрифты, выполнить скрипты — и человек всё это время смотрит на старую страницу.
Speculation Rules переносят эту работу на время до клика. Пока пользователь читает текущую страницу, браузер готовит следующую в фоне, а после клика показывает уже готовую.
В карточках — чем prefetch отличается от prerender, как настраивается момент срабатывания, какие лимиты держит Chrome и что делать с аналитикой, которая успевает выстрелить раньше визита.
#frontendvkhub #html #speculation_rules
Как приоритет слоёв побеждает специфичность в 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. Внутри одного слоя специфичность работает как обычно, между слоями она не значит ничего.@layer разрешён до @import, и ставить его первым надёжнее: порядок задан явно, а не собирается из очерёдности импортов.@layer reset, vendor, base, components, utils;
@import url("normalize.css") layer(reset);
@import url("bootstrap.css") layer(vendor);
!important. Всё, что написано вне слоёв, живёт в неявном верхнем слое и перебивает любой именованный, поэтому миграция идёт мягко: @layer подключается в новом коде, старый unlayered работает поверх и переезжает по частям.!important порядок слоёв инвертируется, important из reset перебьёт important из utils. Менее очевидная часть той же механики: unlayered important оказывается слабее любого слоя. Правка, которая годами держала переопределение поверх всего, после переезда библиотеки в слой молча перестанет работать.@layer components { @layer forms, buttons, cards; }. Приоритет считается по иерархии, а весь блок components занимает одно место в глобальном порядке. Рабочая раскладка обычно укладывается в шесть слоёв: reset, tokens, base, components, utils, overrides.@layer в статусе Baseline widely available. Единственное — Safari 15.3 и старше выбрасывает содержимое слоя целиком, без @supports там будет пустая страница.
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 и вложенное. Решает классическую задачу: статья и блоки комментариев стилизуются независимо, но рендерятся внутри одной иерархии.<style> внутри HTML. Тег <style> с @scope без селектора привязывается к родителю:<div class="widget">
<style>
@scope {
h3 { color: teal; }
button { background: navy; }
}
</style>
<h3>Заголовок</h3>
<button>Кнопка</button>
</div>
<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. @scope часто закрывает задачу без сборочного шага.
Temporal API
Почти в каждом проекте найдётся место, где дату надо сдвинуть на месяц вперёд или посчитать разницу между двумя днями. И почти всегда ради этого в зависимостях лежит date-fns или dayjs, потому что штатный Date для такой работы не приспособлен: он мутабельный, нумерует месяцы с нуля и знает ровно одну зону, системную.
Temporal переносит эту арифметику в сам язык, и за последний год он перестал быть предметом ожидания. В Firefox поддержка появилась ещё в 139, в январе 2026 её добрали Chrome и Edge в версии 144, а в марте предложение дошло до Stage 4 и вошло в ES2026. Поэтому разговор сместился к переводу существующего кода, и об этом расскажем в карточках: какие в Temporal типы, как он считает даты и зоны и как всё это стыкуется со старым Date.
#frontendvkhub #temporal
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));
}
}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). Нормальный планировщик с приоритетами вместо «что-то в очередь и забыли».scheduler-polyfill падает на setTimeout(0) там, где нативного нет, так что в проде уже можно.setTimeout(0) или await new Promise(r => setTimeout(r, 0)) для разбиения тяжёлых циклов — самое время посмотреть на scheduler.yield(). Меньше задержек, приоритет не теряется, прогресс-бары и анимации не страдают.
Продолжаем рубрику «Знакомство с командой». Сегодня свою историю расскажет Денис Гордеев, руководитель подразделения Core Frontend ВКонтакте.
➡️ От школьного фана к фронтенду
Свой первый веб-проект я сделал ещё в школе: это была система для проведения олимпиад по программированию. Поступил в МГУПИ на специальность АСОИУ («автоматизированные системы обработки информации и управления») и со второго курса начал параллельно работать фулстек-разработчиком в разных компаниях. С каждым новым опытом перекос в сторону фронтенда становился всё сильнее.
В итоге я решил, что хочу заниматься именно фронтендом. Мне нравится эта связь между инженерными решениями, качеством продукта и пользовательским опытом.
➡️ Над чем работаешь
Сейчас я руковожу подразделением Core Frontend ВКонтакте. Мы отвечаем за весь фронтенд vk.com, m.vk.com и за библиотеки компонентов VKUI и VKCOM kit.
Работаем по нескольким направлениям: повышаем стабильность и надёжность платформы, развиваем архитектуру и инфраструктуру, BFF, серверный рендеринг, UI-тестирование и тестовые домены.
Эти слои фронтенда не всегда видны пользователю, но они сильно влияют на скорость разработки фич и удобство работы внутри большой кодовой базы.
➡️ Почему роль лида — это постоянный баланс
В своей работе лид должен соблюдать баланс между людьми, процессами и продуктом. Одновременно ему нужно учитывать текущие потребности команды и бизнеса. Иногда нужно глубже погрузиться в архитектуру продукта, иногда — помочь коллегам. Моя задача — вовремя перераспределять внимание и ресурс туда, где это даст максимальный эффект.
➡️ Что сложно
При переходе из роли разработчика в роль лида самым сложным было принять, что я уже не могу и не должен тащить руками тот же объём, что раньше.
Достаточно долго я продолжал работать как полноценный разработчик и как руководитель одновременно. В итоге это приводило к огромным переработкам и полному отсутствию баланса между работой и личной жизнью.
Переход в лидерскую роль требует перестройки мышления. Ты уже не только сам решаешь задачи, но и создаёшь условия, в которых команда может решать их лучше и быстрее.
➡️ Что заряжает
Меня заряжает моя команда. У нас настоящие профессионалы своего дела. Ребята настроены на амбициозные стройки и сильные результаты, все на одной волне и двигаются в одном направлении. При этом у нас тёплая атмосфера, поддержка и взаимовыручка. Для меня это вторая семья.
➡️ Как не выгораешь
У меня есть фраза: «Гори, чтобы светить». Мне важно получать удовольствие от работы. Интерес и ощущение смысла позволяют мне не выгорать. Я вижу результат и понимаю, как он помогает пользователям.
Если устаю, то лучше всего переключает смена деятельности. Например, после большого количества встреч очень приятно поработать руками: набросать MVP для какой-нибудь задачи из бэклога или провести исследование по проблеме, на которую у команды пока не хватает времени.
➡️ Как отдыхаешь
Люблю настолки и компьютерные игры — они отлично помогают переключиться и разгрузить голову.
Ещё учусь играть на гитаре. Это хороший способ расслабиться и отвлечься, особенно когда ощущаешь прогресс.
➡️ Что бы ты сказал себе в начале карьеры
Я бы посоветовал себе как можно раньше попасть в энтерпрайз. Небольшие локальные компании — это тоже круто, там можно получить важный опыт. Но именно в бигтехе появляются уникальные вызовы и масштабные проекты, которые сложно заменить чем-то другим. Здесь быстрее понимаешь, как устроены большие системы, как работают команды, процессы, инфраструктура и продукт, которым пользуются миллионы людей.
#frontendvkhub #команда
@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
Синхронизация состояния между вкладками — пять браузерных API
Когда у пользователя несколько вкладок одного приложения, обычный state не работает. Логин в одной будет подхвачен в других. Открытый сокет в одной — лишний груз в остальных.
В карточках разберём, как пять браузерных API закрывают разные части этой задачи.
#frontendvkhub #api
🔵Анимации с ощущением физики:
разбор squash and stretch приёма из классической анимации, при котором объект сжимается и растягивается в процессе движения. В интерфейсах обычно ограничиваются bounce, scale или slide, а деформацию не добавляют, но именно она делает движение живым. В статье интерактивные демки на SVG, CSS и библиотеке Motion.
🔵 Chrome DevTools получили CLI с поддержкой MCP.
Гайд о том, как подключить DevTools к автоматизированному воркфлоу, чтобы быстрее получать полезные данные о странице. Примеры построены вокруг WebPerf.
🔵 Что нового в JavaScript:
что уже в ES2025, что близится к ES2026 и что можно использовать уже сейчас. Отдельная часть — про совместимость новых возможностей языка с ИИ агентами при генерации и поддержке кода.
🔵 Как Google Code Assist встраивается в работу с React в VS Code.
Агент помогает с генерацией компонентов, хуков, тестов и бойлерплейта. Результат нужно ревьюить, проверять зависимости хуков и приводить код к командным соглашениям.
🔵 TanStack Form:
headless-библиотека для форм с сильной типизацией. Внутри useForm с автокомплитом по defaultValues, несколько режимов валидации, динамические списки полей, реактивный доступ к значениям других полей и композиция field-компонентов. Headless-подход даёт контроль над UI без навязанных стилей и лишних зависимостей.
🔵 Нативный random в CSS:
разбор random() и random-item() из CSS Values and Units Level 5. Автор объясняет, почему это не просто удобная фича, а шаг к генеративному CSS: меньше костылей через Sass, :nth-child и JavaScript, больше возможностей прямо на уровне layout и стилей.
#frontendvkhub #дайджест
Новый pnpm 11 меняет несколько базовых вещей одновременно: рантайм, формат модулей, движок хранения, публикацию, глобальные установки и политику безопасности. Минимальная версия рантайма — Node.js 22+, поддержка предыдущих версий прекращена. Сам pnpm теперь распространяется исключительно в формате ESM, получает хранилище на SQLite, а pnpm init по умолчанию задаёт "type": "module".
Хранилище на SQLite
Индекс хранилища переехал из миллионов JSON-файлов в единую базу SQLite по пути $STORE/index.db. Метаданные пакетов хранятся в бинарном формате, а манифест записан прямо в индекс, поэтому новый pnpm больше не читает package.json каждого пакета при разрешении зависимостей. Это даёт сокращение числа системных вызовов и операций чтения. Для монорепозиториев с большими графами зависимостей это ощутимое снижение накладных расходов.
Записи теперь идут напрямую в content-addressed пути. SQLite работает в WAL-режиме для безопасного параллельного доступа. Также добавлены pre-allocation памяти для .tar.gz с известным размером и NDJSON-кэш метаданных.
Нативная публикация
pnpm больше не делегирует команды публикации в npm. Команды publish, login, logout, view, deprecate, unpublish, dist-tag и version реализованы нативно, что убирает зависимость от поведения npm. Новый pnpm теперь сам обрабатывает OTP-поток, интерактивные промпты и веб-авторизацию.
Политика безопасности
Несколько настроек по умолчанию стали строже. minimumReleaseAge теперь 1440 минут. Свежеопубликованные версии пакета теперь недоступны для установки в течение 24 часов. Идея в том, что вредоносный код обычно обнаруживается быстро, и суточная задержка снижает риск установки заражённого релиза.blockExoticSubdeps стал по умолчанию true: транзитивные зависимости не могут подтягиваться из прямых ссылок на .tar.gz. Добавлен trustPolicy с политикой no-downgrade, когда доверие к пакету не может ухудшаться со временем. strictDepBuilds теперь по умолчанию тоже true, т.е. скрипты сборки выполняются только для явно разрешённых пакетов. pnpm audit перешёл на фильтрацию по GHSA-идентификаторам, поскольку npm registry отключил CVE-ориентированные эндпоинты, а настройка auditConfig.ignoreCves теперь заменена на auditConfig.ignoreGhsas. Глобальные установки стали изолированными: каждый pnpm add -g получает собственную директорию, node_modules и lockfile. Это исключает конфликты между глобальными пакетами.
Конфигурация.npmrc сведён к настройкам npm registry и авторизации. Специфичные для pnpm параметры перенесены в pnpm-workspace.yaml или глобальный config.yaml. Переменные окружения npm_config_* больше не используются, поэтому CI-системы, которые на них полагались, требуют ручных исправлений.
Сборочные флаги из pnpm 10 объединены в allowBuilds. Параметр ignorePatchFailures удалён, ошибки патчей теперь вызывают прерывание по умолчанию. Для миграции есть скрипт pnpm-v10-to-v11, который покрывает большинство механических изменений.
Миграция
Существует заметный риск для enterprise окружений. Тулчейны, которые полагаются на require() загрузку внутренних модулей, могут сломаться. Такие ошибки часто обнаруживаются только на этапе сборки в смежной команде.
Теперь pnpm 11 безопаснее против атак на цепочку поставок, но требует явного управления политиками при быстром цикле релизов.
#frontendvkhub #pnpm11