Настя Котова // Frontend & Node.js
840 subscribers
42 photos
2 files
119 links
Фронтендерица с лапками 🐾
Посты каждый понедельник 💃 Копаюсь во внутрянке технологий и рассказываю вам
Download Telegram
В одной из частей цикла про V8 я рассказывала, почему важно сохранять массивы однородными и без «дыр» — тогда движок может их эффективнее оптимизировать.

В блоге V8 есть отличная статья, где показано, как можно посмотреть, какой тип элементов сейчас у массива и от чего он меняется. Ниже — краткий гайд, как повторить это у себя локально.

Нам понадобится отладочная (debug) сборка V8 — с ней можно смотреть не только на типы элементов, но и, например, как движок оптимизирует или деоптимизирует код.

1. Сначала ставим depot_tools, чтобы получить утилиту gclient.
2. Потом по инструкции из документации подтягиваем исходники V8.
На macOS важно: если у вас установлены только XCode Command Line Tools, их нужно удалить и поставить полноценный XCode. Подробности — здесь.
3. Дальше собираем движок:

gclient sync
cd /path/to/v8
git pull && gclient sync
tools/dev/gm.py arm64.debug # debug-сборка для arm на macOS


У меня сборка шла очень долго, поэтому лучше сразу собирать правильную версию (release или debug). Debug-версия обладает бОльшими возможностями для логирования разной информации.

После сборки можно запустить движок в REPL-режиме:

v8/out/arm64.debug/d8 --allow-natives-syntax


Флаг --allow-natives-syntax позволяет использовать специальные отладочные функции, такие как %DebugPrint(array):

d8> const array = [1, 2, 3]; %DebugPrint(array);


Пример вывода:

DebugPrint: 0x2dcc00389c0d: [JSArray]
- map: 0x2dcc0005b7d1 <Map[16](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x2dcc0005b7f9 <JSArray[0]>
- elements: 0x2dcc0006ca25 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x2dcc000007bd <FixedArray[0]>
.....


Можно и просто передать файл с кодом:

v8/out/arm64.debug/d8 --allow-natives-syntax ~/Documents/all-examples/v8/test.js


А если добавить флаг --trace-elements-transitions, то движок будет печатать все изменения типа:

// test2.js
const array = [1, 2, 3];
array[3] = 4.56;



v8/out/arm64.debug/d8 --trace-elements-transitions ~/Documents/all-examples/v8/test2.js


Результат:

elements transition [PACKED_SMI_ELEMENTS -> PACKED_DOUBLE_ELEMENTS] in ~+15 at test2.js:1 for 0x2cf600389c45 <JSArray[3]> from 0x2cf60006ca0d <FixedArray[3]> to 0x2cf600389c55 <FixedDoubleArray[22]>


Дальше можно экспериментировать: добавлять в массив undefined, -0, пропущенные индексы — и смотреть, как V8 сразу меняет внутренний тип. Это наглядный способ понять, как движок анализирует наш код и почему иногда одно лишнее значение или неправильная инициализация может замедлить выполнение.
👍11🔥6
Как можно профилировать память в Node.js?

Один из базовых способов — heap snapshot через Chrome DevTools. Для этого нужно запустить Node с флагом --inspect: node --inspect app.js.
После запуска можно открыть в браузере chrome://inspect и подключиться к процессу в DevTools и на вкладке Memory снять снимок кучи. Он показывает, какие объекты занимают память.

Если же мы хотим отслеживать метрики памяти в реальном времени, то можно сделать это с помощью PerformanceObserver. Он слушает события, создаваемые системой perf_hooks. Например, можно смотреть, как растёт heap и когда происходят сборки мусора:

import { PerformanceObserver, performance } from 'node:perf_hooks';

const obs = new PerformanceObserver((items) => {
for (const entry of items.getEntries()) {
console.log(`[${entry.entryType}]`, entry.name, entry.duration.toFixed(2), 'ms');
}
});

obs.observe({ entryTypes: ['gc'] });


Если нужно больше деталей о работе V8 и нет времени писать лишний код, можно просто запустить процесс с флагом --trace_gc: node --trace_gc app.js.
В консоли появятся строки вроде:

[89452:0xa81400000] 3103 ms: Scavenge 18.9 (27.7) -> 18.7 (50.7) MB, pooled: 0 MB, 4.29 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure;

Здесь видно тип сборки мусора, её длительность и изменение размера кучи.

Ну а самый простой и быстрый способ получить обзор текущего состояния, и, скорей всего, вы про него уже не раз слышали — метод process.memoryUsage. Он возвращает данные по основным сегментам памяти:

const mem = process.memoryUsage();
console.log(`Heap used: ${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB`);
console.log(`RSS: ${(mem.rss / 1024 / 1024).toFixed(2)} MB\n`);
👍127👌2🤝1
Как много мы здесь говорили про что-то фундаментальное последнее время — про нативные модули в Node.js, про libuv, про V8… Ещё и исходный код многих этих вещей написан не на JavaScript.
Мне захотелось почитать чего-то родного, немножечко моих любимых js-исходников. И так родилась следующая тема.

Как работают сборщики: Webpack

Сегодня начнём говорить про сборщики и про то, как они работают. Пока в планах две части — первая про Webpack, вторая про Vite. Но, возможно, какие-то частные моменты я захочу раскрыть и покопать сильнее.
Также, если вам интересно что-то конкретное — мои комментарии и сообщения в этом канале всегда открыты)
1🔥371😁1
В продолжение разговора про Webpack стоит немного поговорить про code splitting.

