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

Если первая часть была больше про историю, то теперь спускаемся вглубь: как именно движок исполняет JavaScript, какие компиляторы внутри него живут и зачем их так много.

Ignition, TurboFan, Sparkplug, Maglev — всё это может звучать пугающе, но на самом деле это слоистая архитектура V8, которую я постаралась разложить по полочкам.

Погружение в V8. Часть 2. Из чего состоит движок.
117👍3
Вышла третья часть моего цикла про V8!

На этот раз про то, как V8 разбирает код и что происходит ещё до первого его выполнения. Посмотрим на AST, preparser, сканер, скоупы и байткод.

Погружение в v8. Часть 3. Парсинг, AST и анализ кода.
415👍2
Я уже немного писала про Garbage Collection ранее в канале, но цикл про V8 будет неполный без отдельной статьи на эту тему, так что вот:

Погружение в v8. Часть 4. Управление памятью и сборка мусора.

В новой части разбираемся, как движок управляет памятью, оптимизирует сборку мусора и не только.
🔥211
Новая часть цикла про V8 ждёт вас.

И в ней не только изучим теорию некоторых интересных оптимизаций, но и посмотрим на прикладные советы для нашего повседневного JS, которые помогут движку делать свою работу ещё лучше! 🙊

Погружение в v8. Часть 5. Скрытые оптимизации.
🔥16
Финал цикла про V8!

Здесь рассмотрим, как движок взаимодействует с браузером, Node.js и WebAssembly, а также поговорим про будущее.

Погружение в v8. Часть 6. От среды к среде.

Этот цикл был для меня небольшим путешествием внутрь движка.
Надеюсь, читать его было так же интересно, как мне — писать ❤️
114🔥5
Интересная тема — как вообще организовывается авторизация в разных проектах.

У кого-то это JWT-токены (тут я когда-то писала про них), у кого-то — сессионные куки, у кого-то и то, и другое.
JWT удобен тем, что сервер выписывает токен, подписывает своим ключом, а клиент просто прикладывает его к каждому запросу. Но если токен утёк, то всё равно до конца его срока жизни он будет валиден — нет никакого механизма отзыва. Поэтому такие токены делают короткоживущими и добавляют второй — refresh-токен, чтобы обновлять сессию.
Дальше начинается самое интересное — где хранить этот refresh-токен. В localStorage небезопасно, в куках — только если httpOnly. А в чистом SPA это уже сложнее, потому что нельзя управлять такими куками с фронта.

Тут кто-то может начать задумываться про Backend-for-Frontend (BFF). Но, как по мне, делать BFF только ради авторизации — странное решение. Это может быть одним из пунктов, но не единственным. Если у вас есть ещё причины (например, хочется проксировать запросы, фильтровать данные или использовать SSR) — тогда да, BFF может дать больше гибкости и безопасности.
В целом, у SPA возможностей в плане авторизации гораздо меньше, чем у фронта с BFF. Поэтому если вы никак не можете менять логику авторизации на бэкенде — BFF может стать неплохим компромиссом.

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

Авторизация вообще тесно связана с безопасностью.
Бессмысленно ставить JWT и при этом не защититься от XSS, CSRF и не настроить CSP-политику. Это как поставить стальные ворота, но оставить рядом огромную дыру в стене — мощно, но бесполезно.

Ну и отдельный мир — это SSO (OAuth, OpenID, и вот это всё). Я с ним пока не работала, но тема явно интересная и заслуживает отдельного разбора.
А с чем сталкивались вы? Какие подходы к авторизации вам кажутся удачными, а какие — категорически нет?
🤔65
Node.js на стероидах.pdf
12.7 MB
Вчера выступила с докладом про нативные модули (или native addons) в Node.js. Это расширения, написанные на C/C++, которые обеспечивают доступ к высокопроизводительным или низкоуровневым функциям, недоступным на чистом JavaScript.

По традиции, делюсь ссылкой на полезные материалы и приклыдываю свои слайды:
👉 Материалы на github
(в презентации много графиков с бенчмарками, а в материалах исходный код для них)

Если у вас есть вопросы по этой теме — пишите мне в личку @startpoint_forl, в сообщения этого канала или в комментарии под этим постом.
Также можете просто поставить реакцию, мне будет очень приятно)
27🔥13
А теперь — пост про то, что не влезло в доклад о нативных модулях: связь Node.js и C++, обработку исключений внутри модулей, команды для компиляции и работу с памятью.

Ещё немного про нативные модули в Node.js

Ну а я уже думаю над темами следующих интересных статей и докладов для вас. Будем копать глубже!)
🔥62
В одной из частей цикла про 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 раз подробнее именно в части работы интерпретатора и компиляторов движка 💫

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

(в опросе представлены не все посты, а только самые “жирненькие”, но всегда можно дополнить этот список в комментариях)
👏4