На заре сборщиков мы жили в достаточно простом мире: был один entry point — был один большой выходной файл. Это работало нормально, пока проекты не начали разрастаться. Оказалось, что огромный бандл — это отсутствие нормального кеширования, необходимость перекачивать весь код заново при любых изменениях и замедления загрузки.

Из этой боли родилась идея code splitting. Какое-то время для этого использовали только lazy-импорты, и разработчики сами должны были подсказывать сборщику, где стоит разрезать проект. Но со временем стало понятно, что это ограничивает архитектуру. Поэтому последние версии Webpack (начиная с 4-й) пошли другим путём: они научились анализировать граф модулей глубже и стали достаточно “умными”, чтобы делить код даже без lazy-импортов.

Магия спрятана в настройках оптимизации. Например, можно включить splitChunks для всех чанков и указать желаемый максимальный размер:

module.exports = {
entry: './src/index.js',
optimization: {
splitChunks: {
chunks: 'all',
maxSize: 8000
}
},
...
}


Теперь Webpack смотрит на итоговый entry-чанк, понимает, что он слишком большой, и начинает аккуратно вырезать из него фрагменты модулей, формируя дополнительные чанки. Импорт остаётся обычным, при этом код распределяется по нескольким файлам, которые браузер загрузит параллельно и будет кешировать независимо.

Результат на моём маленьком демо:

Было: main.js (24KB)

Стало:
├── main.js (3.3KB)
├── 648.js (3.1KB)
├── 14.js (2.6KB)
├── 339.js (2.2KB)
└── 812.js (1.4KB)


Все файлы будут загружены сразу, и это ожидаемое поведение: вебпак просто оптимизирует структуру бандла, но не делает его динамическим. Для ленивой загрузки по-прежнему нужны import(). Но в ситуациях, когда проект сложно разделить руками, а хочется хотя бы улучшить кеш и скорость начальной загрузки за счёт нескольких параллельных файлов, такая оптимизация работает хорошо — особенно в сочетании с переходом на HTTP/2.

При этом полностью отказываться от объединения модулей (.ts, .css, .tsx и т.д.) в чанки не стоит даже при работе с HTTP/2 — это показывают исследования и результаты бенчмарков (пример). Поэтому code splitting в Webpack является хорошим компромиссным решением, которое повышает скорость загрузки.

UPD: В комментариях также есть интересные рассуждения на тему причин появления code splitting и его эффективности.
8🔥2👍1
Сегодня день начался не с кофе, а с внепланового обновления React и Next.js 💃

Если вы пропустили, вчера была исправлена критическая уязвимость в Rect 19 и Next.js 15, связанная с серверными компонентами. Также затронуло ещё несколько пакетов.
Для исправления достаточно обновить patch-версии затронутых библиотек на своём проекте.

Подробнее можно почитать в блоге React — https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1🔥1
Наконец закончила вторую часть про Vite! Больше всего конечно посвятила разбору его работы в dev-режиме, а так же Hot Module Replacement.

Как работают сборщики: Vite
🔥18👍8😁1
Уязвимости React — причины и выводы

За последние пару недель вокруг React Server Components и server actions вскрылся целый пласт уязвимостей (вот и вот). В чём же была проблема?

Server actions выглядят в коде как обычные коллбеки: мы пишем функцию с use server, вызываем её из компонента — и всё. Но под капотом это POST-запрос на сервер. Клиент отправляет payload, сервер его десериализует, понимает, какой серверный модуль нужно вызвать, и выполняет этот код уже на своей стороне. И вот именно в этом месте была самая первая, самая критичная уязвимость.

В первой версии сервер доверял данным из запроса слишком сильно. Имя функции, которую нужно вызвать, приходило от клиента и использовалось напрямую для поиска экспорта в модуле. Проверки, что это действительно ожидаемый server action, что это собственное свойство модуля, а не что-то из прототипа, были недостаточно строгими. В итоге получалось, что сервер брал недоверенные данные и использовал их для выбора и выполнения кода. Это и привело к возможности удалённого выполнения кода без авторизации.

После того как эту дыру закрыли, стало понятно, что проблема глубже, чем просто имя модуля. Даже если код больше нельзя выполнить, сервер всё равно принимает сложный payload, парсит его, резолвит модули, оборачивает всё в промисы и пытается корректно обработать ошибочные сценарии. И этим тоже можно было воспользоваться: появились варианты запросов, которые не приводили к выполнению кода, но заставляли сервер долго и дорого думать, зацикливаться на обработке промисов и фактически ложиться по CPU. Так появилась DoS-уязвимость: если нельзя выполнить код, можно попытаться сломать сервер через его внутреннюю логику.

Третья проблема выросла из того же корня. В процессе обработки некорректных server action payload’ов сервер иногда формировал ответы или ошибки, в которых оказывалось слишком много внутренней информации. В отдельных сценариях это позволяло получить исходный код серверных функций или детали их реализации. Код не выполнялся, сервер не падал, но граница между «внутренним» и «внешним» снова оказывалась размыта.

Если смотреть на всё это вместе, становится видно, что это не три независимые уязвимости, а одна цепочка. Серверные компоненты и экшены — это по сути RPC поверх HTTP. И как только клиент начинает передавать серверу не просто данные, а описание того, что именно нужно сделать, безопасность становится гораздо более тонкой материей.

Всё это, на мой взгляд, хороший повод ещё раз подумать о том, что в момент, когда мы как фронтенд-разработчики выходим на сервер, правила немного меняются. Фронтенд уже следит за большим количеством вещей: UI и UX, производительность интерфейса, его состояние, кроссбраузерность и т.д. Просто теперь к этому списку всё чаще добавляются сервер, безопасность, миграции, обратная совместимость и API.

Даже на простых вещах это легко почувствовать. У нас, например, на проекте с BFF однажды возникла волна 404 во время выкатки нового релиза просто потому, что мы переименовали эндпойнты и не заложили многоступенчатую миграцию между старым клиентом и новым сервером. Никто не делал ничего «неправильно», мы просто не подумали об этом сценарии, потому что крайне редко с ним сталкиваемся. И вот такие истории с уязвимостями в React хорошо подсвечивают одну мысль: когда фронтенд становится ближе к серверу, про серверные риски тоже нужно начинать думать чаще.
🔥16💯63😱1🙏1
В следующем году я опять планирую выступать на конференциях. И в этот раз начну с DUMP в Санкт-Петербурге!

Готовлю доклад с темой Ignition, Sparkplug, Maglev, TurboFan: как компилирует V8
Он вышел по следам моего цикла статей в V8, но будет в 100 раз подробнее именно в части работы интерпретатора и компиляторов движка 💫

Презентацию и запись (если она будет) выложу сюда после конференции. Но если есть возможность — конечно же приходите лично!
Подробнее можно почитать тут
🔥17
А ещё к концу года хочу сделать подборку моих самых лучших статей за этот год. Поэтому буду признательна, если проголосуете, что именно вам понравилось больше всего!

(в опросе представлены не все посты, а только самые “жирненькие”, но всегда можно дополнить этот список в комментариях)
👏5
Вот и наступило время (да-да) для итогов 2025 года!

2025 для меня и моего канала выдался хорошим и очень продуктивным. Я нашла направление, в котором мне действительно интересно разбираться и готовить материал, выпустила несколько циклов статей и выросла со 100 подписчиков до 800+ (глядишь, и до тысячи когда-нибудь дойдём!)
А ещё — выступила на двух оффлайн-конференциях и одном онлайн-митапе 💃

Хайлайтами года хочется оставить:

🔥 лучший цикл статей, на ваш взгляд и по статистике
Event Loop в Node.js

⚙️ второе место делят циклы
→ Как работают сборщики (Webpack и Vite) и Погружение в v8

🖤 недооцененный, но один из моих любимых
Погружение в libuv

🚀 самый популярный пост (по количеству пересылок)
Про последние уязвимости в React

лучшие мемы на канале
→ все 💫

Вообще, в начале этого года одним из направлений я выбрала развитие личного бренда. Мне хотелось больше писать и выступать, набирать аудиторию и параллельно прокачивать экспертизу в интересных мне темах. Мир фронтенда бездонный для изучения, но 3–4 года до этого я будто законсервировалась в своих знаниях. Будем честны: обычная работа среднестатистического разработчика не подкидывает тебе постоянно новые технологии и неординарные задачи. А искать что-то самому без чёткой цели или выхлопа работает не для всех.

Для меня этот канал, эти статьи и посты стали мотивацией к регулярному развитию. Весь год я дисциплинировала себя — каждую неделю должна была изучить что-то новое, разобраться в чём-то и рассказать об этом здесь, для вас. И я рада, что такая системность приносит свои плоды (хотя бы в растущем числе подписчиков).

Неизвестно, что будет с индустрией дальше. Может, я и правда поздно залетела во всю эту историю с прокачкой личного бренда. Но как минимум одно точно мотивирует меня продолжать — в декабре 2025-го я знаю гораздо больше, чем в декабре 2024-го. И я не узнала бы и половины из этого, если бы не этот канал. Так что продолжаем!

Ну а пока я ухожу на небольшие новогодние каникулы здесь, чтобы все мы немного отдохнули. Новый материал, как обычно, ждите 12 января!)
С наступающим! 💫
135🔥8👍1👏1
Этот год здесь хочется начать с какого-то не перегружающего поста, потому что, кажется, многие всё ещё отходят от новогодних праздников (я так точно) и только потихоньку въезжают в рабочий режим.

Про ИИ в разработке уже не написал разве что ленивый. В частности, про его использование как рабочего инструмента. Я не буду открывать Америку, а просто поделюсь своим небольшим опытом — вдруг кому-то окажется актуально.

Я не считаю себя ни ярым сторонником, ни противником ИИ. Не согласна с тем, что он заменит всех, но и игнорировать его возможности и плюсы тоже странно. Поэтому на работе я уже несколько месяцев активно использую Cursor. Я всегда работала в VS Code, так что перейти на него было совсем несложно. И вот пара вещей, которые помогли мне приспособить его под себя.

1. Memory Bank для каждого проекта

Сейчас мы работаем сразу над несколькими фронтенд-проектами, и для каждого из них у нас заведён свой Memory Bank. Это специальная папка, где в Markdown-файлах описаны основные правила работы с кодом: от codestyle до архитектуры проекта и примеров реализации популярных задач.

Чтобы Cursor всегда учитывал эту информацию, мы настроили project rules в .cursor/rules и явно указали, где именно лежит Memory Bank. Если вы используете несколько ИИ-тулов, такая папка может быть общей — а дальше каждая тулза просто будет на неё ссылаться.

Инициализировать Memory Bank довольно просто: достаточно попросить Cursor проанализировать проект и сгенерировать черновой вариант. Мы так и сделали, а потом тщательно всё отревьюили (потому что, справедливости ради, в таких генерациях он любит иногда придумывать).

После появления Memory Bank полезность рекомендаций ИИ заметно выросла, по крайней мере, по моим ощущениям. Плюс это избавляет от постоянного повторения одного и того же и одинаковых промптов в духе «сделай аналогично вот этому».

2. Реализация больших фичей через поэтапное планирование

Если с какими-то очень специфичными багами ИИ меня скорее тормозит, чем помогает, то вот при добавлении нового функционала с нуля он может быть отличным помощником. Особенно когда нужно написать сразу несколько компонентов, стили для них, логику в стейт-менеджере и реализовать новые запросы на бэкенд.

Я почти всегда действую по одному и тому же сценарию, и он работает безотказно. Сначала даю ИИ подробный контекст: описание задачи, скрины из макетов, дополнительные детали. Мне тут помогает моя любовь к структуре и документации: я пишу довольно объёмный промпт, насыщая его подробностями. Например, если это форма, я сразу описываю, как должны валидироваться поля и какие компоненты хочется использовать.

Дальше в этом же промпте я прошу ИИ не переходить к реализации, а сначала составить подробный план работ: что именно он будет делать и куда вносить изменения. Я делаю ревью плана, обычно хватает 1–2 итераций правок, чтобы исправить неточности или недопонимания. После этого план полностью готов, и я прошу реализовать его либо целиком, либо поэтапно, если в задаче есть неопределённость.

В итоге это сильно сокращает время на рутину: написание того, что и так уже есть в моей голове, создание однотипных компонентов и вёрстки. В конце всё равно приходится ревьюить, подчищать лишнее, доводить вёрстку до pixel perfect, но делать это на основе уже готового рабочего каркаса куда проще.


На самом деле именно эти две вещи я бы назвала фундаментальными в своей работе с Cursor. Всё остальное — это скорее нюансы и точечные улучшения, вроде использования MCP-серверов. Но в целом ИИ и такие IDE, как Cursor, действительно ускоряют работу и снимают с меня рутинную часть, оставляя мне главное — продумывать реализации и дошлифовывать результат до идеала.
👍19🔥5
Новый год — новый цикл статей!

За последнее время мы обсуждали и V8, и какие-то нюансы работы Node.js. Теперь же пришло время прикоснуться к самому прекрасному во фронтенде — к рендерингу 💫

От кода до пикселей: как работает рендеринг. Часть 1. Браузерные движки.

Первая часть уже ждёт вас 💃
120👍10❤‍🔥3🔥2