<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>JavaScript</title>
    <description>Руководства и новости по JavaScript фреймворкам, библиотекам и платформам, трюки, а также разбор успешных проектов.</description>
    <link>https://tproger.ru/tag/javascript</link>
    <atom:link href="https://tproger.ru/tag/javascript/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sun, 27 Sep 2026 20:33:47 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>JavaScript</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>pnpm 12.3 ускорил большие workspace и сделал глобальные команды нативными</title>
      <link>https://tproger.ru/news/pnpm-12-3-uskoril-bolwie-workspace-i-sdelal-globalnye-komandy</link>
      <comments>https://tproger.ru/news/pnpm-12-3-uskoril-bolwie-workspace-i-sdelal-globalnye-komandy?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/pnpm-12-3-uskoril-bolwie-workspace-i-sdelal-globalnye-komandy</guid>
      <description><![CDATA[<p>pnpm 12.3.0 читает lockfile при обнаружении проектов, делает node, deno и bun нативными файлами, даёт trust-флаги для remove и update; 12.3.1 чинит self-update.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/pnpm-12-3-uskoril-bolwie-workspace-i-sdelal-globalnye-komandy">pnpm 12.3 ускорил большие workspace и сделал глобальные команды нативными</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 03 Sep 2026 08:00:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>Команда pnpm 2 сентября <a href="https://github.com/pnpm/pnpm/releases/tag/v12.3.0">выпустила</a> версию 12.3.0, а через несколько часов, уже 3 сентября, <a href="https://github.com/pnpm/pnpm/releases/tag/v12.3.1">патч</a> 12.3.1. Главное в минорном релизе: установка в больших монорепозиториях стала быстрее за счёт того, что lockfile читается во время обнаружения проектов, а глобальные команды node, deno, bun и shim, созданные через pnpm shim add, превратились в нативные исполняемые файлы на всех платформах.</p><p>Для тех, кто держит workspace на десятки пакетов, это ускорение pnpm install без изменений в конфигурации. Для пользователей Windows это ещё и конец .cmd и .ps1-обёрток: их заменяет .exe. Обратная сторона видна по патчу 12.3.1: обновление с 12.2 через self-update ломало миграцию старых shim, и это исправили в тот же день.</p><ul><li>Установка в больших workspace ускорена: lockfile читается во время discovery проектов, граф workspace обрабатывается один раз вместо полного копирования lockfile.</li><li>Контекстно-зависимые глобальные команды и shim стали нативными исполняемыми файлами; в Windows .exe вместо .cmd и .ps1, старые shim мигрируют при следующей глобальной установке или self-update.</li><li>Флаги доверия --trust-lockfile, --no-trust-lockfile, --trust-policy, --trust-policy-exclude и --trust-policy-ignore-after теперь работают и для remove и update.</li><li>Исправлено зависание recursive run и exec, в том числе с --filter и --workspace-concurrency=1.</li><li>Системный резолвер на Linux использует getaddrinfo и больше не должен молча обращаться к Google Public DNS при неподдерживаемой опции no_tld_query.</li></ul><h2>Откуда взялось ускорение в монорепозиториях</h2><p>По заметкам к релизу, раньше pnpm сначала обходил все проекты workspace, а потом отдельно разбирал lockfile и копировал его целиком для резолвера. В 12.3 lockfile читается уже на этапе discovery, а граф workspace строится один раз и передаётся резолверу и генератору lockfile без полного копирования. Конкретных цифр ускорения команда pnpm в заметках не приводит, формулировка звучит как «sped up installs in large workspaces»; эффект тем заметнее, чем больше пакетов и чем толще lockfile.</p><h2>Что изменилось для глобальных команд и Windows</h2><p>pnpm умеет ставить node, deno и bun как глобальные команды, которые подбирают версию по контексту проекта. До 12.3 в Windows такие команды были скриптами .cmd и .ps1, а на других платформах shell-обёртками. Теперь везде это нативные исполняемые файлы. Старые shim от pnpm 12 мигрируют при следующей глобальной установке или self-update; именно этот переход и сломался при обновлении с 12.2, что исправили в 12.3.1: нативный shim теперь мигрирует при первом запуске.</p><h2>Безопасность: trust-флаги и скрытие секретов</h2><p>Механизм доверия к lockfile появился в pnpm 12 как защита от атак на цепочку поставок: пакеты, не прошедшие политику доверия, не ставятся. В 12.2 флаги были только у install и add; 12.3 добавляет их к remove и update. Второе изменение того же ряда: учётные данные и query-параметры из URL реестров теперь скрываются в сообщениях об ошибках fetch и загрузки tarball, так что токен приватного реестра больше не утекает в лог CI при обрыве соединения.</p><h2>Исправления, которые стоит знать</h2><ul><li>Добавление локальных директорий, tarball-файлов и tarball по URL через pnpm add снова работает.</li><li>recursive run и recursive exec больше не зависают, включая варианты с --filter и --workspace-concurrency=1.</li><li>Дочерние процессы в Windows запускаются корректно.</li><li>Linux: системный резолвер переведён на getaddrinfo; при неподдерживаемой опции no_tld_query в resolv.conf pnpm больше не должен молча уходить к Google Public DNS, что важно в закрытых сетях и там, где внешние DNS фильтруются.</li><li>12.3.1: исправлены обработка workspace anchor и параллельная проверка lockfile.</li></ul><h2>Как обновиться</h2><p>Обновляться стоит сразу на 12.3.1, минуя 12.3.0. Если pnpm установлен через Corepack или npm i -g pnpm, обновление идёт обычным способом; если через self-update, после перехода с 12.2 стоит один раз запустить любую глобальную команду, чтобы shim мигрировал. Проверить, что всё на месте: pnpm --version должен показать 12.3.1, а pnpm shim ls (если пользуетесь shim) не должен ругаться на старые обёртки.</p><p>Кому обновление ничего не изменит: одиночным проектам без workspace и тем, кто не пользуется глобальными командами pnpm. Из соседних новостей про инструменты JavaScript на сайте: <a href="https://tproger.ru/news/github-cli-2-99-prikladyvaet-skrinwoty-i-video-k-issue-iz-termin">GitHub CLI 2.99</a> и <a href="https://tproger.ru/news/wasmi-2-0-uskoril-interpretaciyu-webassembly-v-2-2-raza">Wasmi 2.0</a>. Полный список изменений с номерами issue лежит на <a href="https://github.com/pnpm/pnpm/releases/tag/v12.3.0">странице релиза</a>.</p><p>Источники: <a href="https://github.com/pnpm/pnpm/releases/tag/v12.3.0">pnpm v12.3.0 на GitHub</a>, <a href="https://github.com/pnpm/pnpm/releases/tag/v12.3.1">pnpm v12.3.1 на GitHub</a></p><p>Изображение на обложке: pnpm, логотип проекта</p>]]></content:encoded>
    </item>
    <item>
      <title>WebLLM запускает языковую модель в браузере без сервера за один вечер</title>
      <link>https://tproger.ru/articles/webllm-zapuskaet-yazykovuyu-model-v-brauzere-bez-servera-za-odin</link>
      <comments>https://tproger.ru/articles/webllm-zapuskaet-yazykovuyu-model-v-brauzere-bez-servera-za-odin?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/webllm-zapuskaet-yazykovuyu-model-v-brauzere-bez-servera-za-odin</guid>
      <description><![CDATA[<p>Разбор WebLLM 0.2.84: установка, код чата со стримингом, видеопамять для 9 моделей от 0,7 до 6,4 ГБ, 71–80% нативной скорости и поддержка WebGPU в браузерах.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/webllm-zapuskaet-yazykovuyu-model-v-brauzere-bez-servera-za-odin">WebLLM запускает языковую модель в браузере без сервера за один вечер</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[Браузеры]]></category>
      <category><![CDATA[WebAssembly]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 03 Sep 2026 06:49:32 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://github.com/mlc-ai/web-llm">WebLLM</a> — открытый движок команды MLC AI, который выполняет языковую модель внутри браузера на WebGPU и отдаёт её через API в стиле OpenAI. Сервера для инференса нет: веса скачиваются один раз, дальше промпты и ответы не покидают вкладку. Проект живёт с 2023 года, но 2 сентября 2026 года <a href="https://news.ycombinator.com/item?id=49536411">снова поднялся</a> на первую страницу Hacker News с 92 очками, а на GitHub у него 18 846 звёзд и 1 372 форка по данным API на 3 сентября.</p><p>Я разбираю WebLLM как рабочий инструмент: что нужно от железа и браузера, сколько весят модели, какой скоростью придётся заплатить за отказ от сервера и где проще остаться на облачном API. Все команды и фрагменты кода взяты из README репозитория, цифры производительности из статьи авторов на arXiv, требования к видеопамяти из файла конфигурации в исходниках.</p><p>Материал для фронтенд- и фулстек-разработчиков, которым нужен чат, классификатор или JSON-извлекатель на клиенте без счёта за токены и без передачи пользовательских данных третьей стороне.</p><ul><li>WebLLM выполняет модель в браузере на WebGPU, без сервера; API совместим с OpenAI Chat Completions: стриминг, JSON-режим, seed. Вызов функций помечен в README как незавершённый.</li><li>Актуальная версия пакета @mlc-ai/web-llm — 0.2.84 от 27 мая 2026 года; в 2025–2026 годах вышло всего 7 релизов против 63 за 2024 год по данным реестра npm.</li><li>По замерам авторов на MacBook Pro M3 Max WebLLM выдаёт 41,1 токена/с на Llama-3.1-8B и 71,1 токена/с на Phi-3.5-mini, то есть 71–80% от нативного MLC-LLM на том же железе.</li><li>Модели в 4-битной квантизации требуют от 0,7 ГБ видеопамяти (gemma3-1b) до 6,4 ГБ (Qwen3.5-9B) по значениям vram_required_MB в src/config.ts; первый запуск качает всё это с huggingface.co.</li><li>WebGPU по данным MDN есть в Chrome и Edge с версии 113, в Safari 26 с сентября 2025 года, в Firefox с 141 только частично: Linux и Intel-маки не поддерживаются.</li></ul><h2>Как WebLLM выполняет модель в браузере и при чём тут WebGPU?</h2><p>Модель считается на видеокарте пользователя через WebGPU, а всё, что не ложится на GPU, крутится в WebAssembly. Авторы <a href="https://arxiv.org/abs/2412.15803">описывают</a> схему так: родственный проект MLC LLM компилирует открытую модель заранее в два артефакта, конвертированные веса и WASM-библиотеку с WebGPU-ядрами и вспомогательными функциями. WebLLM в браузере загружает оба артефакта с хостинга, инициализирует WebGPU-устройство и дальше исполняет граф вычислений локально.</p><blockquote>Everything runs inside the browser with no server support and is accelerated with WebGPU.</blockquote><p>Отсюда же и главное ограничение, о котором в статье авторы говорят прямо: в отличие от CUDA у WebGPU нет готовых ускоренных библиотек для типовых ядер, поэтому матричные умножения и внимание команде пришлось писать и оптимизировать самой. Цена этого видна в замерах: на Apple MacBook Pro M3 Max в Chrome Canary 133 WebLLM версии 0.2.75 выдавал 41,1 токена/с на 4-битной Llama-3.1-8B против 57,7 у нативного MLC-LLM с Metal-ядрами на той же машине, и 71,1 против 89,3 токена/с на Phi-3.5-mini. Это 71,2% и 79,6% нативной скорости соответственно, замер вендора, другого железа в статье нет.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-09-03/faf6a5ef-05f1-4c84-a5b3-0de9ac78a2a2.webp" alt="Столбчатая диаграмма: скорость декодирования WebLLM и MLC-LLM в токенах в секунду на Llama-3.1-8B (41,1 против 57,7) и Phi-3.5-mini (71,1 против 89,3)" /><figcaption>Скорость декодирования в браузере и нативно на одном MacBook Pro M3 Max, 4-битная квантизация. График: Tproger по данным статьи авторов WebLLM на arXiv 2412.15803</figcaption></figure><p>Второй компонент, о котором обычно забывают, это кэш. Веса и WASM-библиотека скачиваются при первом обращении и складываются в хранилище браузера. README перечисляет четыре бэкенда, которые задаются полем cacheBackend в AppConfig: Cache API по умолчанию, IndexedDB, Origin Private File System и экспериментальный cross-origin через расширение Chrome, чтобы одни и те же веса не качались заново для каждого сайта. Без расширения движок сам откатывается на Cache API.</p><p>JSON-режим, который в серверных API обычно реализован на стороне провайдера, здесь тоже выполняется локально: по README структурированная генерация встроена в WebAssembly-часть библиотеки модели, а попробовать её со своей JSON-схемой можно в <a href="https://huggingface.co/spaces/mlc-ai/WebLLM-JSON-Playground">JSON Playground</a> на Hugging Face. Про то, как WASM-рантаймы догоняют нативный код, мы писали в заметке про <a href="https://tproger.ru/news/wasmi-2-0-uskoril-interpretaciyu-webassembly-v-2-2-raza">Wasmi 2.0</a>.</p><h2>Что нужно, чтобы запустить первый чат?</h2><p>Достаточно npm-пакета, одного вызова фабрики и терпения на первую загрузку весов. README даёт три варианта установки и импорт через CDN для JSFiddle и CodePen без сборщика.</p><p>Движок создаётся функцией CreateMLCEngine: это асинхронная фабрика: она возвращает Promise, а готовый движок с загруженной моделью получают через await. Колбэк прогресса обязателен на практике, потому что без него пользователь смотрит на пустой экран, пока качаются гигабайты.</p><blockquote>loading models requires downloading and it can take a significant amount of time for the very first run without caching previously</blockquote><p>Дальше интерфейс тот же, что у клиента OpenAI: engine.chat.completions.create({ messages }). Одна ловушка: параметр model в запросе игнорируется, модель выбирается при создании движка или через engine.reload(model). Стриминг включается флагом stream: true, а статистика по токенам приходит только в последнем чанке, если попросить её через stream_options.</p><p>Готовый чат для проверки железа не нужно собирать самому: <a href="https://chat.webllm.ai/">WebLLM Chat</a> сделан на том же пакете, а его код открыт в отдельном репозитории.</p><h2>Какие модели доступны и сколько видеопамяти им нужно?</h2><p>В список prebuiltAppConfig.model_list в <a href="https://github.com/mlc-ai/web-llm/blob/main/src/config.ts">src/config.ts</a> на 3 сентября входят 165 записей: это варианты квантизации и длины контекста для семейств Llama 3.x, Qwen 2.5, Qwen 3 и Qwen 3.5, Phi 3.5 и Phi 4 mini, Gemma 2 и Gemma 3, Mistral и Ministral 3, дистилляты DeepSeek-R1, SmolLM2, OLMo 2, а также две модели эмбеддингов snowflake-arctic-embed. README при этом всё ещё перечисляет старый набор с Llama 2 и Qwen2, так что актуальный набор виден только в коде.</p><p>У каждой записи есть поле vram_required_MB, и это честнее любого «весит N гигабайт»: оно включает не только веса, но и память под KV-кэш при заданном контексте. Для 4-битных вариантов с половинной точностью (суффикс q4f16_1) разброс такой:</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-09-03/dd4f8b13-a996-4064-b90b-4c012e309271.webp" alt="Горизонтальная диаграмма: требуемая видеопамять в гигабайтах для девяти моделей WebLLM от 0,7 ГБ у gemma3-1b до 6,4 ГБ у Qwen3.5-9B" /><figcaption>Значение vram_required_MB для вариантов q4f16_1 с контекстом по умолчанию. График: Tproger по данным src/config.ts репозитория mlc-ai/web-llm, 3 сентября 2026</figcaption></figure><ul><li>gemma3-1b-it — 0,71 ГБ; Llama-3.2-1B-Instruct — 0,88 ГБ; Qwen2.5-0.5B-Instruct — 0,94 ГБ. Помечены флагом low_resource_required, это кандидаты для встроенной графики и ноутбуков.</li><li>Qwen3-0.6B — 1,40 ГБ; gemma-2-2b-it — 1,90 ГБ; Llama-3.2-3B-Instruct — 2,26 ГБ.</li><li>Qwen3-4B — 3,43 ГБ; Phi-3.5-mini-instruct — 3,67 ГБ; Llama-3.1-8B-Instruct — 5,00 ГБ; Qwen3-8B — 5,70 ГБ; Qwen3.5-9B — 6,43 ГБ.</li><li>Есть и Llama-3.1-70B-Instruct в 3-битной квантизации с требованием 31,15 ГБ; на потребительской видеокарте это не запустится.</li></ul><p>Варианты с суффиксом «-1k» урезают контекст до одной тысячи токенов и экономят память: у Llama-3.1-8B это 4,60 ГБ вместо 5,00, у Phi-3.5-mini 2,52 ГБ вместо 3,67. Все эти гигабайты приезжают с <a href="https://huggingface.co/mlc-ai">huggingface.co/mlc-ai</a>, поэтому доступность и скорость этого хоста в вашей сети стоит проверить до того, как обещать пользователям «работает без сервера». Свою модель в формате MLC подключают через appConfig.model_list, указав URL весов и WASM-библиотеки. Какие небольшие открытые модели вышли в августе и на чём их запускать дома, мы собирали в <a href="https://tproger.ru/news/otkrytye-nejroseti-avgusta-frontir-na-odnoj-videokarte-i-chto-za">обзоре открытых нейросетей</a>.</p><p>Полный каталог собранных моделей лежит на mlc.ai/models. Номер совместимой сборки библиотек зашит в пакет: для 0.2.84 это константа modelVersion со значением «v0_2_84/base», так что после обновления npm-пакета кэшированные WASM-библиотеки могут потребовать перезагрузки.</p><h2>Как не заморозить интерфейс: Web Worker или Service Worker?</h2><p>Для обычного приложения хватает выделенного Web Worker, а Service Worker нужен, когда модель должна переживать переходы между страницами. Оба варианта в README оформлены одинаково: в потоке живёт обработчик, в основном скрипте создаётся движок-прокси с тем же интерфейсом MLCEngineInterface.</p><p>С Service Worker сложнее. Его жизненным циклом управляет браузер и может убить поток без предупреждения; движок шлёт heartbeat каждые 10 секунд по умолчанию (значение keepAliveMs в src/service_worker.ts), но авторы просят закладывать обработку ошибок и в приложении. README отдельно требует создавать ServiceWorkerMLCEngineHandler на верхнем уровне скрипта и запрещает делать это внутри обработчиков activate или message: браузер перезапускает уже активный воркер без повторного события activate.</p><blockquote>Service Worker's life cycle is managed by the browser and can be killed any time without notifying the webapp. ServiceWorkerMLCEngine will try to keep the service worker thread alive by periodically sending heartbeat events, but your application should also include proper error handling.</blockquote><p>Отдельная ветка применения — расширения Chrome: в репозитории есть примеры базового расширения и расширения на Service Worker с WebGPU, которое держит модель в фоне. По данным MDN, в Firefox WebGPU недоступен именно в контексте Service Worker, так что этот сценарий пока привязан к Chromium.</p><h2>Где WebLLM выигрывает у серверного API, а где проигрывает?</h2><p>Выигрывает там, где важны приватность промптов и нулевая стоимость токена, проигрывает там, где нельзя выбирать пользователю железо. Разложу по пунктам, отделяя проверяемое от обещаний.</p><ul><li><b>Деньги.</b> Инференс идёт на GPU пользователя, счёт за токены равен нулю. Платите трафиком: каждый новый пользователь качает от 0,7 до 6,4 ГБ весов с Hugging Face; свой CDN нужен только тем, кто перехостит артефакты сам.</li><li><b>Приватность.</b> Промпты и ответы на сервер не уходят, это следует из архитектуры. Но сетевые запросы есть: веса и WASM скачиваются с huggingface.co и jsdelivr, поэтому формулировка «данные никогда не покидают устройство» некорректна, и в политике приватности так писать нельзя.</li><li><b>Скорость.</b> 71–80% от нативного инференса по замеру авторов на M3 Max. На ноутбуке с встроенной графикой цифр в статье нет, и обещать «десятки токенов в секунду» без своего замера нельзя.</li><li><b>Первый запуск.</b> Минуты загрузки против миллисекунд первого ответа у API. Кэш спасает повторные визиты, но не первый.</li><li><b>Браузеры.</b> По данным MDN, WebGPU есть в Chrome и Edge с версии 113 (полная поддержка на Linux только с 144 и только на Intel Gen12 и новее), в Safari 26 на macOS и iOS с 15 сентября 2025 года, в Firefox с 141 частично: есть Windows и Apple Silicon, нет Linux и Intel-маков. Проверка navigator.gpu обязательна.</li><li><b>Управление моделью.</b> Серверный API даёт одну версию модели для всех и мгновенную замену. В WebLLM версия модели живёт в кэше каждого браузера, а обновление пакета может потребовать перекачки библиотек.</li><li><b>Функции.</b> Вызов инструментов через tools и tool_choice в README помечен как WIP с предварительной поддержкой. Для агентских сценариев это стоп-фактор; про выбор стека для агентов у нас есть <a href="https://tproger.ru/articles/kak-vybrat-frejmvork-dlya-ii-agentov">отдельный разбор</a>.</li></ul><p>Есть и организационный риск. По реестру npm пакет обновлялся 19 раз в 2023 году, 63 раза в 2024-м, 3 раза в 2025-м и 4 раза с начала 2026 года; последний релиз 0.2.84 вышел 27 мая. Код в репозитории при этом продолжает меняться, последний push был 3 сентября. Для продакшена это означает, что свежие модели из config.ts могут ждать релиза месяцами.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-09-03/4059bf51-0d09-422e-8e6b-ba9c40176049.webp" alt="Столбчатая диаграмма: число релизов пакета @mlc-ai/web-llm по годам: 19 в 2023, 63 в 2024, 3 в 2025, 4 в 2026" /><figcaption>Релизы пакета @mlc-ai/web-llm по годам, 2026 год по 3 сентября. График: Tproger по данным реестра npm</figcaption></figure><blockquote>Evaluations show that WebLLM can retain up to 80% native performance on the same device with room to close the gap further.</blockquote><h2>Что делать по шагам, чтобы собрать чат на WebLLM за вечер</h2><p>План ниже покрывает путь от проверки железа до защиты артефактов; команды и имена функций взяты из README для версии 0.2.84.</p><ol><li>Проверьте, что в браузере есть navigator.gpu, и покажите понятную заглушку тем, у кого его нет. По MDN это Chrome и Edge 113+, Safari 26+, Firefox 141+ с оговорками по ОС.</li><li>Установите пакет: npm install @mlc-ai/web-llm (версия 0.2.84). Для прототипа без сборки подойдёт импорт с https://esm.run/@mlc-ai/web-llm.</li><li>Выберите модель из prebuiltAppConfig.model_list по полю vram_required_MB. Для широкой аудитории начните с моделей с флагом low_resource_required: Llama-3.2-1B, Qwen2.5-0.5B, gemma3-1b.</li><li>Создайте движок через CreateMLCEngine с initProgressCallback и выведите прогресс загрузки на экран.</li><li>Перенесите инференс в Web Worker через CreateWebWorkerMLCEngine, чтобы интерфейс не замирал во время генерации.</li><li>Включите стриминг флагом stream: true; для извлечения структурированных данных используйте JSON-режим из раздела Full OpenAI Compatibility.</li><li>Если артефакты лежат на своём хостинге, добавьте в запись модели поле integrity с SRI-хэшами для конфига, WASM и токенизатора; хэш генерируется командой openssl из README.</li></ol><p>При несовпадении хэша движок бросает IntegrityError, либо пишет предупреждение и продолжает работу, если задать onFailure: "warn". Без поля integrity проверка не выполняется вовсе, как и в прежних версиях.</p><h2>Что дальше: чего нет в WebLLM и за чем следить</h2><p>Первое, за чем стоит следить, это релиз после 0.2.84: в src/config.ts уже есть Qwen3.5 и Ministral 3, и вопрос в том, когда они попадут в опубликованный пакет. Второе — статус вызова функций, который держится в README как WIP. Третье — cross-origin хранилище: сейчас это расширение Chrome и экспериментальный API, а без него каждый сайт качает свою копию весов.</p><p>Мы не проверяли скорость на встроенной графике и на Windows-ноутбуках; единственные опубликованные цифры относятся к MacBook Pro M3 Max. Доступность huggingface.co и esm.run из конкретной сети мы тоже не измеряли: перед запуском продукта на WebLLM стоит замерить время первой загрузки у своей аудитории или перехостить артефакты.</p><p>Источники: <a href="https://github.com/mlc-ai/web-llm">GitHub: mlc-ai/web-llm (README, лицензия Apache-2.0)</a>, <a href="https://github.com/mlc-ai/web-llm/blob/main/src/config.ts">src/config.ts: prebuiltAppConfig.model_list с vram_required_MB</a>, <a href="https://github.com/mlc-ai/web-llm/blob/main/src/service_worker.ts">src/service_worker.ts: keepAliveMs и heartbeat</a>, <a href="https://arxiv.org/abs/2412.15803">arXiv 2412.15803: WebLLM: A High-Performance In-Browser LLM Inference Engine</a>, <a href="https://blog.mlc.ai/2024/06/13/webllm-a-high-performance-in-browser-llm-inference-engine">Блог MLC: WebLLM, 13 июня 2024</a>, <a href="https://webllm.mlc.ai/docs/">Документация WebLLM</a>, <a href="https://registry.npmjs.org/@mlc-ai/web-llm">Реестр npm: @mlc-ai/web-llm (версии и даты)</a>, <a href="https://news.ycombinator.com/item?id=49536411">Hacker News: обсуждение WebLLM 2 сентября 2026</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API#browser_compatibility">MDN: WebGPU API, совместимость браузеров</a>, <a href="https://huggingface.co/mlc-ai">Hugging Face: модели mlc-ai</a>, <a href="https://huggingface.co/spaces/mlc-ai/WebLLM-JSON-Playground">WebLLM JSON Playground</a>, <a href="https://chat.webllm.ai/">WebLLM Chat</a>, <a href="https://mlc.ai/models">Каталог моделей MLC</a>, <a href="https://github.com/mlc-ai/mlc-llm">GitHub: mlc-ai/mlc-llm</a></p><p>Изображение на обложке: Скриншот: chat.webllm.ai</p>]]></content:encoded>
    </item>
    <item>
      <title>Chrome 154 beta принёс постквантовое шифрование в Web Crypto</title>
      <link>https://tproger.ru/news/chrome-154-beta-prinyos-postkvantovoe-wifrovanie-v-web-crypto</link>
      <comments>https://tproger.ru/news/chrome-154-beta-prinyos-postkvantovoe-wifrovanie-v-web-crypto?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/chrome-154-beta-prinyos-postkvantovoe-wifrovanie-v-web-crypto</guid>
      <description><![CDATA[<p>Chrome 154 beta: ML-KEM и ML-DSA в Web Crypto, options bag и targetAddressSpace у WebSocket, режимы links и tabs у scroll-marker-group, CORS в Background Fetch.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/chrome-154-beta-prinyos-postkvantovoe-wifrovanie-v-web-crypto">Chrome 154 beta принёс постквантовое шифрование в Web Crypto</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[Браузеры]]></category>
      <category><![CDATA[Google Chrome]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 03 Sep 2026 03:20:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Google 2 сентября <a href="https://developer.chrome.com/blog/chrome-154-beta">перевела</a> Chrome 154 в бету для Android, ChromeOS, Linux, macOS и Windows; desktop-сборка имеет номер 154.0.8037.0, для Android и ChromeOS сборки выходят отдельно. Самое заметное для веб-разработчиков: в Web Crypto API добавили постквантовые алгоритмы ML-KEM и ML-DSA, WebSocket научился принимать объект настроек и ходить в локальную сеть с явным разрешением, а у CSS-свойства scroll-marker-group появились режимы links и tabs.</p><p>Бета обычно доживает до стабильного релиза без крупных изменений, поэтому всё перечисленное стоит проверять уже сейчас: часть новинок меняет поведение существующего кода. Background Fetch начинает применять CORS, а AbortController теперь передаёт причину отмены в Response и ReadableStream. Автор поста в блоге Chrome for Developers Рэйчел Эндрю; отдельных региональных ограничений на бету в источнике нет.</p><ul><li>Web Crypto получил ML-KEM 768 и 1024, ML-DSA 44, 65 и 87, ChaCha20-Poly1305 и гибридную схему X-Wing.</li><li>WebSocket принимает options bag: new WebSocket(url, { protocols: "soap" }); новое поле targetAddressSpace позволяет подключаться к локальным адресам при secure context и разрешении Local Network Access.</li><li>scroll-marker-group получил режимы links и tabs; в режиме tabs активный маркер остаётся единственным tab-stop, а неактивный контент скрывается из дерева доступности.</li><li>CSSStyleValue и связанные конструкторы доступны в воркерах; text-decoration-inset принимает auto, длину, процент и одно-/двухзначную запись; FontFace.width стал алиасом stretch.</li><li>Background Fetch теперь соблюдает CORS; WebGPU получил модификаторы frag_depth less и greater, которые позволяют не терять ранний Z-тест.</li></ul><h2>Что даёт постквантовая криптография в браузерном API</h2><p>До сих пор постквантовые алгоритмы в Chrome работали только на уровне TLS, незаметно для страницы. В 154 они выходят в JavaScript: через crypto.subtle можно генерировать ключи и выполнять инкапсуляцию ML-KEM (стандарт NIST для обмена ключами) и подписи ML-DSA. Добавлены также ChaCha20-Poly1305 и X-Wing, гибрид классического X25519 и ML-KEM. На практике это нужно тем, кто делает сквозное шифрование в веб-приложениях: мессенджерам, хранилищам паролей, локальным подписям документов. Проверить у себя просто: вызвать crypto.subtle.generateKey с новым именем алгоритма в бете и убедиться, что он не бросает NotSupportedError.</p><h2>Зачем WebSocket объект настроек и локальная сеть</h2><p>Конструктор WebSocket много лет принимал только URL и строку или массив протоколов. Теперь второй аргумент может быть объектом: new WebSocket(url, { protocols: "soap" }). Это задел под будущие опции без ломки сигнатуры. Второе поле, targetAddressSpace, решает конкретную боль: страница с публичного HTTPS-сайта не могла открыть сокет к устройству в локальной сети. В 154 это возможно, но при трёх условиях: secure context, разрешение пользователя по механизму Local Network Access и имя хоста, которое резолвится в локальный IP. Сценарий типичный для панелей управления IoT и локальных серверов разработки.</p><h2>Что меняется в CSS</h2><p>Свойство scroll-marker-group, которое вместе с псевдоэлементами ::scroll-marker позволяет делать карусели и табы без JavaScript, получило два режима. links ведёт себя как набор ссылок, tabs как настоящий tablist: активный маркер единственный останавливается по Tab, а контент неактивных вкладок исключается из accessibility tree. Это закрывает главную претензию к CSS-каруселям: раньше скринридер видел все панели сразу.</p><p>Из остального: text-decoration-inset позволяет отступать подчёркивание от краёв текста и принимает auto, длину, процент и запись из одного или двух значений; FontFace.width и дескриптор font-width в @font-face стали алиасами stretch и font-stretch; типизированная объектная модель CSS (CSSStyleValue и конструкторы) теперь доступна в воркерах, что упрощает расчёты стилей вне главного потока.</p><h2>Что может сломаться после обновления</h2><ul><li>Background Fetch теперь применяет CORS: фоновые загрузки с чужих доменов без правильных заголовков начнут падать.</li><li>AbortController прокидывает reason в Response и ReadableStream: код, который сравнивал ошибку отмены с конкретным AbortError, может получить другой объект.</li><li>По заметкам к релизу, браузер в ряде сценариев использует события click вместо пары pointerdown/pointerup; обработчики, завязанные на порядок этих событий, стоит перепроверить.</li><li>В WebGPU появились модификаторы frag_depth less и greater: шейдеры, которые пишут глубину, могут вернуть себе ранний Z-тест, но это требует правки WGSL.</li></ul><h2>Как попробовать</h2><p>Бета ставится параллельно со стабильным Chrome со <a href="https://www.google.com/chrome/beta/">страницы загрузки Chrome Beta</a>; анонс сборки опубликован в <a href="https://chromereleases.googleblog.com/2026/09/chrome-beta-for-desktop-update.html">Chrome Releases</a>; на Android доступна через бета-программу в магазине. Стабильный релиз 154 по обычному графику Chrome выходит примерно через четыре недели после беты, точную дату Google в посте не называет. До этого времени полезно прогнать сайт с включёнными фоновыми загрузками и WebSocket-подключениями к локальным устройствам.</p><p>Что происходит в других движках, можно сравнить с недавними релизами: WebKit <a href="https://tproger.ru/news/safari-27-poluchit-perepisannyj-zagruzchik-modulej-i-rabochij-top-l">переписал загрузчик модулей</a> для Safari 27, а Firefox 155 <a href="https://tproger.ru/news/firefox-dlya-ios-poluchil-vstroennyj-blokirovshhik-reklamy-a-firefo">добавил</a> CSS-функцию progress(). Полный список изменений Chrome 154 с примерами кода лежит в <a href="https://developer.chrome.com/blog/chrome-154-beta">блоге Chrome for Developers</a>.</p><p>Источники: <a href="https://developer.chrome.com/blog/chrome-154-beta">Chrome for Developers: Chrome 154 beta</a>, <a href="https://chromereleases.googleblog.com/2026/09/chrome-beta-for-desktop-update.html">Chrome Releases: Chrome Beta for Desktop Update</a></p><p>Изображение на обложке: Google, логотип Chrome</p>]]></content:encoded>
    </item>
    <item>
      <title>Агенты Fable 5.1 собрали в браузере проходимую копию Юнион-сквер</title>
      <link>https://tproger.ru/news/agenty-fable-5-1-sobrali-v-brauzere-prohodimuyu-kopiyu-yunion-skver</link>
      <comments>https://tproger.ru/news/agenty-fable-5-1-sobrali-v-brauzere-prohodimuyu-kopiyu-yunion-skver?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/agenty-fable-5-1-sobrali-v-brauzere-prohodimuyu-kopiyu-yunion-skver</guid>
      <description><![CDATA[<p>PhiloLabs выложила fable51-worlds: рой агентов Claude Fable 5.1 по одному промпту собрал проходимую 3D-копию площади Юнион-сквер в Сан-Франциско на чистом Three.js. 453 здания из OSM, 129 магазинов, 220 пешеходов, MIT. Что внутри, как проверяли и что не получилось.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/agenty-fable-5-1-sobrali-v-brauzere-prohodimuyu-kopiyu-yunion-skver">Агенты Fable 5.1 собрали в браузере проходимую копию Юнион-сквер</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[3D-технологии]]></category>
      <category><![CDATA[Сделано с помощью ИИ]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 03 Sep 2026 02:55:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Организация PhiloLabs 2 сентября <a href="https://github.com/PhiloLabs/fable51-worlds">выложила на GitHub</a> репозиторий fable51-worlds: проходимую 3D-реконструкцию площади Юнион-сквер в Сан-Франциско, которую, по заявлению авторов, по одному промпту построил рой автономных агентов Claude Fable 5.1. Сцена работает в браузере на чистом Three.js без игрового движка и без чужих 3D-тайлов, запускается командами cd union-square-sf, npm install и npm run dev, а код и сгенерированные ассеты отданы под MIT. К утру 3 сентября по Москве обсуждение на Hacker News собрало 188 очков.</p><p>Для разработчика это открытый пример, где агентный конвейер отвечает за весь цикл: сбор геоданных, генерацию моделей, рантайм, автотесты по фотографиям и независимое ревью. В репозитории лежит исходный промпт, база исследования с источником у каждого факта, скрипты генерации и отчёты рецензентов с оценками от 5,5 до 8 из 10, которые авторы не подгоняли под целевую планку. Это можно клонировать, повторить на своём районе или разобрать как образец того, что сегодня получается у агентов в 3D, а что пока нет.</p><ul><li>Сцена: 453 контура зданий из OpenStreetMap, 75 фасадов с описанием, составленным агентами по фотографиям, 11 950 оконных и дверных проёмов, 129 опознанных магазинов с реальными арендаторами 2024–2026 годов.</li><li>Жизнь на улице: 220 пешеходов на навигационном графе из 1398 узлов, 109 машин, автобусы и канатные трамваи по улице Пауэлл, 36 регулируемых перекрёстков.</li><li>Два интерьера, куда можно зайти: Apple Union Square и Nintendo San Francisco, 23 интерактивных объекта.</li><li>Проверка: 34 контрольных точки, где рендер сверяется со снимком с того же места (свободные фотографии есть для 28 из них), 147 листов сравнения, 9 отчётов агентов-рецензентов, 18 из 18 шагов сценарного прохода.</li><li>Производительность в headless Chromium при 1080p: 50–79 кадров в секунду на улице днём, 32 в виде сверху. Ассеты: 206 GLB-файлов на 5,1 МБ.</li></ul><h2>Что построили</h2><p>Реконструкция покрывает примерно 800 на 720 метров: саму площадь и кварталы между улицами Мейсон и Грант, Саттер и О'Фаррелл. Рельеф взят из USGS 3DEP (1317 высотных отметок), поэтому Пауэлл-стрит поднимается к Ноб-Хиллу так же, как в жизни. Начало координат привязано к монументу Дьюи, а вся сцена повёрнута на реальный азимут уличной сетки 80,686 градуса, чтобы фасады стояли на своих участках.</p><p>Управление обычное для браузерных прогулок: WASD для ходьбы, Shift для бега, E для взаимодействия, Tab для орбитальной камеры, T запускает кинематографический тур, клавиши 1, 2 и 3 переключают день, закат и ночь. Клавиша R накладывает на рендер реальную фотографию с той же точки, чтобы глазами оценить расхождение.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-09-03/24f1d9df-a2b3-40bc-9a1b-3fad469f6574.webp" alt="Монумент Дьюи на площади в реконструкции" /><figcaption>Монумент Дьюи с площади. Кадр из репозитория PhiloLabs (MIT)</figcaption></figure><h2>Как это собрано</h2><p>Авторы подчёркивают, что ничего в сцене не расставлено «на глаз»: всё собирается при загрузке из данных. Конвейер из четырёх стадий описан в README и целиком лежит в репозитории.</p><ol><li>Разведка. Одиннадцать параллельных исследовательских агентов собрали базу: контуры зданий из OSM, высоты USGS, спецификацию улиц и транспорта (число полос, односторонние направления, положение рельсов канатной дороги), обмер площади, перепись из 122 магазинов, отдельные исследования магазинов Apple и Nintendo и 34 контрольных ракурса. У каждого факта записан источник и уровень уверенности; непроверенное не придумывалось, а помечалось как нерешённое.</li><li>Офлайн-генерация ассетов. Скрипты на Blender как библиотеке (bpy) выпускают 206 оптимизированных GLB-модулей: окна, карнизы, витрины, маркизы, фонари, светофоры, скамейки, гидранты, машины, пальмы, торговое оборудование, части тел пешеходов. Blender в рантайме не участвует.</li><li>Рантайм. Приложение на Three.js собирает рельеф, улицы, фасады, реквизит, толпу и трафик из JSON-спецификаций. Фасадный движок превращает описание здания в геометрию: цокольный ярус, шагающий вместе с уклоном тротуара, витрины с реальными арендаторами и вывесками, окна по ритму пролётов, карнизы и парапеты. 75 зданий описаны отдельными спецификациями, остальные выводятся автоматически и берут арендаторов из переписи.</li><li>Проверка по камере. Playwright гоняет настоящее приложение по 34 ракурсам, снимает скриншоты и кладёт их рядом с фотографиями под свободной лицензией с тех же точек, плюс наложение 50 на 50. Девять независимых агентов-рецензентов (геометрия, семантика, архитектор, местный житель Сан-Франциско, художник по окружению, технический художник, интерактив) пишут отчёты, которые запускают следующий цикл правок.</li></ol><p>Пешеходы ходят по навигационному графу из 1398 узлов по тротуарам, переходам и лестницам площади, у них есть роли: пассажир, покупатель, турист, сидящий, переходящий дорогу. Машины двигаются по графу полос по модели IDM и останавливаются на красный; светофоры работают циклом в 60 секунд.</p><h2>Что показала проверка</h2><p>Самая полезная часть репозитория для тех, кто оценивает возможности агентов, это <a href="https://github.com/PhiloLabs/fable51-worlds/blob/main/union-square-sf/FINAL_QA_REPORT.md">итоговый QA-отчёт</a>, сгенерированный 2 сентября. Географическая точность получила 7,5 из 10, узнаваемость зданий 5,5 с оценкой 6,5 после исправления, точность магазинов 6 (размещены 76% названных арендаторов из переписи), интерьер Apple 7, Nintendo 7,5, детализация улицы, материалы и общая узнаваемость по 6, освещение 7, навигация 8, производительность 6. Целевая планка в промпте была 8–9, и авторы прямо пишут, что оценки под неё не подгонялись.</p><p>Список открытых расхождений тоже опубликован. Контур универмага Saks в OSM нарисован по границе участка, поэтому тротуар на Пауэлл получился шириной 2,1 метра вместо примерно четырёх. Здание Уиттелл смоделировано высотой 56 метров по OSM вместо 66 по справочникам. У 34 витрин из переписи арендатор не установлен: они отрисованы нейтральной пустой фасцией, а не выдуманы. Есть и исправленные баги с понятной причиной: все стены фасадов были сгенерированы с обратной обмоткой полигонов, и отсечение задних граней прятало их там, где сзади не было панели, из-за чего стены «исчезали», если поднять взгляд.</p><p>Замеры производительности сделаны в headless Chromium при 1920 на 1080 через ANGLE и Metal, то есть на Mac. Днём в центре площади 63 кадра в секунду при 2108 вызовах отрисовки и 7,25 млн треугольников; на перекрёстке Пауэлл и Гири 51 кадр, 2646 вызовов и 7,91 млн треугольников; внутри Apple 79 кадров; в виде сверху 32 кадра при 3522 вызовах и 8,52 млн треугольников. Куча JavaScript держится на 290 МБ днём и 242 МБ ночью. Ночной режим дешевле по вызовам отрисовки, но кадров на перекрёстках даёт меньше: 38,5 на Пауэлл и Гири.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-09-03/f47a4f1e-3e67-4506-a03b-4d9403e9be35.webp" alt="Пауэлл-стрит ночью в реконструкции" /><figcaption>Пауэлл-стрит ночью. Кадр из репозитория PhiloLabs (MIT)</figcaption></figure><h2>Чего в репозитории нет</h2><p>На вопрос о цене и времени автор публикации на Hacker News под ником surreal_ ответил, что это результат одного прогона по очень длинному промпту с явными указаниями про субагентов и цикл самопроверки: около двух часов работы, примерно 8 млн токенов и около $33 по тарифам API. Сколько было неудачных попыток до этого прогона и вмешивался ли человек по ходу, не уточняется. Кто стоит за PhiloLabs, тоже неизвестно: организация на GitHub создана в феврале 2026 года, без описания и сайта, в ней пять репозиториев. В комментариях автор пишет, что команда уже сгенерировала большинство туристических мест Сан-Франциско и готовит обзорную статью про «моделирование мира через код»; сроков нет.</p><p>Разработчик игр в комментариях на HN отметил, что в похожих экспериментах модели выдают неоптимизированную геометрию с избыточным числом полигонов, и что для игровых ассетов он предпочитает просить у модели низкополигональные силуэты. Автор проекта ответил, что при генерации мира кодом геометрия строится из примитивов и параметрических конструкций, поэтому топология чистая по построению, а слабое место скорее текстуры. Числа из QA-отчёта, 7–8 млн треугольников и больше двух тысяч вызовов отрисовки на кадр для одного квартала, по нашей оценке, всё же говорят о заметном запасе для оптимизации.</p><h2>Что с этим делать</h2><p>Репозиторий стоит открыть не ради прогулки по Сан-Франциско, а ради конвейера. Файл PROMPT.md показывает, как сформулирована задача на автономную сборку: перечень обязательных объектов с адресами, требование Three.js в рантайме и Blender только офлайн, обязательная сверка с фотографиями на каждой вехе и независимые рецензенты. Папка qa/ пригодится как образец проверки против реальности, а не против самого себя: фиксированные ракурсы по координатам, автоматические сравнения и отчёты, которые прямо называют, где результат ниже планки.</p><p>Код и сгенерированные ассеты отданы под MIT, но исходные данные лицензированы отдельно: контуры зданий получены из OpenStreetMap под ODbL, высоты USGS в общественном достоянии, а сами эталонные фотографии в репозитории не распространяются: для каждого сектора сохранена только их атрибуция, чтобы набор можно было скачать заново. Названия и логотипы магазинов, как оговаривают авторы, остаются собственностью их владельцев, так что перед использованием сцены в своём продукте права на товарные знаки нужно проверять отдельно.</p><p>Источники: <a href="https://github.com/PhiloLabs/fable51-worlds">Репозиторий PhiloLabs/fable51-worlds</a>, <a href="https://github.com/PhiloLabs/fable51-worlds/blob/main/union-square-sf/README.md">README мира Union Square</a>, <a href="https://github.com/PhiloLabs/fable51-worlds/blob/main/union-square-sf/FINAL_QA_REPORT.md">FINAL_QA_REPORT.md</a>, <a href="https://news.ycombinator.com/item?id=49541458">Обсуждение на Hacker News</a></p><p>Изображение на обложке: PhiloLabs, fable51-worlds (MIT)</p>]]></content:encoded>
    </item>
    <item>
      <title>Safari 27 получит переписанный загрузчик модулей и рабочий top-level await</title>
      <link>https://tproger.ru/news/safari-27-poluchit-perepisannyj-zagruzchik-modulej-i-rabochij-top-l</link>
      <comments>https://tproger.ru/news/safari-27-poluchit-perepisannyj-zagruzchik-modulej-i-rabochij-top-l?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/safari-27-poluchit-perepisannyj-zagruzchik-modulej-i-rabochij-top-l</guid>
      <description><![CDATA[<p>WebKit переписал загрузчик ES-модулей с нуля на C++ и исправил многолетние ошибки top-level await в Safari. Что ломалось и когда убирать обходные пути.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/safari-27-poluchit-perepisannyj-zagruzchik-modulej-i-rabochij-top-l">Safari 27 получит переписанный загрузчик модулей и рабочий top-level await</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Браузеры]]></category>
      <category><![CDATA[Safari]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 02 Sep 2026 16:00:09 GMT</pubDate>
      <content:encoded><![CDATA[<p>Команда WebKit 2 сентября <a href="https://webkit.org/blog/18227/fixing-top-level-await-in-safari/">рассказала</a>, что переписала загрузчик JavaScript-модулей Safari с нуля и тем самым закрыла многолетние ошибки top-level await, из-за которых модули с await на верхнем уровне падали с сообщением «Cannot access ... before initialization». Исправление уже есть в Safari Technology Preview 251 и бете Safari 27; в стабильный Safari оно попадёт с выходом Safari 27.</p><p>Для фронтендера это означает, что один из последних поводов не использовать top-level await в продакшене исчезает: в Chrome и Firefox функция работает давно, а Safari оставался браузером, ради которого библиотеки и сборщики держали обходные пути. По словам автора публикации, инженера WebKit Кая Тамкуна, вместе с загрузчиком в порядок приведены ES-модули в целом, так что после релиза Safari 27 на них «можно опираться, не задумываясь».</p><ul><li>Старый загрузчик модулей Safari был написан по черновику WHATWG Loader, последний раз обновлённому в январе 2016 года, ещё до появления async/await в языке.</li><li>Top-level await, добавленный в ECMAScript 2022, реализовали поверх этой устаревшей основы, отсюда ошибки порядка выполнения и обращения к неинициализированным экспортам.</li><li>В январе 2026 года команда удалила старый загрузчик целиком и переписала его на C++ по алгоритмам спецификации ECMAScript, отказавшись от самохостируемого JavaScript.</li><li>Проверка: тест-кейсы от команды Bun, чей рантайм на JavaScriptCore унаследовал те же баги, фаззер графов модулей со сравнением вывода с другими движками, все модульные тесты test262 и исправленные тесты WPT без регрессий.</li><li>Попробовать можно сегодня в Safari Technology Preview 251 или Safari 27 beta; сроки стабильного Safari 27 в публикации не названы.</li></ul><h2>Что именно ломалось</h2><p>Top-level await позволяет писать await прямо в теле ES-модуля: модуль приостанавливается до разрешения промиса, и вместе с ним ждут все модули, которые его импортируют, а независимые ветки графа зависимостей продолжают выполняться. В Safari нарушался именно порядок: загрузчик неверно выбирал, когда считать модуль выполненным. WebKit показывает это на минимальном примере, где один и тот же модуль с задержкой на верхнем уровне динамически импортируется три раза подряд.</p><p>Ожидаемый порядок завершения импортов 1, 2, 3. Старый загрузчик выдавал 2, 3, 1, причём второй и третий импорт завершались с ошибкой Cannot access 'someArray' before initialization, и только первый печатал список экспортов. Причина одна: когда первый импорт доходил до await и уступал управление, промис второго импорта должен был ждать окончания выполнения модуля, но из-за ошибки разрешался сразу. Код получал ещё не инициализированные экспорты и падал. С новым загрузчиком вывод становится ожидаемым: 1, 2, 3, и каждый раз с корректным списком ключей.</p><h2>Почему баг не удавалось починить годами</h2><p>Спецификация ECMAScript оставляет часть механики модулей на усмотрение хоста, в первую очередь загрузку по сети в браузере или с диска в Node.js и Bun. Загрузчик Safari писали в эпоху предложения WHATWG Loader, которое описывало эту хостовую часть и последний раз обновлялось в январе 2016 года. Тогда модули выполнялись строго синхронно, и черновика хватало. Затем предложение фактически умерло, вытесненное разделом о модулях в самом стандарте ECMAScript, а в 2022 году в язык вошёл top-level await. В WebKit его реализовали поверх алгоритмов заброшенного черновика, а не по асинхронным алгоритмам стандарта. Отсюда тонкие ошибки, которые, по словам Тамкуна, команда безуспешно пыталась закрыть несколько раз, пока не решила заменить основание.</p><p>Второе решение касается языка реализации. Старый загрузчик был самохостируемым встроенным кодом на JavaScript. У такого подхода есть плюсы: его можно инлайнить в пользовательский код и не платить за переход между JavaScript и C++. Но он медленнее стартует, потому что компилируется во время выполнения, а оптимизирующие JIT-компиляторы JavaScriptCore плохо используют его широкие паттерны применения; загрузчик к тому же не горячий путь, так что выигрыша от компиляции на лету почти нет. Новый загрузчик написан целиком на C++.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-09-02/02a4b3fb-cf74-49e7-9132-dcbbed91c537.webp" alt="Схема вызовов операций загрузчика модулей WebKit: загрузка, линковка, выполнение и вспомогательные функции" /><figcaption>Граф вызовов операций загрузчика модулей, по которому команда планировала порядок реализации. Источник: WebKit</figcaption></figure><h2>Как переписывали и проверяли</h2><p>Работа началась в январе 2026 года с удаления файла со старым загрузчиком. Дальше команда переводила псевдокод операций из спецификации ECMAScript в C++ по одной, начав с листовых функций вроде ExecuteModule и ModuleRequestsEqual, у которых нет зависимостей, и двигаясь по графу вызовов. Через несколько недель черновая реализация справлялась с типичными случаями, и появился draft pull request.</p><p>Тестов было три слоя. Во-первых, инженеры Bun, чей рантайм построен на JavaScriptCore и унаследовал те же проблемы, передали собранные ими случаи неправильного поведения; их адаптировали под консольную оболочку jsc. Во-вторых, команда написала фаззер, который генерирует большие графы модулей, часть с top-level await, часть без, и сравнивает текстовый вывод JavaScriptCore с выводом других движков байт в байт; по словам WebKit, во всех проверенных примерах новый загрузчик отработал верно. В-третьих, все модульные тесты набора test262 стали проходить, а часть ранее падавших тестов WPT исправилась без регрессий. Перед слиянием разработчики несколько недель пользовались сборкой Safari с новым загрузчиком как основным браузером.</p><h2>Что делать фронтендеру</h2><ul><li>Проверить свои модули с top-level await в Safari Technology Preview 251 или Safari 27 beta; о найденных проблемах WebKit просит сообщать на bugs.webkit.org.</li><li>Обходные пути для Safari пока не убирать: стабильный Safari 27 ещё не вышел, а старые версии Safari останутся у пользователей надолго.</li><li>Если проект работает на Bun, следить за обновлениями рантайма: он использует JavaScriptCore и, по словам WebKit, унаследовал те же ошибки загрузчика; о сроках их исправления в Bun публикация не говорит.</li></ul><p>Дата выхода стабильного Safari 27 в публикации не названа. Открытым остаётся и вопрос старых версий: о переносе исправления в уже вышедшие Safari WebKit не сообщает, так что доля пользователей на Safari 26 и старше будет определять, когда обходные пути можно убрать совсем.</p><p>Источник: <a href="https://webkit.org/blog/18227/fixing-top-level-await-in-safari/">WebKit Blog: Fixing Top-Level Await in Safari</a></p><p>Изображение на обложке: Изображение: логотип WebKit, Apple</p>]]></content:encoded>
    </item>
    <item>
      <title>Firefox для iOS получил блокировщик рекламы, а Firefox 155 функцию progress()</title>
      <link>https://tproger.ru/news/firefox-dlya-ios-poluchil-vstroennyj-blokirovshhik-reklamy-a-firefo</link>
      <comments>https://tproger.ru/news/firefox-dlya-ios-poluchil-vstroennyj-blokirovshhik-reklamy-a-firefo?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/firefox-dlya-ios-poluchil-vstroennyj-blokirovshhik-reklamy-a-firefo</guid>
      <description><![CDATA[<p>Mozilla добавила в Firefox для iOS встроенный блокировщик рекламы на WebKit Content Blocker и EasyList. Одновременно вышел Firefox 155 с CSS-функциями progress() и alpha(), Promise.allKeyed() и исправлениями уязвимостей высокого уровня. Что включить и что проверить.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/firefox-dlya-ios-poluchil-vstroennyj-blokirovshhik-reklamy-a-firefo">Firefox для iOS получил блокировщик рекламы, а Firefox 155 функцию progress()</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[Браузеры]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 01 Sep 2026 17:25:52 GMT</pubDate>
      <content:encoded><![CDATA[<p>Mozilla 1 сентября <a href="https://blog.mozilla.org/en/firefox/ad-blocker-on-ios/">добавила</a> в <b>Firefox для iOS</b> встроенный блокировщик рекламы. На iPhone это важнее, чем звучит: расширения в iOS-версии Firefox, по формулировке Mozilla, «недоступны таким же образом», как на десктопе и Android, а браузер работает на движке WebKit от Apple, поэтому uBlock Origin туда не поставить. Теперь блокировка есть в настройках, но выключена по умолчанию.</p><p>В тот же день вышел <b>Firefox 155</b> для десктопа, и для верстальщиков в нём есть чем заняться: CSS-функции progress() и alpha(), свойство font-width, attr() в любом свойстве, а в JavaScript Promise.allKeyed(). Обновиться стоит и без новинок: бюллетень безопасности MFSA 2026-82 закрывает несколько уязвимостей высокого уровня.</p><ul><li>Блокировщик в Firefox для iOS включается в Settings, затем Browsing, затем Ad Blocker; по умолчанию выключен, расширение ставить не нужно.</li><li>Работает на технологии Apple WebKit Content Blocker и списке EasyList; рекламу в поисковой выдаче и рекламу самих сайтов может пропускать.</li><li>Firefox 155: CSS progress(), alpha(), font-width, attr() везде; Promise.allKeyed() и allSettledKeyed(); согласование QUIC v2 для HTTP/3; NDJSON в JSON Viewer.</li><li>MFSA 2026-82 закрывает уязвимости высокого уровня, среди них CVE-2026-84119, 84143 и 84144, и повышение привилегий в Firefox для Android.</li><li>Минимальную версию iOS для блокировщика Mozilla не назвала; доступность функции по регионам анонс не описывает.</li></ul><h2>Почему блокировщик пришлось встраивать</h2><p>Apple разрешает сторонним браузерам блокировать контент только через свой механизм <b>Content Blocker</b>: приложение отдаёт WebKit готовый список правил, а сам движок решает, какие запросы не выполнять. Расширение не может «посмотреть» на страницу и вмешаться в её код, как это делает uBlock Origin на десктопе. Mozilla взяла список EasyList, самый распространённый набор правил для рекламы, и упаковала его в такой блок правил внутри приложения.</p><blockquote>Ad Blocker uses Apple's WebKit Content Blocker technology and the EasyList filter list.</blockquote><p>Из этой архитектуры следуют ограничения, которые Mozilla перечисляет сама. Реклама, которую сайт отдаёт с собственного домена, может остаться, потому что её не отличить от контента по URL. Реклама в результатах поиска не блокируется. Рекламные ярлыки на стартовой странице самого Firefox к блокировщику не относятся. Включается всё в три касания: Settings, Browsing, Ad Blocker; там же выключается, если сайт перестал работать.</p><h2>Что Firefox 155 даёт верстальщику</h2><p>По <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/155">заметкам для разработчиков на MDN</a>, самое полезное в CSS — функция progress(): она возвращает долю от 0 до 1, показывающую, где значение находится между двумя границами. Раньше такой расчёт делали руками через calc(), теперь адаптивные размеры шрифта и отступов записываются короче. Функция alpha() меняет прозрачность цвета без переписывания его в rgb(), а attr() отныне работает в любом свойстве, а не только в content.</p><p>В JavaScript добавились Promise.allKeyed() и Promise.allSettledKeyed(): они принимают объект с промисами и возвращают объект с теми же ключами, так что не нужно сопоставлять результаты по индексам массива. Свойство font-width заменило font-stretch, старое имя оставлено как псевдоним. Если импорт модуля упал из-за временной ошибки сервера, повторный импорт теперь может выполниться успешно; это касается JS, JSON, CSS и текстовых модулей. В сетевом стеке появилось согласование QUIC version 2 для HTTP/3, а JSON Viewer в DevTools научился читать NDJSON построчно.</p><p>Для автотестов: в WebDriver BiDi исправлены перезагрузка фреймов, подписки и конфликт с DevTools, в WebRTC добавлена поддержка двухбайтовых RTP-заголовков с идентификаторами от 15 и новые поля статистики. Перед использованием progress() и alpha() в проде стоит сверить поддержку в Chrome и Safari на caniuse: MDN описывает только Firefox.</p><h2>Почему обновиться сегодня</h2><p><a href="https://www.mozilla.org/en-US/security/advisories/mfsa2026-82/">Бюллетень MFSA 2026-82</a> сопровождает релиз набором исправлений. CVE-2026-84119 (выход из песочницы через use-after-free в навигации DOM), CVE-2026-84143 и CVE-2026-84144 помечены как уязвимости высокого уровня, CVE-2026-84142 объединяет внутренне найденные ошибки памяти умеренного уровня. Отдельно для Firefox на Android бюллетень называет повышение привилегий (CVE-2026-84117, высокий уровень) и раскрытие информации через WebExtensions (CVE-2026-84127, умеренный уровень). Обновление на 155 закрывает всё это разом; на десктопе и Android оно важнее новых функций.</p><h2>Что дальше</h2><p>Mozilla объясняет встроенный блокировщик ограничениями расширений на iOS и формулирует их как «недоступны таким же образом», а не «невозможны навсегда»: в ЕС и Японии Apple уже допускает альтернативные движки, и если Firefox на iOS когда-нибудь перейдёт на Gecko, вопрос с расширениями встанет заново. Пока же блокировщик работает ровно в тех пределах, которые задаёт Content Blocker API.</p><p>Источники: <a href="https://blog.mozilla.org/en/firefox/ad-blocker-on-ios/">Introducing Ad Blocker for Firefox on iOS (Mozilla)</a>, <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/155">Firefox 155 for developers (MDN)</a>, <a href="https://www.mozilla.org/en-US/security/advisories/mfsa2026-82/">Mozilla Foundation Security Advisory 2026-82</a></p><p>Изображение на обложке: Mozilla</p>]]></content:encoded>
    </item>
    <item>
      <title>Zero на Three.js: инженерный разбор интерактивного сайта</title>
      <link>https://tproger.ru/articles/zero-na-three-js-inzhenernyj-razbor-interaktivnogo-sajta</link>
      <comments>https://tproger.ru/articles/zero-na-three-js-inzhenernyj-razbor-interaktivnogo-sajta?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/zero-na-three-js-inzhenernyj-razbor-interaktivnogo-sajta</guid>
      <description><![CDATA[<p>Разбираем, как BUNQ LABS сделала zero.university: виртуальный скролл, шейдеры, KTX2/DRACO, адаптивное качество и загрузка текстур без фризов. Проверьте техники.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/zero-na-three-js-inzhenernyj-razbor-interaktivnogo-sajta">Zero на Three.js: инженерный разбор интерактивного сайта</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[Дизайн интерфейсов и UX]]></category>
      <category><![CDATA[Фреймворки и библиотеки]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 28 Jul 2026 04:19:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Представьте, что вы заходите на сайт, а он не пускает. Нет кнопки «войти» — только поле, где нужно нарисовать ноль. Как только круг замыкается, из линии расходится инеевый узор, и сайт начинает рассказывать историю. Это не декоративная заглушка: так работает <a href="https://zero.university/">zero.university</a> — кейс, где первый жест пользователя сразу задаёт тон всему повествованию.</p><p><b>Zero</b> — иммерсивный лендинг индийского стартапа Zero University, который предлагает альтернативу классическому университетскому пути. Сайт превращает скролл в шестиактную историю: традиционное образование, разбитое стекло, горящие деньги, уничтоженные сертификаты, тоннель из логотипа ZERO и, наконец, интерактивная карта города с офисами реальных компаний. Цель — не просто показать красивую картинку, а провести пользователя через аргумент: диплом перестаёт быть гарантией, а навыки открывают дорогу в индустрию.</p><p>Проект разрабатывала студия <b>BUNQ LABS</b> четыре месяца. Исходные ассеты занимали больше гигабайта: несжатые Blender-сцены, 8K-текстуры и запечённые анимации. Финальная сборка уместилась в 10 МБ и держит 60 FPS даже на бюджетном Android. Команда опубликовала технический разбор на Codrops, а мы выделили решения, которые можно перенести в свои WebGL-проекты.</p><ul><li>Первый жест как ворота: распознавание нуля — это простая проверка угла, округлости и замкнутости, а не нейросеть.</li><li>Виртуальный скролл заменяет нативный: одно число управляет загрузкой, анимацией, шейдерами и текстом.</li><li>Ассеты важнее рендера: DRACO, KTX2/ETC1S, атласы и собственный превьювер сжали гигабайты до мегабайтов.</li><li>Текстурные загрузки — главный источник фризов; декодинг в воркере и очередь по requestIdleCallback решают проблему.</li><li>Адаптивное качество по frame time позволяет не гадать о мощности устройства.</li><li>Мобильная точность шейдеров: mediump на Adreno и Mali — это реальный 16-битный float, и его недостаточно для сложных эффектов.</li></ul><p>Разбираем, как это устроено изнутри, и что из этого можно взять в свою работу.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-07-28/00e5f2ad-8fc7-49b9-88da-322a34a6bef1.webp" alt="Шесть этапов интерактивного повествования zero.university: от традиционного образования до интерактивной карты города." /><figcaption>Схема повествования: шесть этапов и пять «ворот», через которые проходит пользователь. Источник: Codrops / BUNQ LABS</figcaption></figure><h2>Сюжет из шести этапов и пяти ворот</h2><p>Интерактивный нарратив строится вокруг пяти «ворот» — точек, где скролл останавливается и ждёт действия пользователя. Сначала нужно нарисовать ноль, затем удерживать касание, чтобы разбить стекло, потом — чтобы запуститься сквозь тоннель. Каждый жест связан с сюжетным поворотом: обещание университетского пути буквально трескается, за ним появляется статистика безработицы, диплом превращается в горящую бумагу, сертификаты рвутся на полосы.</p><h3>От обещания к свободе</h3><p>Шесть этапов идут от иллюзии контролируемого пути к интерактивной карте, где пользователь сам выбирает, куда смотреть. Финальный экран — город с башней Zero University в центре. Можно приближать, панорамировать и открывать карточки ролей, сценариев и инструментов. Это не просто финал: после того как сайт вёл пользователя за руку, он отдаёт управление обратно.</p><h2>Архитектура: один скролл управляет всем</h2><p>Одно из главных архитектурных решений — отказ от нативного скролла браузера. Нет ScrollTrigger, нет огромного прокручиваемого DOM. Вместо этого события колёсика и тача обновляют виртуальное значение скролла, которое плавно догоняет цель. Всё остальное — загрузка ассетов, анимации, тайминги шейдеров, текст и оверлеи — читают это единственное число.</p><p>Проект разбит на девять таких сегментов; загрузчик одновременно выполняет роль первых ворот. Каждый сегмент самодостаточен: у него свой жизненный цикл, свои объекты и своя утилизация. Это упростило поддержку и отладку. Переход к любому этапу воспроизводит жизненные циклы всех предыдущих сегментов, поэтому состояние всегда консистентно — как будто пользователь дошёл до этого места естественным скроллом.</p><h2>Конвейер 3D-ассетов: как уместить 1 ГБ в 10 МБ</h2><p>Большую часть четырёх месяцев ушла не на код, а на подготовку ассетов. Исходники пришли из Blender: несжатая геометрия, 8K-текстуры, запечённые анимации — всё вместе больше гигабайта. Первый месяц команда тратила на то, чтобы выбрать формат для каждого типа ресурсов.</p><h3>DRACO и KTX2</h3><p>Вся геометрия отправляется со сжатием DRACO, декодеры для которого хостятся локально в public/vendor/. Урок команда усвоила на собственном опыте: замедление на gstatic и unpkg привело к тому, что все сжатые ассеты перестали декодироваться, хотя сами файлы лежали на ихних серверах. Если декодер зависит от чужого CDN, весь пайплайн зависит от него.</p><p>Самый большой выигрыш дали текстуры. PNG может быть маленьким на диске, но в видеопамять он загружается распакованным. Текстура 2048² занимает около 16 МБ VRAM независимо от размера файла. Формат KTX2 со сжатием ETC1S остаётся сжатым на GPU, занимает меньше памяти и загружается быстрее.</p><h3>Собственный превьювер компрессии</h3><p>С KTX2 есть проблема: ETC1S — lossy, и локально его не посмотреть обычным просмотрщиком. Чтобы не гадать с настройками, команда сделала внутренний дашборд: сжатая и несжатая версия каждой картинки и видео рядом. Так можно было подобрать степень сжатия индивидуально — усилить там, где артефакты не заметны, сохранить качество для ключевых объектов и отключить мипмапы, где они не нужны. Инструмент простой, но редко встречается в WebGL-пайплайнах, хотя экономит огромное количество времени и памяти.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-07-28/3a57d9e3-14e7-4f55-8aad-78cee34f1da3.webp" alt="Внутренний дашборд команды для сравнения сжатой и несжатой версии каждого ассета." /><figcaption>Превьювер компрессии: сжатая и несжатая версии текстур и видео рядом. Источник: Codrops / BUNQ LABS</figcaption></figure><h3>Атласы вместо дюжин картинок</h3><p>Похожие текстуры собрали в общие атласы. Например, все текстуры рук уместили в один атлас 4×4, а каждая mesh сдвигала UV-координаты, чтобы взять свой кусок. Спрайты текста, сертификаты, бумажные обрывки, облака, монеты и осколки стекла тоже ушли в атласы. Больше 50 отдельных изображений превратились примерно в дюжину атласов, а большинство градиентных фонов заменили несколькими строками GLSL. Ранние сборки весили 35–40 МБ, финальная — меньше 10 МБ. При этом интерактивная карта мира вынесена в отдельную группу загрузки, чтобы не блокировать открытие сайта.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-07-28/e1686dcf-b13a-494e-94b3-0e32f0eb880a.webp" alt="Атлас текстур рук 4×4: каждая mesh сдвигает UV-координаты, чтобы взять свой кусок." /><figcaption>Атлас рук: все текстуры рук упакованы в одну текстуру 4×4. Источник: Codrops / BUNQ LABS</figcaption></figure><h2>Текстурные загрузки: главный источник фризов</h2><p>Уменьшить скачивание — только половина дела. Сжатые текстуры всё равно нужно загрузить в GPU на главном потоке, и крупная текстура легко блокирует рендер на 50 мс — достаточно, чтобы потерять несколько кадров. Если эта загрузка случается впервые во время скролла, подёргивание заметно сразу. Сайт может хорошо показывать себя в бенчмарках и при этом чувствоваться вязким.</p><ul><li>Декодировать вне главного потока — через createImageBitmap(). Тогда во время рендера остаётся только загрузка в GPU.</li><li>Загружать в GPU в простое — декодированные текстуры ставят в очередь и выгружаются по requestIdleCallback, пока есть запас времени.</li><li>Разбивать большие атласы на плитки 256² и загружать по одной на кадр, чтобы не выбиваться из бюджета кадра.</li></ul><p>Когда известно, что следующий этап вот-вот появится — после загрузчика или во время перехода между воротами — очередь сбрасывается синхронно. Все нужные текстуры уже в видеопамяти, прежде чем они появятся на экране.</p><h2>Адаптивное качество по реальному frame time</h2><p>Невозможно заранее знать, на каком устройстве откроется сайт. Рендерер поэтому постоянно измеряет время кадра по скользящему окну и динамически меняет уровень качества. Если рендеринг замедляется — понижается tier. Если производительность стабильно высокая — tier снова растёт. Задержка между переключениями не даёт постоянно метаться у границы.</p><p>Уровни качества влияют только на визуальную полировку: devicePixelRatio, количество сэмплов размытия, геометрию монет, разрешение текста. Сама история остаётся неизменной и на флагмане, и на бюджетном телефоне. Для особо тяжёлых моментов — например, разбитие стекла — рендерер временно понижает пиксельное соотношение и отключает размытие и иней, прячет затраты внутри самого жеста.</p><h2>Шейдеры под каждый эффект</h2><p>Каждый ключевой момент использует собственный шейдер. ИИ помогал сгенерировать первые версии, но все финальные шейдеры переписывались и дорабатывались вручную. Вот несколько приёмов, которые стоит запомнить.</p><h3>Постпроцессинг из нескольких проходов</h3><p>Кадр собирается из цепочки проходов: основная 3D-сцена, процедурный фон, преломление стекла, иней и след от жеста, глубина резкости, foreground с зернистостью и тональной картой, отложенный текст, и в конце — разбитое стекло. Стеклянный, текстовый и шейдер разбития инициализируются лениво и прогреваются в простое, чтобы не попадать на критический путь загрузчика.</p><h3>Иней из нарисованного нуля</h3><p>Эффект инея строится на ping-pong-буфере из четырёх проходов: горизонталь, вертикаль и две диагонали. Каждый проход распространяет нарисованный штрих, захватывая самые яркие соседние пиксели и создавая восьмиугольный паттерн роста. Яркость текстуры льда модулирует шаг распространения, край получается кристаллическим. После замыкания контура центр масс штриха становится точкой отсчёта для радиального таяния.</p><h3>Освещение без источников света</h3><p>Реалтаймовое освещение скинненной меши рук было бы слишком дорогим, а свет должен был точно совпадать с оригинальным артом. Поэтому освещение запекли в текстуры и смешивают между ними. Используются два слота: один содержит текущий ключевой кадр, другой — следующий. По мере проигрывания анимации новый слот плавно вытесняет старый.</p><p>Перед смешиванием альфа предумножается, чтобы вокруг прозрачных краёв рук не появлялись тёмные ореолы. Обе текстуры освещения лежат в одном атласе, поэтому переключение между ключами требует только обновления двух UV-смещений и коэффициента смешивания.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-07-28/cb760b51-b0f4-4804-aff6-58ce015d2fda.webp" alt="Две запечённые текстуры освещения рук внутри одного атласа для плавного переключения." /><figcaption>Запечённое освещение рук: два ключевых кадра в одном атласе смешиваются по прогрессу анимации. Источник: Codrops / BUNQ LABS</figcaption></figure><h3>Горящие деньги</h3><p>Эффект сгорания использует noise-driven distance field. Фронт горения проходит по купюре, слегка опережая точку дискарда. Перед ним появляется тонкий HDR-ободок углей, затем обугленная поверхность. FBM — самая дорогая часть шейдера, поэтому фрагменты отсекаются как можно раньше, если они ещё не достигли порога горения.</p><h3>Рвущиеся сертификаты</h3><p>Каждая вершина хранит индекс полосы, к которой принадлежит фрагмент диплома. Фронт разрыва движется слева направо, и каждая полоса отделяется и падает независимо со своим вращением. Нормали для освещения строятся аналитически из той же волновой функции, которая анимирует меш, поэтому normal map не нужен.</p><h3>Тоннель ZERO</h3><p>Тоннель получается выдавливанием поперечного сечения из логотипа ZERO. Импульс яркости распространяется по мировой координате Z, поэтому эффект остаётся бесшовным, даже когда секции тоннеля повторяются.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-07-28/bfbebb17-dc15-4bbb-b00c-a1fb7f98c13f.webp" alt="Тоннель ZERO, полученный выдавливанием сечения логотипа." /><figcaption>Тоннель ZERO: импульс яркости распространяется по мировой координате Z, сохраняя бесшовность. Источник: Codrops / BUNQ LABS</figcaption></figure><h3>Текст, который приходит в фокус</h3><p>Нарративный текст появляется не мгновенно, а проявляется через семиотводное гексагональное размытие. Радиус размытия зависит от прогресса появления, поэтому буквы будто фокусируются. Проход выполняется в отложенном текстовом проходе и полностью пропускается, когда текст не виден.</p><h3>Точность на мобильных</h3><p>Одна из самых неприятных находок возникла на реальных мобильных GPU. Несколько шейдеров пришлось явно перевести на highp float, потому что на Adreno и Mali mediump — это настоящий 16-битный float. На десктопе ошибок не было, а на телефоне полосы диплома двигались одинаково, а эффект горения покрывался бэндами. Вывод: тестировать на реальных мобильных устройствах нужно с первых дней, а не перед релизом.</p><h2>Дизайн взаимодействий</h2><p>Исходный материал от заказчика состоял из картинок и видео, поэтому поведение каждых ворот проектировали с нуля. Большинство используют общую систему «нажми и удерживай». Общий конфиг задаёт момент появления подсказки и длительность удержания, а каждые ворота реализуют визуал через собственные хуки.</p><p>Пока пользователь удерживает касание, кадр постепенно смещается в тёмно-красный. Когда стекло разбивается, цвет возвращается примерно за 200 мс — это создаёт ощущение, что осколки выбивают темноту. Проверили вариант в 400 мс: он ощущался заметно слабее. Звук разбития привязан к первому отрисованному кадру, а не к таймеру: на медленных устройствах таймер может сыграть раньше анимации, и эффект рассинхронится.</p><h2>Финальная награда — интерактивный мир</h2><p>Последняя последовательность запуска выносит пользователя в открытое небо. Облака расступаются, открывая город с башней Zero University в центре, над которой появляется ореол — финальные ворота. Камера приземляется в интерактивную карту, где можно панорамировать, зумить и открывать карточки с ролями, сценариями и инструментами. Плашка Join Beta превращается в форму waitlist. После того как сайт вёл пользователя через историю, он отдаёт ему контроль — и просит остаться.</p><h2>FAQ</h2><h2>Выводы</h2><p>Самый важный урок проекта — подготовка ассетов и загрузка в GPU заслуживают не меньше внимания, чем сам рендеринг. Инструменты вроде превьювера компрессии, локальных декодеров, очереди загрузки и адаптивного менеджера качества редко выглядят героически, но именно они превратили гигабайт исходников в 10-мегабайтный сайт, который не тормозит на дешёвом телефоне.</p><blockquote>ИИ генерировала первые версии кода и прототип, который выиграл нам проект, за 48 часов. Но полировка — сотни мелких решений, тестирование на реальных устройствах и инженерная интуиция — осталась за людьми.</blockquote><p><b>Источник:</b> технический разбор команды BUNQ LABS на <a href="https://tympanus.net/codrops/2026/07/17/zero-the-engineering-behind-a-defiant-interactive-narrative/">Codrops</a>.<br />Попробуйте открыть <a href="https://zero.university/">zero.university</a> на десктопе и на бюджетном смартфоне — и сравните, где заметнее компромиссы качества.</p>]]></content:encoded>
    </item>
    <item>
      <title>React-паттерн, который все используют, но он убивает производительность</title>
      <link>https://tproger.ru/articles/react-pattern-kotoryj-vse-ispolzuyut-no-on-ubivaet-proizvodite</link>
      <comments>https://tproger.ru/articles/react-pattern-kotoryj-vse-ispolzuyut-no-on-ubivaet-proizvodite?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/react-pattern-kotoryj-vse-ispolzuyut-no-on-ubivaet-proizvodite</guid>
      <description><![CDATA[<p>Почему React.memo перестаёт работать из-за inline-пропсов и как это исправить. Разбираем на примере со 200 строками и замерами.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/react-pattern-kotoryj-vse-ispolzuyut-no-on-ubivaet-proizvodite">React-паттерн, который все используют, но он убивает производительность</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 24 Jul 2026 04:56:37 GMT</pubDate>
      <content:encoded><![CDATA[<p>Вы обернули список в React.memo, добавили useCallback на обработчики и ждёте, что интерфейс полетит. Но при каждом вводе в поисковую строку список всё равно тормозит, а Profiler показывает 200 лишних рендеров. Чаще всего виноват не React, а один крошечный JSX-паттерн, который встречается в каждом втором компоненте.</p><p>В статье разбираем, почему React.memo сравнивает пропсы по ссылке, как inline-объекты и inline-функции обнуляют эту оптимизацию, и какие два простых приёма действительно возвращают производительность.</p><p>React.memo пропускает рендер только тогда, когда все пропсы равны по Object.is. Для объектов и функций это проверка по ссылке.</p><p>Inline-объекты и inline-колбэки в JSX создают новую ссылку на каждый рендер родителя, поэтому memo видит «новые» пропсы и снова рисует дочерний компонент.</p><p>В эксперименте со 200 memoизированными строками это дало 243,9 мс на один keystroke; после стабилизации ссылок — 6 мс.</p><p>Сначала выносите статические объекты за пределы компонента, а useCallback применяйте только там, где колбэк действительно пересекается с memoизированным потомком.</p><p>Оптимизировать всё подряд не нужно: измеряйте в Profiler, а не добавляйте хуки «на всякий случай».</p><h2>Как React решает, рендерить компонент заново или нет</h2><p>Когда родитель перерисовывается, React не делает послаблений дочерним элементам только потому, что они обёрнуты в React.memo. Он сравнивает новые пропсы со старыми. Если каждый проп проходит проверку Object.is, React может «отказаться» от рендера — bailout. Если хотя бы один проп не равен, компонент рисуется заново.</p><p>Для примитивов Object.is работает очевидно: 1 === 1 и 'hello' === 'hello'. А вот для объектов и функций сравнение идёт по ссылке.</p><p>Два объекта с одинаковым содержимым — это разные объекты в памяти. То же самое с функциями. Поэтому, когда вы пишете style=\{\{ padding: 16 \}\}, React получает новую ссылку на каждом рендере и считает проп изменившимся.</p><h2>Почему inline-пропсы — это не микрооптимизация, а поломка контракта</h2><p>Сам по себе объект в JSX не вреден. Если компонент дешёвый, редко перерисовывается и не обёрнут в memo, inline-пропсы почти не влияют на скорость. Проблема появляется, когда три условия накладываются друг на друга:</p><ul><li>родитель перерисовывается часто — поиск, скролл, фильтры, live-данные;</li><li>дочерний компонент или поддерево достаточно тяжёлые, чтобы лишний рендер был заметен;</li><li>вы уже добавили React.memo и ожидаете, что React будет пропускать работу.</li></ul><p>В такой ситуации нестабильные ссылки не просто добавляют накладных расходов — они полностью отменяют ту оптимизацию, ради которой вы взяли React.memo. UI продолжает работать, но лагает ввод, тормозят списки, а в Profiler видно, что дерево горит жёлтым при каждом чихе.</p><h3>Классический опасный пример</h3><p>Представьте список товаров из 200 строк. Каждая строка обёрнута в memo, но в месте вызова передаются inline-пропсы:</p><p>Здесь style и onAddToCart создаются заново при каждом рендере ProductList. Для memo это сигнал, что у каждой строки изменились пропсы, и все 200 компонентов рисуются снова.</p><h2>Эксперимент: от 243,9 мс до 6 мс на один keystroke</h2><p>Автор оригинальной статьи собрал контрольный пример: поисковый интерфейс со 200 memoизированными строками. Все строки получают одинаковые логические значения, но новые ссылки на объекты и функции. Результат после шести введённых символов: каждая видимая строка отрендерилась 14 раз.</p><p>В React DevTools Profiler один keystroke дал коммит длительностью 243,9 мс, в котором подсветились все 200 волокон строк. Инструмент @welldone-software/why-did-you-render прямо указал причину: props.style — «different objects that are equal by value», props.onAddToCart — «different functions with the same name».</p><p><b>Почему 14 рендеров?</b><br />
Каждый ввод в поиск меняет searchTerm, родитель перерисовывается, и inline-пропсы дают строкам новые ссылки. Счётчик рендеров растёт на каждый keystroke, даже если отфильтрованные товары не изменились.</p><h2>Как починить: два приёма вместо дюжины хуков</h2><p>Чтобы восстановить bailout, нужно сделать так, чтобы неизменяющиеся значения не получали новую ссылку на каждом рендере. Правило простое: сначала вынести, потом закешировать.</p><h3>1. Статические объекты — за пределы компонента</h3><p>Если объект не зависит от пропсов и состояния, создайте его один раз на уровне модуля. Это дешевле любого хука и не требует dependency-массива.</p><h3>2. Динамические колбэки — useCallback</h3><p>Если функция передаётся в memoизированный компонент и не должна меняться без причины, оберните её в useCallback со стабильным массивом зависимостей.</p><p>После этих двух изменений в эксперименте время рендера упало с 243,9 мс до 6 мс, счётчики строк застыли на 2, а Why Did You Render замолчал — avoidable re-renders исчезли.</p><h2>Когда не нужно ничего стабилизировать</h2><p>Главная ошибка — оборачивать в useCallback каждую функцию и выносить каждый объект за компонент. React сам по себе быстрый, а мемоизация — это контракт, а не стиль кодирования.</p><ul><li>Компонент дёшев и редко перерисовывается — не тратьте когнитивный бюджет команды.</li><li>Дочерний элемент не обёрнут в React.memo — тогда стабильные ссылки ничего не экономят.</li><li>Значение зависит от часто меняющегося состояния — useCallback с нестабильным массивом зависимостей всё равно будет пересоздаваться.</li><li>Вы ещё не замерили в Profiler — оптимизация без измерений почти всегда лишняя работа.</li></ul><p>React Compiler, который сейчас выходит в стабильное состояние, автоматически мемоизирует многое из того, что раньше делали вручную. Но и он не отменяет понимания ссылочной стабильности: useMemo и useCallback остаются полезными, когда нужен точный контроль, например для зависимостей эффектов.</p><h2>Часто задаваемые вопросы</h2><h2>Выводы</h2><p>Inline-объекты и inline-колбэки в JSX — не антипаттерн сами по себе. Они становятся проблемой только на границе memoизации, где React ожидает стабильные ссылки. Как только вы понимаете это правило, многие «таинственные» лишние рендеры перестают быть таинственными.</p><ol><li>Профилируйте до оптимизации, а не после.</li><li>Вынесите статические объекты за компонент — это самый дешёвый способ стабилизации.</li><li>Применяйте useCallback только для колбэков, которые уходят в memoизированные потомки.</li><li>Проверяйте себя через React DevTools Profiler и Why Did You Render.</li><li>Не забывайте про React Compiler, но не полагайтесь на него как на волшебную палочку.</li></ol><blockquote>Мемоизация — это контракт. Если потомок рассчитывает на стабильные ссылки, родитель должен их обеспечить. Нарушение этого контракта стоит намного дороже, чем отсутствие мемоизации вовсе.</blockquote><p><b>Источник:</b> <a href="https://blog.logrocket.com/react-pattern-everyone-uses-kills-performance/" rel="noopener noreferrer">LogRocket — The React pattern everyone uses that kills performance</a>.</p><p>Проверьте свой текущий проект: откройте React DevTools Profiler, введите что-нибудь в поиск и посмотрите, сколько компонентов подсветится жёлтым только из-за новой ссылки в пропсах.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как исправить hydration-ошибки RSC в Next.js: практический гид</title>
      <link>https://tproger.ru/articles/kak-ispravit-hydration-owibki-rsc-v-next-js-prakticheskij-gid</link>
      <comments>https://tproger.ru/articles/kak-ispravit-hydration-owibki-rsc-v-next-js-prakticheskij-gid?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-ispravit-hydration-owibki-rsc-v-next-js-prakticheskij-gid</guid>
      <description><![CDATA[<p>Разбираем, почему возникают hydration mismatches в React Server Components и Next.js App Router, и как находить, исправлять и предотвращать их в production.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-ispravit-hydration-owibki-rsc-v-next-js-prakticheskij-gid">Как исправить hydration-ошибки RSC в Next.js: практический гид</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Отладка]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 21 Jul 2026 11:37:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Hydration mismatch в Next.js легко поймать в next dev, но в production он превращается в минимизированный код ошибки React и ссылку на декодер. В статье разбираем, почему ошибки гидратации особенно болезненны в приложениях с React Server Components, какие причины встречаются чаще всего и какой рабочий процесс помогает находить и предотвращать такие баги.</p><p>Речь пойдёт не о теории reconcile-алгоритма, а о практике: что проверять первым делом, как изолировать проблему через Suspense, куда смотреть в production-логах и как писать тесты, которые ловят регрессии до попадания к пользователям.</p><h2>Что такое hydration mismatch</h2><p>Гидратация — это процесс, при котором React берёт серверный HTML и навешивает на него обработчики событий, после чего страница становится интерактивной. React ожидает, что дерево, которое он отрендерил на клиенте, точно совпадёт с тем, что пришло с сервера. Если строки, атрибуты или структура различаются, возникает hydration mismatch.</p><p>В development-режиме React покажет предупреждение, текст расхождения и стек компонента. В production всё сводится к коротким кодам вроде #418 или #425. Без телеметрии вы не узнаете, на каком маршруте, в каком компоненте и из-за каких данных произошёл сбой.</p><h2>Почему это больно именно в RSC</h2><p>В классическом SSR обычно один серверный рендер и один клиентский проход гидратации. App Router добавляет Server Components, Client Components, потоковую передачу, Suspense-границы, динамические данные маршрута и RSC payload, который едет рядом с HTML.</p><p>Когда несовпадение происходит вне Suspense-границы, React может отбросить весь серверный HTML и перерендерить дерево на клиенте. Приложение заплатило цену серверного рендера, а пользователь не получил ни производительности, ни преимуществ стриминга. Это не просто предупреждение в консоли, а реальная потеря производительности.</p><h2>Ключевые выводы</h2><ul><li>Hydration mismatch в production без телеметрии почти невозможно локализовать: нужна инструментация на клиенте.</li><li>Основные причины: browser-only API, дата/время/локаль, состояние авторизации, невалидный HTML, расширения браузера, CSS-in-JS.</li><li>Граница 'use client' — это не только маркер сборки, но и граница гидратации: серверный рендер должен быть детерминирован.</li><li>Suspense-границы помогают изолировать сбой и не давать ему сломать всё дерево.</li><li>Проверяйте hydration только на production-сборке: next dev ведёт себя иначе.</li><li>Добавьте Playwright-smoke-тесты на критичные маршруты, чтобы ловить регрессии в CI.</li></ul><h2>Самые частые причины</h2><p>Перед тем как копать RSC payload или сравнивать HTML, стоит проверить шесть типовых сценариев. Они покрывают подавляющее большинство production-инцидентов.</p><ul><li><b>Browser-only API во время рендера:</b> window, document, localStorage, navigator — сервер не знает об этих значениях.</li><li><b>Дата, время и локаль:</b> Date, Intl.DateTimeFormat, относительное время — сервер обычно в UTC, пользователь в своей зоне.</li><li><b>Состояние авторизации:</b> сервер рендерит выклогнутый UI, клиент сразу видит авторизованного пользователя.</li><li><b>Невалидный HTML:</b> браузер чинит DOM до гидратации, а React сравнивает с исходным деревом.</li><li><b>Расширения и middleware:</b> браузерные плагины и edge-переписывания меняют HTML.</li><li><b>CSS-in-JS и порядок классов:</b> styled-components и Emotion могут генерировать разные имена классов при стриминге.</li></ul><h3>Browser-only API и сторонние провайдеры</h3><p>Самая частая причина — не ваш собственный код, а сторонний провайдер, который читает localStorage, window или navigator при инициализации. Под это попадают аналитика, фичер-флаги, A/B-тесты, session replay и персонализация.</p><p>Проблемный вариант: провайдер читает localStorage прямо при рендере.</p><p>Исправление: серверные флаги передаются в Client Component, а локальные оверрайды применяются после гидратации.</p><p><b>Принцип:</b> серверный и первый клиентский рендер должны получить одинаковый результат. Браузерные оверрайды включаются в useEffect после монтирования.</p><h3>Дата, время и локаль</h3><p>Сервер часто работает в UTC, а пользователь — в своей зоне. Date.toLocaleString(), Intl.DateTimeFormat и функции вроде formatDistanceToNow() дают разные строки в зависимости от часового пояса и времени между рендером и гидратацией.</p><p>Проблемный вариант: относительное время считается на сервере.</p><p>Исправление: сервер отдаёт стабильную абсолютную дату, клиент заменяет её на относительную после монтирования.</p><p>suppressHydrationWarning здесь уместен, потому что расхождение ожидаемо, ограничено листовым элементом  и управляется явно. Но оборачивать им большой контейнер, чтобы скрыть неизвестную ошибку, — значит замазать баг, а не починить его.</p><h3>Состояние авторизации</h3><p>Типичный сценарий: сервер рендерит UI для неавторизованного пользователя, потому что маршрут статический или не читает куки, а клиент сразу видит залогиненного пользователя. При каждой загрузке страница перерендеривается.</p><p>Решение — читать куки на сервере через cookies() из next/headers.</p><p>С cookies() маршрут становится динамическим — это обычно правильный компромисс для UI, зависящего от авторизации. Начиная с Next.js 15, cookies() асинхронный и требует await. В Next.js 16 синхронный доступ к request-time API полностью уходит.</p><h3>Невалидный HTML</h3><p>Браузер молча чинит невалидную вёрстку. React же гидратирует не исходную строку, а DOM, который построил браузер. Классический пример —  внутри <p></p>: браузер закроет параграф до div, и React увидит другое дерево.</p><p>Браузер превратит это в примерно такую структуру:</p><p>Если HTML приходит из CMS или редактора, валидируйте и нормализуйте его на сервере. Для собственных компонентов следите за предупреждениями validateDOMNesting в DevTools.</p><h3>Расширения браузера и middleware</h3><p>Менеджеры паролей, переводчики, грамматические плагины и другие расширения могут вставлять или переупорядочивать узлы до гидратации. Edge middleware и CDN-трансформации тоже могут переписывать HTML в пути.</p><p>Если несовпадение затрагивает только &lt;html&gt; или &lt;body&gt;, сначала подозревайте расширения. React 19 стал терпимее к инъекциям в head/body, но на старых версиях такие ошибки особенно шумные.</p><p>suppressHydrationWarning на корневых элементах — документированный escape hatch, но он работает только на один уровень вглубь и не должен использоваться для сокрытия неизвестных расхождений в продуктовом UI.</p><h3>CSS-in-JS и порядок классов</h3><p>В проектах со styled-components или Emotion имена классов могут зависеть от порядка рендера. Стриминг и Suspense меняют этот порядок, поэтому нужен registry с useServerInsertedHTML.</p><h2>Production workflow для отладки</h2><p>Не начинайте с diff-а огромных HTML-документов. Работайте по порядку, сужая область поиска.</p><ol><li>Настройте клиентскую телеметрию: перехватывайте console.error и отправляйте hydration-ошибки на свой endpoint.</li><li>Воспроизводите баг на production-сборке: next build &amp;&amp; next start, а не next dev.</li><li>Используйте Suspense-границы, чтобы изолировать проблемный участок и понять, в каком поддереве ошибка.</li><li>Сравнивайте серверный HTML (curl) и клиентский DOM после гидратации.</li><li>Когда найдёте расходящийся элемент, проверьте: browser-only API, дата/время, авторизацию, HTML-вложенность, расширения, стили.</li></ol><h3>Инструментация: instrumentation-client.ts</h3><p>Файл instrumentation-client.ts в Next.js запускается после загрузки документа, но до гидратации React. Это удобная точка для лёгкой клиентской телеметрии.</p><p>Если используете LogRocket, Sentry или другой инструмент, прикрепите тот же payload к текущей сессии — тогда можно будет увидеть, как выглядела страница в момент сбоя.</p><h3>Изоляция через Suspense</h3><p>Suspense-границы не только показывают fallback при загрузке. Если hydration mismatch случается внутри границы, React может ограничить восстановление этим поддеревом, а не переключать весь root на клиентский рендер.</p><p>Оберните поочерёдно основные секции. Если страничная ошибка исчезает после оборачивания конкретной секции, баг почти наверняка внутри неё.</p><h3>Сравнение HTML и DOM</h3><p>Получите серверный HTML через curl с нужными куками:</p><p>В Chrome DevTools после загрузки страницы скопируйте живой DOM:</p><p>Сохраните результат в /tmp/client-render.html и сравните:</p><p>Этот метод хорошо ловит HTML-слой, но может пропустить расхождения на уровне reconciler-а и RSC payload. Если raw HTML совпадает, проверяйте Client Components и браузерное состояние.</p><h2>Профилактика</h2><p>Лучше не допускать ошибки, чем потом их чинить. Несколько практик, которые помогают держать серверный и клиентский рендер синхронизированными.</p><ol><li>Server Component должен быть детерминированным: одни и те же входные данные дают одни и те же выходные HTML и RSC payload.</li><li>Все browser-only API, куки, заголовки, локаль и время должны оставаться за границей 'use client' или читаться через request-time API на сервере.</li><li>Для клиентского UI сначала рендерите стабильный baseline, а улучшения добавляйте в useEffect после гидратации.</li><li>Валидируйте HTML из внешних источников до того, как React его увидит.</li><li>Тестируйте hydration на production-сборке в CI.</li></ol><h3>Пример: стабильный baseline + клиентское улучшение</h3><p>Скелетон — это стабильная базовая разметка. Карточки товаров появляются после монтирования. Сервер и первый клиентский рендер совпадают, а пользователь получает нужный контент чуть позже.</p><h3>Smoke-тесты в CI</h3><p>Ручное тестирование пропустит регрессии. Добавьте Playwright-тест на критичные маршруты.</p><p>Запускайте такой тест только против production-сборки, никогда против next dev. Сделайте его обязательной проверкой для маршрутов, где hydration failure критичен: продуктовые страницы, дашборды, чекаут, личный кабинет.</p><h2>FAQ</h2><h2>Выводы</h2><p>Hydration mismatch — не придирка React, а реальный баг производительности и корректности. Каждое несовпадение означает, что приложение могло отбросить серверный HTML, за который уже заплатило ресурсами.</p><p>В приложениях с React Server Components, где цель — меньше клиентского JavaScript и более ранний стриминг полезного UI, hydration failures тихо отменяют эти преимущества. Поэтому важно держать браузерную логику за границей 'use client', рендерить стабильные серверные baseline и проверять гидратацию в production-условиях до того, как ошибку найдут пользователи.</p><blockquote>Hydration errors should be fixed, not ignored — официальная позиция React.</blockquote><p><b>Источник:</b> <a href="https://blog.logrocket.com/how-fix-rsc-hydration-mismatches-next-js/" rel="noopener noreferrer">LogRocket Blog — How to fix RSC hydration mismatches in Next.js</a>.</p><p>Проверьте свои критичные маршруты на production-сборке, настройте телеметрию и не давайте hydration-ошибкам копиться в консоли.</p>]]></content:encoded>
    </item>
    <item>
      <title>«Почему стоит перестать деструктурировать всё в JavaScript»</title>
      <link>https://tproger.ru/articles/pochemu-ya-perestal-destrukturirovat-vsyo-v-javascript</link>
      <comments>https://tproger.ru/articles/pochemu-ya-perestal-destrukturirovat-vsyo-v-javascript?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/pochemu-ya-perestal-destrukturirovat-vsyo-v-javascript</guid>
      <description><![CDATA[<p>Разбираем, когда деструктуризация объектов в JS и React упрощает код, а когда делает его труднее для чтения. Практические правила от Мэтта Смита.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/pochemu-ya-perestal-destrukturirovat-vsyo-v-javascript">«Почему стоит перестать деструктурировать всё в JavaScript»</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 20 Jul 2026 05:40:11 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если вы пишете на JavaScript, скорее всего, деструктуризация — один из первых паттернов, который вы используете каждый день. Но что, если автоматическая деструктуризация делает код не короче, а труднее для чтения?</p><p>В своей статье разработчик Мэтт Смит объясняет, почему перестал раскладывать объекты на переменные по умолчанию и как это изменило его подход к читаемости кода.</p><p><b>Деструктуризация — инструмент, а не норма.</b> Её стоит применять там, где она правда упрощает код, а не просто экономит символы.</p><p><b>Объект хранит контекст.</b> project.status понятнее, чем голая переменная status, особенно в больших функциях.</p><p><b>Вложенные объекты раскрывайте поэтапно.</b> Сначала доберитесь до нужного уровня, а потом уже извлекайте поля.</p><p><b>Деструктурируйте позже, а не раньше.</b> Сохраняйте исходный объект до тех пор, пока он действительно не понадобится в разобранном виде.</p><p><b>Каждая новая переменная — когнитивная цена.</b> Если имя не добавляет смысла, лучше обратиться к свойству объекта напрямую.</p><h2>Деструктуризация — не религия</h2><p>Несколько лет назад Мэтт Смит деструктурировал почти всё: объекты, пропсы, параметры, возвращаемые значения. Если встречался объект — он его раскладывал. Это перестало быть осознанным решением и превратилось в привычку: так пишет «современный JavaScript».</p><p>Сейчас он всё ещё использует деструктуризацию, но уже не автоматически. Причина простая: возвращаясь к старому коду, Смит тратит больше времени, чем ожидает, чтобы понять, откуда взялась та или иная переменная. Ему приходится мысленно собирать исходный объект обратно, прежде чем разобраться, что происходит.</p><p>В какой-то момент до него дошло: он оптимизировал процесс написания, а не чтения. Экономия нескольких нажатий клавиш сегодня оборачивалась дополнительными минутами разбора завтра.</p><h2>Не бойтесь повторов</h2><p>Один из самых распространённых паттернов — вынесть поля из объекта, а потом передать их как пропсы в компонент:</p><p>С этим ничего не случилось. Но сегодня автор с большей вероятностью напишет так:</p><p>Да, здесь повторяется post. Но когда он вернётся к этому коду через месяц, ему не придётся вспоминать, откуда взялись title или author. Контекст остаётся на виду.</p><h2>Объект несёт контекст</h2><p>Это особенно заметно в длинных функциях. Представьте, что в начале файла вы написали:</p><p>А сто строк ниже встретили такой код:</p><p>Моментальная пауза: «archived»… что именно? Сравните с вариантом, где объект сохраняет свою форму:</p><p>Объект по-прежнему несёт полезный контекст. Лишние символы редко замедляют чтение. А вот необходимость помнить, к какой сущности относится переменная, замедляет гораздо сильнее.</p><h2>Вложенность лучше раскрывать поэтапно</h2><p>Раньше автор считал вложенную деструктуризацию элегантной. Сейчас она часто кажется попыткой понять всю структуру объекта ещё до того, как начал решать задачу.</p><p>Автор предпочитает писать иначе:</p><p>Так лучше отражается мышление о данных: сначала интересует профиль, а уже потом, если нужно, из него извлекают конкретные поля.</p><h2>Деструктурируйте позже, а не раньше</h2><p>С параметрами компонентов ситуация похожа. Для маленьких компонентов деструктуризация пропсов отлично работает:</p><p>Но по мере роста компонента автор чаще пишет так:</p><p>Ему нравится держать исходный объект под рукой до тех пор, пока он действительно не понадобится в разобранном виде. Это также упрощает понимание того, что получил компонент.</p><h2>Каждая переменная — цена для читателя</h2><p><b>Каждая локальная переменная просит читателя запомнить ещё одно имя.</b> Иногда это стоит того, иногда — нет.</p><p>Если новая переменная обозначает осмысленную концепцию, автор с удовольствием её вводит. Имя вроде billingAddress становится частью словаря функции.</p><p>Но если автор просто превращает project.status в status, уверенности, что код стал читабельнее, нет. Полезное правило большого пальца: если деструктуризация даёт коду лучший словарь — использует её. Если она только экономит несколько символов — обычно нет.</p><h2>Когда деструктуризация всё ещё уместна</h2><p>Всё вышесказанное — не аргумент против деструктуризации. Автор по-прежнему применяет её постоянно.</p><p>Эти случаи локальны, сфокусированы и убирают шум. Есть и другие исключения. При маппинге массива я спокойно пишу:</p><p>Объект существует всего несколько строк, поэтому автор не чувствует, что теряет контекст. Иногда имеет смысл даже переименовать извлечённое свойство: const { status: projectStatus } = project;.</p><p>Такое имя сохраняет больше контекста, чем просто status. Но если автор всё равно несёт имя объекта в переменную, часто оказывается, что project.status читается естественнее. Не нужно придумывать новое имя, а связь с объектом остаётся очевидной.</p><p>Разница в том, что автор больше не деструктурирует просто потому, что объект существует.</p><h2>Главный вопрос</h2><p>Автор всё ещё любит деструктуризацию. Есть множество мест, где она заметно очищает код. Просто он больше не хватается за неё автоматически.</p><p>Перед тем как убрать объект, он спрашивает себя: удаление объекта действительно облегчает понимание или просто делает код короче?</p><blockquote>Если деструктуризация даёт коду лучший словарь — автор её использует. Если она только экономит несколько символов — обычно нет.</blockquote><h2>Выводы</h2><p>Деструктуризация остаётся одним из самых полезных синтаксических улучшений JavaScript. Но удобство записи не должно превращаться в удобство только для автора. Читаемость кода измеряется не количеством символов, а скоростью, с которой человек понимает, что происходит.</p><p>Сохраняйте объекты там, где они несут контекст. Раскрывайте вложенность поэтапно. Вводите переменные, только если они добавляют смысла. И не забывайте главный вопрос: вы делаете код понятнее или просто короче?</p><p><b>Источник:</b> <a href="https://allthingssmitty.com/2026/07/13/i-stopped-destructuring-everything/">Matt Smith — I stopped destructuring everything</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Кнопка «наверх» в Django: почему в проде это уже не три строки JavaScript</title>
      <link>https://tproger.ru/articles/knopka-naverh-v-django-pochemu-v-prode-eto-uzhe-ne-tri-stroki-j</link>
      <comments>https://tproger.ru/articles/knopka-naverh-v-django-pochemu-v-prode-eto-uzhe-ne-tri-stroki-j?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Фёдор Малков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/knopka-naverh-v-django-pochemu-v-prode-eto-uzhe-ne-tri-stroki-j</guid>
      <description><![CDATA[<p>Как добавить кнопку «наверх» в Django-сайт и Django Admin: настройка, CSP, доступность, мобильная версия и работа рядом с cookie-баннерами.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/knopka-naverh-v-django-pochemu-v-prode-eto-uzhe-ne-tri-stroki-j">Кнопка «наверх» в Django: почему в проде это уже не три строки JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[jQuery]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Django]]></category>
      <category><![CDATA[MIT]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Дизайн]]></category>
      <category><![CDATA[CDN]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 01 Jul 2026 13:01:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>Как сделать scroll-to-top для сайта и Django Admin, не забыв про мобильные устройства, CSP, доступность, плавающие виджеты и нормальную настройку без правки шаблонов.</p><p>На первый взгляд кнопка «наверх» — задача на пять минут. Добавил<i> position: fixed</i>, обработчик window.scrollTo()  — готово.</p><p>Но стоит этой кнопке появиться в живом проекте, как выясняется, что она пересекается с cookie-баннером, мешает чату поддержки, выглядит иначе в мобильной версии, не дружит со строгим CSP или пропадает из Django Admin.</p><p>В итоге маленькая UI-деталь начинает обрастать условиями. Я решил собрать их в отдельный Django-пакет — django-scroll-to-top</p><figure><img src="https://media.tproger.ru/user-uploads/139342/2026-06-29/000bf08b-be8e-4252-82ce-dbef3556426e.webp" alt="Пример кнопки &quot;Наверх&quot; на демо сайте" /><figcaption>Пример кнопки "Наверх" на демо сайте</figcaption></figure><h2>Когда трёх строк JavaScript достаточно</h2><p>Для небольшого сайта, где нет сложной верстки, админки, CSP и требований к повторному использованию, самый простой вариант действительно выглядит примерно так:</p><p>Это нормальное решение. Не всегда стоит тянуть пакет ради одной кнопки.</p><p>Но в реальном Django-проекте быстро появляются дополнительные вопросы:</p><ul><li>когда именно показывать кнопку: после 300 пикселей, одного экрана или только при прокрутке вверх;</li><li>что делать на коротких страницах;</li><li>как не перекрыть cookie-баннер, чат, toast-уведомления или нижнюю мобильную навигацию;</li><li>как дать пользователю закрыть кнопку;</li><li>как не сломать клавиатурную навигацию и режим reduced motion;</li><li>как сделать отдельное оформление для сайта и Django Admin;</li><li>как не заставлять проект добавлять unsafe-inline в Content Security Policy;</li><li>как позволить редактору или администратору изменить цвет, положение и иконку без нового деплоя.</li></ul><p>Именно в этот момент «три строки JavaScript» превращаются в отдельный компонент.</p><h2>Что я хотел получить</h2><p>Цель была не в том, чтобы сделать ещё одну стрелку в правом нижнем углу. Хотелось собрать переиспользуемый компонент со следующими свойствами:</p><ol><li>Подключение сайта одной template-тегом.</li><li>Отдельная поддержка обычных страниц и стандартного Django Admin.</li><li>Настройка внешнего вида через админку, а не через постоянную правку CSS.</li><li>Без jQuery, CDN, фронтенд-фреймворка и обязательной сборки.</li><li>Безопасная работа при строгой CSP.</li><li>Прогрессивное улучшение: без JavaScript остаётся обычная ссылка в начало страницы.</li><li>Возможность жить рядом с другими фиксированными элементами интерфейса.</li></ol><p>Пакет в итоге хранит обычные настройки установки в settings.py, а визуальное поведение — в базе данных. Это позволяет менять кнопку через Django Admin, публиковать новую версию настроек и при необходимости откатываться на предыдущую. В проекте есть отдельные профили для публичного сайта и Django Admin, а ревизии могут быть черновыми, опубликованными или архивными.</p><h2>Быстрое подключение</h2><p>Базовый сценарий начинается с установки:</p><p>В settings.py добавляем приложение. Если нужна поддержка стандартной админки, пакет должен идти раньше django.contrib.admin:</p><p>Включаем области, где должна работать кнопка:</p><p>Для публичной части добавляем URLConf пакета:</p><p>А в общий шаблон сайта — один тег:</p><p>На стандартном Django Admin ничего дополнительно вставлять не нужно: пакет использует обычный механизм разрешения шаблонов Django. Если же в проекте переопределён admin/base_site.html  тег можно добавить вручную в блок footer</p><h2>Настройка без превращения админки в редактор CSS</h2><p>Мне не хотелось хранить в базе шаблоны, произвольный CSS или JavaScript. Это неудобно для сопровождения и создаёт лишнюю поверхность для ошибок.</p><p>Поэтому визуальная часть собрана из контролируемых вариантов:</p><ul><li>круг, квадрат, скруглённый квадрат или pill;</li><li>заливка solid, outline, soft, ghost, glass или gradient;</li><li>положение в любом углу экрана;</li><li>отдельные размеры для desktop и mobile;</li><li>светлая и тёмная тема;</li><li>встроенные иконки, иконки от разработчика или загружаемые SVG;</li><li>настройки тени, границы, opacity и focus ring.</li></ul><figure><img src="https://media.tproger.ru/user-uploads/139342/2026-06-29/90107109-2271-460a-9dea-815592d468e7.webp" alt="Настройки кнопки" /><figcaption>Настройки кнопки</figcaption></figure><h2>Что происходит, когда рядом есть cookie-баннер или чат</h2><p>Нижний правый угол страницы редко бывает свободен. Там часто живут:</p><ul><li>cookie-баннер;</li><li>компактная кнопка после закрытия баннера;</li><li>чат поддержки;</li><li>кнопка обратного звонка;</li><li>мобильная навигация;</li><li>toast-уведомления.</li></ul><p>Пакет умеет рассматривать такие элементы как препятствия. Для этого можно пометить элемент атрибутом:</p><p>Дальше для кнопки можно выбрать поведение: игнорировать препятствия, сдвинуться вдоль края, попробовать другой угол или скрыться, если безопасного места не осталось.</p><p>Для сложных виджетов есть отдельный адаптер: он может отслеживать появление и исчезновение элементов, например компактного launcher после закрытия cookie-баннера. При этом ни cookie-пакет, ни чат не становятся зависимостями  django-scroll-to-top</p><h2>Доступность — не отдельная галочка в конце</h2><p>У кнопки есть понятное имя для screen reader, поддержка клавиатуры, видимый focus-visible, минимальный размер области нажатия и режим prefers-reduced-motion.</p><p>Если пользователь отключил анимации на уровне системы, плавная прокрутка не будет навязываться. Если JavaScript не загрузился, кнопка остаётся обычной ссылкой на начало документа.</p><p>Полный независимый аудит WCAG 2.2 AA и тестирование масштабирования 200% и 400% пока находятся в roadmap, поэтому называть компонент полностью сертифицированным по WCAG было бы неправильно. Но структурные требования — клавиатурная доступность, фокус, reduced motion, forced-colors и безопасная работа без JavaScript — уже заложены в компонент и покрываются тестами.</p><h2>CSP и загружаемые SVG</h2><p>В корпоративных проектах часто нельзя просто добавить inline-скрипт и включить unsafe-inline ради одной кнопки.</p><p>По умолчанию компонент использует same-origin CSS и JavaScript. Для него подходит политика такого вида:</p><p>Настраиваемые цвета и размеры отдаются не через inline-стили, а через версионированный stylesheet endpoint. Это позволяет сохранить простой контракт с одним template-тегом и не ослаблять CSP.</p><p>Отдельно пришлось подумать о загружаемых SVG. Админ не рендерит исходный файл как есть: SVG проходит санитарную обработку. Скрипты, обработчики событий, внешние ресурсы, встроенные документы и небезопасные namespace отклоняются. Для загружаемых иконок также хранится информация об авторе, источнике и лицензии.</p><h2>Ревизии, публикация и откат</h2><p>Одна из самых полезных вещей в пакете — не сама кнопка, а жизненный цикл её настроек.</p><p>Можно создать черновик, посмотреть результат в live preview, опубликовать изменения или вернуться к предыдущей версии. Это особенно удобно, когда кнопку настраивает не разработчик, а контент-менеджер или дизайнер.</p><figure><img src="https://media.tproger.ru/user-uploads/139342/2026-06-29/c9840966-4e32-4506-807a-ac78830fecfa.webp" alt="Живой предпросмотр" /><figcaption>Живой предпросмотр</figcaption></figure><p>У ревизий есть три состояния:</p><ul><li>draft — редактируемый черновик;</li><li>published — текущая активная конфигурация;</li><li>archived — сохранённая версия для отката.</li></ul><figure><img src="https://media.tproger.ru/user-uploads/139342/2026-06-29/2fcf0cfc-b29e-4ce1-b1de-9c07aac1fce0.webp" alt="настройки ревизий профиля кнопки" /><figcaption>настройки ревизий профиля кнопки</figcaption></figure><h2>Где пакет уместен, а где нет</h2><p>django-scroll-to-top имеет смысл, когда кнопка нужна в нескольких проектах, должна работать в Django Admin, настраиваться без деплоя или жить в окружении со строгими требованиями к CSP и интерфейсу.</p><p>Для лендинга на одну страницу проще и правильнее написать несколько строк самостоятельно. Это будет быстрее, понятнее и дешевле в сопровождении.</p><p>Но если такая маленькая деталь начинает повторяться в нескольких продуктах, появляется необходимость поддерживать мобильную версию, доступность, независимые настройки для сайтов и админки, то отдельный компонент уже перестаёт быть избыточным.</p><p>Сейчас пакет выпущен как beta-версия 0.2.0, требует Python 3.10+ и поддерживает Django 4.2 LTS, 5.x и 6.0. Лицензия — MIT.</p><p>Исходный код, документация и примеры использования доступны в GitHub-репозитории проекта.</p><p>Пакет опубликован в PyPI под именем django-scroll-to-top.</p><p>Обратная связь, баг-репорты и предложения по интеграции с кастомными Django Admin-темами приветствуются в Issues.</p>]]></content:encoded>
    </item>
    <item>
      <title>Операция Endgame отключила 106 серверов SocGholish и очистила почти 15 000 сайтов на WordPress</title>
      <link>https://tproger.ru/news/operaciya-endgame-otklyuchila-106-serverov-socgholish-i-ochistila-po</link>
      <comments>https://tproger.ru/news/operaciya-endgame-otklyuchila-106-serverov-socgholish-i-ochistila-po?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/operaciya-endgame-otklyuchila-106-serverov-socgholish-i-ochistila-po</guid>
      <description><![CDATA[<p>Международная операция Endgame вывела из строя 106 серверов SocGholish и очистила 14 971 заражённый сайт на WordPress. Разбираем угрозу и защиту.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/operaciya-endgame-otklyuchila-106-serverov-socgholish-i-ochistila-po">Операция Endgame отключила 106 серверов SocGholish и очистила почти 15 000 сайтов на WordPress</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[WordPress]]></category>
      <category><![CDATA[Хакеры]]></category>
      <category><![CDATA[Информационная безопасность]]></category>
      <category><![CDATA[Кибербезопасность]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 20 Jun 2026 06:45:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Международная операция <b>Endgame</b> вывела из строя 106 серверов малвари <b>SocGholish</b> и очистила 14 971 заражённый сайт на WordPress. За действиями стоят правоохранительные органы Нидерландов, Канады, Германии и США.</p><p>В рамках Operation Endgame отключены 106 серверов SocGholish и очищены 14 971 заражённый сайт на WordPress.</p><p>SocGholish (он же FakeUpdates) — JavaScript-даунлоудер, активен с 2017 года и доставляет шифровальщиков и шпионское ПО.</p><p>Распространяется через скомпрометированные сайты под видом обновлений Chrome, Firefox и популярного софта.</p><p>Связан с группами Evil Corp, LockBit, RansomHub, Dridex и Raspberry Robin.</p><p>Владельцам сайтов рекомендовано обновить CMS, сменить учётные данные и удалить подозрительные аккаунты.</p><p>Операция прошла в рамках инициативы <b>Operation Endgame</b>, запущенной в 2024 году для борьбы с ботнетами и криминальной инфраструктурой. Владельцам очищенных сайтов уже разосланы уведомления: обновить CMS, изменить пароли и удалить подозрительные учётные записи.</p><p><b>SocGholish</b> — один из старейших JavaScript-даунлоудеров. Он заражает компьютеры через взломанные сайты, предлагая посетителям фальшивые обновления браузеров или популярного ПО. После установки малварь открывает начальный доступ к системе, который затем используется для атак шифровальщиками и шпионским ПО.</p><p>За годы активности за SocGholish закрепились множество имён: FakeUpdates, Gold Prelude, TA569 и UNC1543. Инфраструктура малвари тесно связана с группами <b>Evil Corp</b>, <b>LockBit</b>, <b>RansomHub</b>, <b>Dridex</b> и <b>Raspberry Robin</b>.</p><h2>Как защитить свой сайт</h2><ul><li>Регулярно обновляйте WordPress, плагины и темы.</li><li>Используйте сложные уникальные пароли и двухфакторную аутентификацию.</li><li>Проверьте список администраторов и удалите подозрительные аккаунты.</li><li>Мониторьте файлы сайта на наличие внедрённого JavaScript-кода.</li></ul><p>Operation Endgame называет эту операцию началом дальнейших действий против SocGholish. Хотя часть инфраструктуры отключена, экосистема малвари включает аффилиатов и системы распределения трафика, поэтому угроза сохраняется. Подробности — в <a href="https://thehackernews.com/2026/06/operation-endgame-disrupts-socgholish.html">первоисточнике на The Hacker News</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как сделать 3D-вращение изображений при скролле: разбираем эффект от Codrops</title>
      <link>https://tproger.ru/articles/kak-sdelat-3d-vrashhenie-izobrazhenij-pri-skrolle-razbiraem-effek</link>
      <comments>https://tproger.ru/articles/kak-sdelat-3d-vrashhenie-izobrazhenij-pri-skrolle-razbiraem-effek?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-sdelat-3d-vrashhenie-izobrazhenij-pri-skrolle-razbiraem-effek</guid>
      <description><![CDATA[<p>Разбираем, как устроен эффект 3D-вращения картинок из свежей статьи Codrops. Примеры кода на GSAP, Lenis и CSS-трансформации.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-sdelat-3d-vrashhenie-izobrazhenij-pri-skrolle-razbiraem-effek">Как сделать 3D-вращение изображений при скролле: разбираем эффект от Codrops</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[Дизайн интерфейсов и UX]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 19 Jun 2026 08:09:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>Хотите, чтобы лендинг или портфолио запомнили с первого взгляда? Попробуйте добавить изображениям объёмное вращение при скролле. Эффект выглядит дорого и кинематографично, но под капотом — обычные CSS 3D-трансформации и немного JavaScript.</p><p>В начале июня команда Codrops опубликовала небольшую, но наглядную демонстрацию: галерея фотографий проезжает через вьюпорт, пока каждая картинка медленно кувыркается в трёхмерном пространстве. Идея позаимствована у аниматора Jason Booth, а код выложен на GitHub вместе с пятью готовыми вариациями.</p><p>В этой статье разберёмся, как устроен эффект, из чего состоит математика и как быстро повторить его у себя — без WebGL, Canvas и тяжёлых библиотек.</p><p>Эффект строится на CSS-свойствах perspective и transform-style: preserve-3d, а анимация крутит rotationX/Y/Z и translateZ по прогрессу скролла.</p><p>Для синхронизации с прокруткой используется GSAP ScrollTrigger с параметром scrub: true; плавность даёт библиотека Lenis.</p><p>Codrops предлагает пять вариаций: от мягкого волнообразного движения до агрессивного разворота с blur и изменением яркости.</p><p>Всего нужно три ингредиента: разметка с фоновыми изображениями, CSS для 3D-контекста и 30—40 строк JS, которые связывают скролл с трансформациями.</p><h2>Как устроен базовый эффект</h2><p>В основе лежит простая мысль: каждая картинка — это не плоский прямоугольник, а объект в 3D-пространстве. Пока пользователь скроллит страницу, объект проходит перед «камерой» и меняет ориентацию. Входная точка анимации начинается, когда элемент появляется снизу экрана, а заканчивается, когда уходит вверх.</p><p>Для реализации используется связка GSAP + ScrollTrigger. Параметр scrub: true привязывает анимацию напрямую к позиции скролла: чем дальше прокручено, тем сильнее трансформация. Библиотека Lenis отвечает за плавность: без неё колёсико мыши на Windows или трекпад на macOS могут давать рваное движение, и вся магия растворится.</p><h3>Разметка и CSS</h3><p>HTML минимален: контейнер .gallery и набор .gallery__item с фоновыми картинками. Главное — обернуть каждый item дополнительным .gallery__item-wrap и задать ему perspective. Именно обёртка создаёт 3D-контекст, внутри которого вращается сама картинка.</p><h3>Плавный скролл и триггеры</h3><p>Перед запуском анимации инициализируем Lenis и связываем её с GSAP. Это стандартный «боеприпас» для большинства современных сайтов с анимацией по скроллу.</p><h3>Математика вращения</h3><p>Каждой картинке случайно задаётся начальная ориентация по трём осям. Затем GSAP анимирует от этих значений до противоположных, создавая эффект «переворота» на 180°. В обработчике onUpdate вычисляется смещение по оси Z: чем ближе элемент к центру экрана, тем глубже он «погружается» в пространство.</p><p><b>Совет:</b> если вы убираете плавный скролл, протестируйте эффект на мобильном устройстве. Нативный скролл на iOS и Android может дать менее плавную картинку, и тогда имеет смысл оставить Lenis или добавить @media (prefers-reduced-motion) для доступности.</p><h2>Пять вариаций одной идеи</h2><p>Codrops не ограничивается одной анимацией: в репозитории пять HTML-файлов, каждый из которых демонстрирует, как одно и то же ядро превращается в разное настроение. Вот краткая карта отличий.</p><ul><li><b>Вариант 1.</b> Мягкое волнообразное расположение картинок по горизонтали, случайные углы rotationX 70—120° и небольшой зазор по Z (−50 px). Универсальная, спокойная подача.</li><li><b>Вариант 2.</b> Больший размах по rotationX (240—290°) и усиленная глубина до −300 px. Картинки буквально переворачиваются перед глазами.</li><li><b>Вариант 3.</b> Триггер вычисляет поворот через cos(progress * π), добавляет сдвиг по Y (yPercent) и фильтры saturate/brightness. Получается «подводное» движение.</li><li><b>Вариант 4.</b> Акцент на скорости: в обработчике Lenis отслеживается velocity, и чем быстрее скролл, тем сильнее blur и ниже насыщенность. Динамично и спортивно.</li><li><b>Вариант 5.</b> Агрессивное искажение масштаба: scaleX и scaleY меняются в противофазе, картинки растягиваются и сжимаются, проходя через центр экрана.</li></ul><h2>Мини-руководство: повторяем у себя</h2><p>Чтобы не копировать всю демку целиком, можно взять только схему и адаптировать под свой проект. Ниже — самый короткий путь от макета до рабочей анимации.</p><h2>FAQ</h2><h2>Выводы</h2><p>3D-анимации по скроллу — это способ сделать обычную галерею запоминающейся без тяжёлых WebGL-сцен. Хватает CSS-свойств perspective и transform-style, пары строк GSAP и библиотеки Lenis для плавности.</p><p>Главное, что предлагает Codrops, — не готовый плагин, а отправная точка. Пять вариаций показывают, как одну и ту же идею можно растянуть от спокойного волнообразного движения до агрессивного искажения с blur. Возьмите базовую схему, подберите углы и фильтры под свои картинки — и получите эффект, который будет выглядеть так, будто над ним работала целая команда аниматоров.</p><blockquote>Пользователь не запоминает интерфейс, который просто красив. Он запоминает тот, который отзывается на его действия.</blockquote><p>Источники: оригинальная статья <a href="https://tympanus.net/codrops/2026/06/18/exploring-3d-image-rotations-on-scroll/">Codrops</a>, демо <a href="https://tympanus.net/Development/RotatingOnScrollAnimations/">Rotating On-Scroll Animations</a> и исходный код на <a href="https://github.com/codrops/RotatingOnScrollAnimations">GitHub</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>144 пакета Mastra в npm скомпрометированы через взломанный аккаунт контрибьютора</title>
      <link>https://tproger.ru/news/144-paketa-mastra-v-npm-skomprometirovany-cherez-vzlomannyj-akkau</link>
      <comments>https://tproger.ru/news/144-paketa-mastra-v-npm-skomprometirovany-cherez-vzlomannyj-akkau?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/144-paketa-mastra-v-npm-skomprometirovany-cherez-vzlomannyj-akkau</guid>
      <description><![CDATA[<p>Хакеры взломали аккаунт экс-контрибьютора Mastra и опубликовали 144 вредоносных пакета в npm. Разбираем, как работает атака и что делать разработчикам.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/144-paketa-mastra-v-npm-skomprometirovany-cherez-vzlomannyj-akkau">144 пакета Mastra в npm скомпрометированы через взломанный аккаунт контрибьютора</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Информационная безопасность]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 17 Jun 2026 12:15:30 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если в вашем проекте есть пакеты @mastra/* — проверьте lock-файлы и логи CI. Утром 17 июня 2026 года злоумышленники опубликовали 144 вредоносные версии в npm-пространстве имён Mastra, добавив в зависимости троянскую библиотеку.</p><ul><li>Скомпрометированы 144 пакета в пространстве имён @mastra/*, включая @mastra/core.</li><li>Атака шла через взломанный аккаунт бывшего контрибьютора ehindero.</li><li>Вредоносная библиотека easy-day-js запускалась через postinstall и устанавливала кроссплатформенный стилер.</li><li>npm уже откатил latest-теги и удалил вредоносные версии у ключевых пакетов.</li></ul><h2>Как прошла атака на пакеты Mastra в npm</h2><p>Mastra — это open-source фреймворк на JavaScript и TypeScript для создания ИИ-приложений и агентов. Только @mastra/core скачивают более 918 тыс. раз в неделю, поэтому инцидент затронул огромное число разработчиков и CI-сред.</p><p>По данным исследователей из JFrog, SafeDep, Socket и StepSecurity, атака длилась 88 минут. Злоумышленники массово публиковали версии с добавленной зависимостью easy-day-js — клоном популярной библиотеки dayjs. Пакет появился у пользователя sergey2016 16 июня в 7:05 UTC как чистая копия, а вредоносный код был внесён 17 июня в 1:01 UTC.</p><p>При установке easy-day-js срабатывал обфусцированный postinstall-хук (скрипт, который npm запускает автоматически после установки пакета): он отключал проверку TLS, загружал вторую стадию с сервера 23.254.164[.]92, запускал её фоновым процессом и самоудалялся. Финальный троян собирал историю браузера, данные свыше 160 браузерных расширений криптовалютных кошельков и передавал их на C2-сервер 23.254.164[.]123 — сервер управления злоумышленников.</p><h2>Что делать разработчикам</h2><ul><li>Проверить lock-файл: grep easy-day-js package-lock.json или npm ls easy-day-js.</li><li>Откатиться к версии, опубликованной до 17 июня 2026 года (~1:01 UTC), и зафиксировать её в зависимостях.</li><li>Сменить токены npm, GitHub, SSH-ключи и переменные окружения на хостах, где стоял подозрительный пакет.</li><li>Проверить логи сети и DNS на обращения к IoC (индикаторам компрометации): IP 23.254.164[.]92 и 23.254.164[.]123.</li><li>Включить проверку подписей пакетов: npm audit signatures, и в CI требовать SLSA-provenance-attestation.</li></ul><p>npm уже удалил вредоносные версии у ключевых пакетов и вернул latest-теги. Но разработчикам стоит проверить свои системы: полезная нагрузка выполняется ещё на этапе установки, до первого импорта пакета. Источник: <a href="https://thehackernews.com/2026/06/144-mastra-npm-packages-compromised-via.html" rel="noopener noreferrer">The Hacker News</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Чистое API на Node.js: практическое руководство</title>
      <link>https://tproger.ru/articles/chistoe-api-na-node-js-prakticheskij-gajd</link>
      <comments>https://tproger.ru/articles/chistoe-api-na-node-js-prakticheskij-gajd?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/chistoe-api-na-node-js-prakticheskij-gajd</guid>
      <description><![CDATA[<p>Как построить поддерживаемое REST API на Node.js: слои, Zod, единые ошибки, версионирование и Swagger. Проверьте свою архитектуру.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/chistoe-api-na-node-js-prakticheskij-gajd">Чистое API на Node.js: практическое руководство</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Фреймворки и библиотеки]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 16 Jun 2026 11:00:21 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если ваше Node.js-приложение начиналось с одного server.js, а через полгода превратилось в лабиринт маршрутов, где бизнес-логика растворилась в обработчиках Express — эта статья для вас. Чистое API проектируется не ради красоты, а чтобы команда могла добавлять конечные точки, не боясь сломать соседние.</p><p>Разберём минимальный, но готовый к продакшену скелет: разделение слоёв, валидацию входных данных, централизованную обработку ошибок, версионирование, ограничение частоты запросов и автоматическую документацию. Все принципы применимы и к Express, и к Fastify, и к NestJS.</p><p>Чистое API — это прежде всего разделение ответственности: роутер знает маршруты, контроллер переводит HTTP в вызовы сервиса, сервис отвечает за бизнес-логику, а схема проверяет входные данные. Такой подход делает код предсказуемым при любом масштабе.</p><p>Чистое API — это, прежде всего, разделение ответственности: роутеры, контроллеры, сервисы и валидация живут в разных слоях.</p><p>Валидация с помощью Zod выполняется на границе — до того, как запрос попадёт в бизнес-логику.</p><p>Централизованный обработчик ошибок — единственное место, где исключения превращаются в HTTP-ответы.</p><p>Стоит закладывать версионирование и документацию OpenAPI с первого дня: позже это обойдётся дороже.</p><p>Fastify и NestJS дают те же архитектурные идеи из коробки, но логика слоёв от этого не меняется.</p><h2>Что мы будем строить</h2><p>Возьмём намеренно простой домен — каталог товаров. Нам важна не бизнес-логика, а структура. К концу у нас будет REST API с единым форматом ответов, валидацией, версионированием по пути /api/v1, ограничением частоты запросов и интерактивной документацией Swagger.</p><h2>Структура проекта</h2><p>Прежде чем писать код, договоримся, где что лежит. Каждая фича — отдельная папка, а не рассыпанный по проекту набор файлов.</p><ul><li>api/v1/ — все маршруты версионированы с первого дня. Добавить v2 позже можно новой папкой, а не рефакторингом.</li><li>products/ — фичевая папка владеет роутером, контроллером, сервисом и схемой.</li><li>middleware/ — сквозная функциональность: защита, логирование, лимиты.</li><li>lib/ — утилиты без привязки к фреймворку.</li></ul><h2>Разделяем ответственность</h2><p>Самая частая ошибка в Express-приложениях — бизнес-логика внутри обработчика маршрута. Там же появляется валидация, работа с базой данных и формирование ответа. При росте проекта такой файл становится опасным для изменений.</p><h3>Роутер знает только «куда идти»</h3><h3>Промежуточный обработчик валидации</h3><p>Промежуточный обработчик validateRequest проверяет body, params и query до попадания в контроллер. Если данные не проходят проверку, ошибка передаётся в централизованный обработчик.</p><h3>Контроллер переводит HTTP в вызовы сервиса</h3><p>Контроллер не знает, где хранятся товары. Его задача — извлечь параметры из запроса, вызвать сервис и вернуть ответ. Всё остальное передаётся в централизованный обработчик ошибок через next(error).</p><h3>Сервис содержит бизнес-логику</h3><p>Сервис не зависит от HTTP. Когда придёт время заменить хранилище в памяти на настоящую базу данных, потребуется поправить только этот файл.</p><h2>Валидация на границе с Zod</h2><p>Любое API, принимающее внешние данные, должно их проверять. Без валидации один некорректный запрос способен превратиться в ошибку времени выполнения, некорректную запись в базе или уязвимость.</p><p>Zod даёт две вещи сразу: проверку во время выполнения и типы TypeScript, выведенные из одной схемы. Промежуточный обработчик validateRequest проверяет body, params и query до того, как запрос попадёт в контроллер. Если данные невалидны, дальше они не идут.</p><h2>Единый обработчик ошибок</h2><p>Разбросанная обработка ошибок — один из главных источников хаоса: где-то возвращается { error: '...' }, где-то { message: '...' }, а где-то случайно отдаётся HTML-страница. Решение — один обработчик, через который проходят все исключения.</p><p>Теперь клиент всегда получает предсказуемую форму ответа, а добавление логирования или отправки ошибок в мониторинг — однострочное изменение в одном месте.</p><h2>Единый формат ответов</h2><p>Успешный ответ всегда выглядит как { success: true, data: ... }, а ошибка — как { success: false, error: { code, message, details } }. Фронтенд или сторонний интегратор знает, чего ожидать от любого эндпоинта.</p><h2>Версионирование API</h2><p>Версионировать API с первого дня стоит недорого. Добавить версию позже — значит ломать существующих клиентов или городить сложную миграцию.</p><p>Новая версия — новая папка src/api/v2/ и новый префикс. Старые клиенты продолжают работать на /api/v1.</p><h2>Ограничение частоты запросов</h2><p>Rate limiting защищает API от случайных и намеренных перегрузок. Настроить его в Express помогает пакет express-rate-limit.</p><p>Глобальный лимит распространяется на все запросы; операции, которые изменяют данные, ограничены жёстче. Ответ тоже соответствует единому формату ошибки.</p><h2>Документация OpenAPI и Swagger</h2><p>API без документации годится только для автора. С помощью swagger-jsdoc и swagger-ui-express можно получить интерактивную документацию прямо из JSDoc-комментариев в роутерах.</p><p><b>Совет:</b><br />Держите описания эндпоинтов в одном файле с маршрутами, а glob в swagger.ts настройте на файлы, доступные во время работы приложения. Лучше генерировать спецификацию на этапе сборки, чем полагаться на исходники TypeScript.</p><h2>Fastify и NestJS: альтернативы Express</h2><p>Всё, что мы разобрали, работает и в Express. Но если вы начинаете проект с нуля, стоит взглянуть на альтернативы.</p><ul><li>Fastify — быстрее Express в бенчмарках (порой в два раза), имеет встроенный логгер Pino и валидацию по схеме. Разделение слоёв остаётся на совести разработчика.</li><li>NestJS — популярен в крупных компаниях: слои модулей, контроллеров и сервисов навязаны архитектурой, что упрощает введение новых разработчиков в проект.</li><li>Express — остаётся лучшим выбором, если вы присоединяетесь к существующему проекту или команда уже знает экосистему.</li></ul><p>Архитектурные принципы — разделение слоёв, единый формат ошибок, валидация на границе — не зависят от фреймворка. Меняется только синтаксис.</p><h2>FAQ</h2><h3>Минимальный набор для старта</h3><p>Для самостоятельного запуска понадобятся базовые зависимости и алиасы путей в tsconfig.json. Объявите @/* на папку src, и примеры заработают без ручных правок импортов.</p><h2>Выводы</h2><blockquote>Чистая структура API — это не переусложнение. Это минимум, при котором бэкенд можно поддерживать нескольким людям.</blockquote><p>Мы собрали минимальный, но масштабируемый каркас: папки по фичам, разделённые слои, валидацию на границе, централизованную обработку ошибок, единый формат ответов, версионирование, лимиты и автоматическую документацию. Ни один из этих шагов сложный сам по себе. Их ценность — в сочетании и в том, чтобы сделать всё это до того, как код разрастётся.</p><p>Если начинаете новый Node.js-проект, не откладывайте структуру «на потом». А в существующем — попробуйте вынести валидацию в схему и собрать ошибки в одном обработчике. Потом всегда дороже.</p><h2>Источники</h2><p>Идеи и примеры в статье основаны на материале Gavin Cettolo «<a href="https://dev.to/gavincettolo/clean-api-design-in-nodejs-a-practical-guide-3a32">Clean API Design in Node.js: A Practical Guide</a>» (dev.to).</p>]]></content:encoded>
    </item>
    <item>
      <title>Фреймворк-независимые дизайн-системы: практический подход к веб-компонентам</title>
      <link>https://tproger.ru/translations/frejmvork-nezavisimye-dizajn-sistemy-prakticheskij-podhod-k-veb-2</link>
      <comments>https://tproger.ru/translations/frejmvork-nezavisimye-dizajn-sistemy-prakticheskij-podhod-k-veb-2?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Картофельный Повелитель]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/frejmvork-nezavisimye-dizajn-sistemy-prakticheskij-podhod-k-veb-2</guid>
      <description><![CDATA[<p>Как построить дизайн-систему без привязки к фреймворку, используя веб-стандарты и веб-компоненты. Пошаговое руководство с примерами кода и документацией. Разбираем на практике.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/frejmvork-nezavisimye-dizajn-sistemy-prakticheskij-podhod-k-veb-2">Фреймворк-независимые дизайн-системы: практический подход к веб-компонентам</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[Дизайн интерфейсов и UX]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Дизайн]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 14 Jun 2026 07:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<p>Перевод статьи Scott Riley (Piccalilli), оригинал: https://piccalil.li/blog/framework-agnostic-design-systems-part-1/<br /><br />Прежде чем мы начнём, небольшое примечание: это практическое руководство, которое охватывает управление, создание и упаковку компонентов дизайн-системы. Невозможно углубляться в каждый шаг до мельчайших деталей, не превратив материал в полноценный курс. Предполагается наличие некоторых базовых знаний:</p><ul><li>Базовые знания HTML и CSS</li><li>Базовое понимание веб-компонентов</li><li>Установленные Node.js и npm</li><li>Умение работать в терминале на уровне, достаточном для установки пакетов</li><li>Базовые знания конфигурационных файлов и JSON</li><li>Понимание революционной идеи о том, что &lt;button&gt; — это не &lt;div&gt;</li></ul><p>Наконец, это довольно длинный пост. Считайте каждый h2 приглашением сделать перерыв на чай и подышать свежим воздухом.</p><p><a href="https://www.youtube.com/watch?v=AWM5ZNdWlqw">Okayyyyylet’sgo</a>.</p><h2>Фреймворк-независимые компоненты</h2><p>Из всех недавних хайпов/пузырей/назовите их как хотите в мире технологий тот, что одновременно волновал и ставил в тупик меня в равной степени, — это бум дизайн-систем. Общая концепция определённо фантастическая, и почти любая команда или проект могут извлечь пользу из какой-либо формы централизованного хранилища дизайнерских решений. Но, как и любой другой бум, он породил много <i>странностей</i>. Люди сошлись на определённых способах восприятия дизайна в «эпоху дизайн-систем», слайды <a href="https://atomicdesign.bradfrost.com/chapter-2/">Atomic Design</a> в каждой конференц-презентации стали мемом, а дизайн-токены стали целой личностью для некоторых.</p><p>Этот пост — не обо всех странностях, но нам нужно опереться на что-то более конкретное, чем <i>крутая технология — это круто</i>. И одна из моих наименее любимых странностей из курса «Дизайн-системы: странности 101» довольно специфична, но при этом является источником настоящей физической боли для меня: <i>библиотеки компонентов, привязанные к конкретному фреймворку</i>.</p><p>Идея о том, что наши дизайн-системы могут, и даже должны, работать на компонентах, написанных под конкретный фреймворк, кажется мне дикой. Дизайн-системы, по крайней мере частично, должны быть про универсальность, компонуемость и переносимость. Встраивание привязки к фреймворку в уравнение с самого первого дня абсолютно нелепо.</p><p>Я понимаю, что веб-стандарты немного отставали на заре дизайн-систем, а веб-компоненты и кастомные элементы отставали от всех тех приятных возможностей, которые предлагают реактивные фреймворки с управлением состоянием. К счастью для нас, это больше не так, и существует ряд замечательных инструментов, построенных вокруг создания и потребления стандартных веб-компонентов.</p><p>На самом деле, я бы даже сказал, что на момент написания этого поста веб-компоненты — <i>единственно лучший подход</i> к созданию библиотеки компонентов. Они переносимы, используют веб-стандарты, и любой фреймворк, который не является полным бардаком (и многие, которые являются, смотрю на тебя, React), будет поддерживать их либо напрямую, либо с минимальной конфигурацией.</p><p>Отвлечения в сторону, этот пост будет максимально практичным введением в создание веб-компонентов с использованием веб-стандартов, современного CSS и некоторых удобных инструментов, которые помогут нам ускориться. Он также будет весьма субъективным и сильно опираться на идею, что мы должны поставлять нашу библиотеку вместе с документацией в одном репозитории. Вы не <i>обязаны</i> заниматься всеми этими штуками с документацией, если не хотите, но я настоятельно рекомендую попробовать. Весь код здесь для вас, так почему бы и нет!</p><h2>Принципы</h2><p>Сначала рассмотрим несколько принципов. Если они вам близки — читайте дальше; если нет — можете закрыть вкладку, заварить чай и заняться своими делами.</p><h3>Минимально возможный уровень</h3><p>Хотя существует масса инструментов, которые превращают компоненты для конкретного фреймворка (давайте будем честны — почти всегда это React) в веб-компоненты, я не думаю, что такой подход соответствует тому, что мы <i>говорим</i>, что хотим от наших библиотек компонентов и паттернов по духу.</p><p>Когда мы создаём компоненты и проектируем API компонентов, мы разрабатываем некоторые из самых атомарных элементов дизайн-системы. В таком сценарии, на мой взгляд, есть явная, ощутимая польза от работы максимально близко к платформе доставки. Для веб-продуктов это означает работу непосредственно с веб-стандартами.</p><p>На уровне компонентов я гораздо более настроен на удаление слоёв абстракции и работу ближе к веб-стандартам. Вместо того чтобы сразу прыгать в модный фреймворк, я гораздо больше предпочитаю работать с инструментами сборки и лёгкими обёртками. Это означает, что вы всегда находитесь в «режиме веб-стандартов», идёте прямым путём. Горжусь вами.</p><h3>Максимально простые компоненты</h3><p>Компоненты должны быть максимально примитивными. Даже самый, казалось бы, сложный компонент можно представить как очень простую конечную машину состояний. Мне ещё не встречался компонент, который нельзя было бы выразить таким образом, и вам не нужно по умолчанию обращаться к раздутому фреймворку для простых вариантов компонентов и изолированного состояния.</p><p>Я бы даже сказал, что многие реактивные компоненты — это антипаттерн. Реактивность обычно означает логику, и очень часто это приводит нас в область «бизнес-логики» и общего состояния на уровне контейнера или приложения. Простая реактивность на уровне компонента часто необходима — представьте кнопку, которая показывает спиннер загрузки, пока что-то обрабатывается, и возвращается в исходное состояние, когда всё готово, — но добавление тонн состояний и реактивности в изолированном коде компонента всегда <i>кажется</i> мне красным флагом.</p><p>По моему опыту, компоненты наиболее полезны, когда им явно сообщают, какое состояние они должны отражать и какой контент содержать. Они намеренно ограничены и явно декларативны. Обработка сложного состояния и реактивности в вашем приложении, даже если это означает комбинирование нескольких примитивов в паттерн, специфичный для приложения, гораздо более разумна, чем попытка централизовать сложный громадный компонент, который пытается слишком многое обрабатывать.</p><h3>Максимально устойчиво к будущему</h3><p>Устоявшиеся фреймворки со временем становятся устаревшими технологиями. Учитывая всю работу по «переписыванию Angular-проектов на React», на которой некоторые из нас спокойно прожили целых два года, мы должны это понимать. Сам React становится (можно спорить, <a href="https://adactio.com/journal/20618">уже стал</a>) устаревшей технологией, а «переписывание нашего React-приложения на Solid/Svelte» — теперь обычное дело. Я вполне ожидаю, что это будет повторяться до тошноты.</p><p>Веб-стандарты — хотя, признаюсь, они развиваются медленнее и поддерживаются утомительными, своеобразными процессами выпуска — всегда будут с нами. Веб-стандарты выдержали проверку времени и последовательно доказывают, что они заметно более надёжны, чем ваш проблемный любимый, переусложнённый фреймворк.</p><p>Фреймворки тоже по-настоящему замечательны, когда используются правильно. Говоря по опыту, попытка написать состоятельные, реактивные приложения на ванильном HTML, CSS и JS — закаляющая, но в конечном счёте неразумная задача. Однако примитивные компоненты — это не сложные, состоятельные, реактивные веб-приложения. Это маленькие куски атомарного веб-кода, и создавать их со всеми накладными расходами и своеобразиями полноценного фреймворка — это просто приглашение к будущему устареванию.</p><p>Создавая <i>непосредственно с помощью веб-стандартов</i>, мы получаем более низкоуровневое понимание того, как работают наши компоненты, встроенную защиту от будущего, избегая модного фреймворка, и по сути более прогрессивную, доступную (или, по крайней мере, более легко делаемую доступной) и нативную для веба библиотеку компонентов.</p><p>Разделяя ваши атомарные компоненты дизайн-системы от ваших <i>компонентов приложения</i>, вы получаете лучшее из обоих миров: переносимые, примитивные компоненты на уровне системы; сложные и реактивные компоненты и обёртки на уровне приложения.</p><p>Таким образом, когда вам действительно понадобится переписать приложение на Solid/Svelte, вам хотя бы не придётся переписывать всю библиотеку компонентов вместе с ним.</p><h3>Принимать решения в коде</h3><p>Я абсолютно готов стоять насмерт на этой позиции. Инструменты для дизайна — <i>ужасные</i> места для принятия решений по <i>дизайн-системе</i>. Это отчасти потому, насколько оторваны такие инструменты, как Figma, от того, как на самом деле работают дизайн-системы, вплоть до откровенно катастрофического несоответствия словаря и концепций.</p><p>Как человек, который по сути больше дизайнер, чем разработчик, я создал и работал с более чем дюжиной «дизайн-систем» в Figma. Как человек, который также проводит гораздо больше времени в коде, чем в инструментах дизайна, я считаю себя вправе сказать, что ни одна из них не отражала того, чем должна быть хорошая системная основа. Это не укол в сторону дизайнеров, которые не пишут код, скорее просто показывает, насколько сложной <i>сами инструменты</i> делают эту часть нашей работы.</p><p>Инструменты для дизайна — это места для быстрого тестирования разных идей и экспериментов со стилем и компоновкой. Они абсолютно ужасны для кодификации системных решений, отчасти из-за своей самой природы: они предоставляют очень маленькое, проприетарное подмножество возможностей нашего реального носителя — браузера.</p><blockquote>Так же и браузер, но я рискую укрепиться на том самом холме.</blockquote><p>Окончательные системные дизайнерские решения должны приниматься в браузере. Токены цвета могут использовать современные цветовые пространства. Токены размеров и отступов должны выражаться в относительных единицах, где это возможно. Почти каждый тип токена может выиграть от какой-либо математики, включая логарифмические шкалы для типографики или программные сдвиги оттенка и светлоты для цветов. API компонентов также следует строить с помощью надёжных, хорошо типизированных определений. Инструменты дизайна могут предложить лишь подобие этих концепций.</p><p>Если вы начинаете с Figma — это вполне нормально, но это ужасный источник истины. Воспринимайте свой инструмент дизайна как точный инструмент прототипирования, а не как конечную цель для дизайнерских решений.</p><h3>Документировать по ходу разработки</h3><p>Опираясь на последний принцип, если лучшее место для принятия решений — код, то лучшее время для документирования этих решений — фаза сборки, пока они свежи в вашей голове. Разрабатываете props? Ну посмотрите-ка, у вас уже есть прекрасный набор определений типов для этих props, неплохо было бы добавить туда маленький комментарий <a href="https://jsdoc.app/">JSDoc</a> и заняться своими делами.</p><p>Мне нравится идти дальше и разворачивать библиотеку компонентов прямо в документирующем фреймворке вроде <a href="https://vitepress.dev/">VitePress</a>, активно создавая человекочитаемую документацию параллельно с разработкой самих компонентов. Это не только в итоге станет «официальной» документацией дизайн-системы, но и позволит проверить, насколько переносимы ваши компоненты.</p><p>Это делает мой мозг счастливым, потому что полное кодовое представление моих дизайн-систем (что абсолютно, всегда включает фактическую документацию) живёт в одном репозитории. Всю систему можно развернуть, не жонглируя зависимостями, и это заставляет меня относиться к документации как к необходимому шагу к релизу.</p><h2>Давайте создадим (и задокументируем)</h2><p>Ладно, хватит болтать, давайте на самом деле создадим что-то практичное. Мы соберём основы для централизованной, независимой от фреймворка библиотеки компонентов. Мы будем прорабатывать документацию по мере создания компонентов, что даст нам действительно чистый тестовый стенд для самих компонентов. Настоящий порочный круг, если таковой вообще был.</p><p>Вот что у нас будет в конце этой статьи:</p><ul><li>Основа для нашей гибридной библиотеки компонентов/документации «всё-в-одном» дизайн-системы</li><li>Стильная, хорошо задокументированная кнопка как веб-компонент</li><li>Развёртываемый сайт документации, который показывает, как замечательна наша кнопка</li><li>Готовая к продакшену библиотека компонентов, которую можно опубликовать в вашем любимом пакетном менеджере</li></ul><p>Нам действительно нужно беспокоиться только о двух инструментах: <a href="https://elenajs.com/">Elena</a> для сборки и распространения нашей библиотеки компонентов и <a href="https://vitepress.dev/">VitePress</a> для создания нашей документации.</p><h3>Elena</h3><p>Клей для всего этого проекта — Elena. Фантастическая библиотека от непобедимого <a href="https://arielsalminen.com/">Ariel Salminen</a> для создания прогрессивных веб-компонентов. Я не буду углубляться в философию того, что означает «прогрессивный» в этом контексте, потому что это уже <a href="https://arielsalminen.com/2026/progressive-web-components/">исключительно хорошо задокументировано</a> самим Ariel.</p><p>Elena — это крошечная библиотека, которая делает Just Enough Abstraction™ поверх стандартных веб-компонентов. Мы получаем такие вещи, как props (отражаемые как пользовательские атрибуты), изолированную реактивность, методы жизненного цикла и даже классные штуки вроде миксинов для компонуемости. Мы не будем углубляться <i>слишком</i> сильно в Elena, но следите за ходом мысли и, если вам понравятся основы, я очень рекомендую погрузиться во всё, что она предлагает. Это круто.</p><p>Цитата из <a href="https://elenajs.com/#why-should-i-use-elena">документации Elena</a>:</p><blockquote>[Elena] берёт на себя межфреймворковую сложность (синхронизация prop/атрибута, делегирование событий, совместимость с фреймворками), чтобы вы могли сосредоточиться на создании компонентов, а не на инфраструктуре.</blockquote><p>Именно этого я и хочу от такого инструмента: позвольте мне писать код, не абстрагируйте веб-стандарты, разберитесь со всей странной ерундой, которую я не хочу трогать.</p><h3>VitePress</h3><p>Я не буду тратить много времени на VitePress, потому что, честно говоря, это просто самое удобное готовое решение для документации, которое не называется Storybook. Пара npm install — и у нас есть надёжное, основанное на Markdown решение для документации, готовое к работе.</p><p>Позже мы сделаем несколько классных штук с VitePress, JSDoc и нашим сгенерированным Elena манифестом пользовательских элементов, что поможет ускорить процесс документирования, но, честно говоря, иметь <i>где-то</i> документировать гораздо важнее, чем то, <i>во что</i> мы документируем.</p><h3>Структура проекта</h3><p>Здесь всё может изначально показаться немного странным. Хотя нам нужно собирать и распространять наши компоненты как отдельную библиотеку, нам также нужно их видеть и тестировать. Самый простой способ — это слепить статический сайт и просто свалить все компоненты на одну страницу. Это <i>вполне нормально,</i> и, по сути, я бы поощрил это, если вы просто экспериментируете с Elena, но по причинам, изложенным выше, я считаю, что имеет большой смысл создавать <i>внутри</i> нашей документации.</p><p>У нас по сути будет что-то вроде монорепозитория: Elena будет делать своё дело на уровне компонентов, а сам сайт документации будет статически генерироваться с помощью VitePress.</p><h2>Создание каркаса проекта</h2><p>Здесь довольно много движущихся частей, поэтому вместо того, чтобы просто кидать вам дикую цепочку склеенных npm install, давайте разберём настройку шаг за шагом.</p><p>Для начала создадим папку проекта:</p><p>Замените my-ds на любое название, которое хотите дать своему проекту.</p><p>Затем инициализируем npm:</p><p>Команда npm init -y создаст файл package.json в корне проекта. Эта корневая папка напрямую ничего не будет делать — она просто склеивает наши компоненты и документацию вместе в монорепозитории.</p><p>Приведём в порядок наш package.json:</p><p>Здесь происходит кое-что, что пока не имеет особого смысла (и даже не будет работать) — это станет понятно чуть позже. Настройка workspaces позволит нам обращаться со сборкой библиотеки компонентов как с пакетом, не публикуя его, а скрипты dev, а также различные watch и build позволят нам отслеживать и собирать либо документацию, либо компоненты (либо оба варианта одновременно с помощью команды dev).</p><p>Кстати, давайте установим пару штук:</p><p>Это установит concurrently, который позволит нам запускать команду watch Elena и команду dev VitePress одновременно. Мы будем держать её запущенной, пока работаем. Также установится VitePress и его тема по умолчанию. Если хотите заморочиться — можете использовать другую тему.</p><h3>Настройка VitePress</h3><p>Настроим документацию. Для начала создадим папку docs:</p><p>Затем создайте docs/.vitepress/config.mjs:</p><p>Проверьте расширение!Обратите внимание: мы используем .mjs, а не .js — это заставляет Node трактовать файл как ESM. Это необходимо, потому что мы импортируем из vitepress. Вам не обязательно знать, что это значит. Честно говоря, я не уверен, что сам это понимаю. Просто убедитесь, что используете .mjs. Ладно, спасибо.</p><p>Здесь мы используем postIsolateStyles, чтобы ограничить область действия встроенных стилей .vp-doc VitePress и не дать им просочиться в наши примеры компонентов. Без этого встроенные стили VitePress <i>могут</i> переопределять стили ваших компонентов (включая инкапсулированные сбросы) из-за того, как Vite внедряет таблицы стилей во время выполнения.</p><p>Далее создайте минимальный docs/index.md, чтобы у VitePress была домашняя страница:</p><p>Сейчас хороший момент, чтобы проверить, всё ли работает:</p><p>Вы должны увидеть очень простой локальный сайт на VitePress! По умолчанию он будет доступен по адресу <a href="http://localhost:5173/">http://localhost:5173/</a>.</p><h3>Настройка Elena</h3><p>Теперь, для MVP нашей дизайн-системы, давайте установим Elena. Мы будем использовать Elena для создания компонентов и в конечном итоге распространять их в виде пакета. Именно здесь некоторые вещи из корневого package.json начинают обретать смысл.</p><p>Начнём с создания папки компонентов и инициализации npm для нашего пакета компонентов:</p><p>Далее отредактируйте packages/components/package.json:</p><p>Затем установите Elena:</p><p>Это установит Elena, её бандлер и CLI-инструмент.</p><p>Мы будем использовать CLI-инструмент Elena для создания каркаса наших компонентов. По сути, он проведёт нас через создание компонента, позволяя запустить команду, которая генерирует нужную папку и создаёт js- и css-файлы для любого компонента, который мы захотим создать. Подробнее об этом позже!</p><p>Пока что нам нужно настроить Elena. Во многих случаях можно пропустить этот шаг и просто использовать настройки по умолчанию. Однако мы ведём себя как глупые гуси и совмещаем документацию и компоненты в одном проекте, так что, возможно, придётся кое-что подкрутить. Плюс я люблю, когда конфиги явные, а не невидимые.</p><p>Создайте конфиг Elena по пути packages/components/elena.config.mjs:</p><p>Это говорит Elena, где искать наши компоненты (src), куда выводить собранные компоненты (dist) и где искать точку входа нашей библиотеки (src/index.js). Обратите внимание: все эти пути относительны директории packages/components, <b>а не</b> корня проекта. Наши инструменты Elena и библиотека компонентов самодостаточны.</p><p>Эта точка входа важна, если мы хотим импортировать все наши веб-компоненты через bundle.js, который генерирует Elena. Пока что создадим пустую.</p><p>Создайте packages/components/src/index.js. Пока что он может быть просто пустым или содержать комментарий-заглушку:</p><p>По мере создания компонентов мы сможем добавлять соответствующие export в этот файл, чтобы они попадали в наш production-бандл.</p><p>Убедитесь, что Elena работает: перейдите в корень проекта и выполните:</p><p>К сведениюЕсли вы запускаете это на Mac с Apple Silicon, то, скорее всего, столкнётесь с ошибкой. По моему опыту, это из-за зависимости Elena (lightningcss), которая немного кривая (простите за такую техническую терминологию). Если при попытке сборки вы получаете ошибку 'MODULE_NOT_FOUND', выполните следующее из корня проекта: Code languagebashCopy to clipboard rm -rf node_modules packages/components/node_modules package-lock.json &amp;&amp; npm install Это уничтожит папку node_modules и переустановит зависимости, разложив всё по своим местам. Если эта ошибка случилась однажды, то, скорее всего, придётся запускать это каждый раз при установке новой зависимости. Мне жаль. Управление пакетами — как всегда, Очень Приятное Занятие.</p><h2>И выдохнем…</h2><p>Отойдём на шаг назад, поставим чайник и посмотрим, что у нас есть. Ваша структура проекта должна выглядеть так:</p><p>Наша корневая папка по сути просто контейнер, так что особо беспокоиться о ней не стоит.</p><p>Наша папка docs — это место, где будет жить всё, связанное с VitePress. В конечном итоге это станет полноценной документацией дизайн-системы, и мы будем использовать её для предпросмотра и документирования наших компонентов по мере их создания.</p><p>Наша папка packages/components — это место, где мы будем работать со всем, связанным с компонентами. Папка packages/components/src — это место, где мы будем создавать компоненты, а index.js в ней — точка входа нашей библиотеки, где мы просто будем export'ировать любые компоненты, которые хотим включить в бандл.</p><p>Наша папка dist в packages/components — это место, где будут храниться собранная библиотека компонентов и манифест кастомных элементов. Затем мы сможем импортировать отсюда в наш проект VitePress так, будто это установленный пакет.</p><p>Однако чтобы дойти до этого, нам нужны какие-то реальные компоненты для распространения.</p><h2>Создаём наш первый компонент</h2><p>Теперь, когда всё настроено, мы наконец-то можем начать создавать компоненты! В этой статье мы будем держать всё просто и сосредоточимся на, возможно, самом распространённом компоненте: прекрасной кнопке.</p><p>По невероятному стечению обстоятельств ваш собственный веб-мастер Piccalilli, <a href="https://piccalil.li/author/andy-bell/">Andy Bell</a>, уже написал <i>великолепную</i> статью о <a href="https://piccalil.li/blog/how-i-build-a-button-component/">создании компонентов кнопок</a> на стандартном HTML и CSS. Мы будем опираться на эти принципы здесь, с небольшими изменениями, чтобы получить максимум от нашей настройки веб-компонентов.</p><p>Статья Andy отлично объясняет <i>почему</i> стоят за многими семантическими и структурными решениями, касающимися самих кнопок, поэтому я не буду углубляться в это слишком сильно. Andy прошёл путь, чтобы мы могли бежать. Какой человек.</p><h3>Композитные, примитивные и декларативные компоненты</h3><p>В основе концепции Elena «прогрессивные веб-компоненты» лежит разделение компонентов на три основные категории: <a href="https://elenajs.com/components/overview#_1-composite">композитные</a>, <a href="https://elenajs.com/components/overview#_2-primitive">примитивные</a> и <a href="https://elenajs.com/components/overview#_3-declarative">декларативные</a>. Документация Elena прекрасно объясняет различия подробно, но важно помнить, <i>что все это всё ещё просто веб-компоненты.</i> Нас не заставляют принимать нестандартные концепции или методы, скорее нас поощряют <i>думать</i> о наших компонентах в этих терминах.</p><p>Я позволю документации Elena сделать основную работу с этими определениями, но вот основные моменты:</p><ul><li><b>Композитные</b> компоненты оборачивают и расширяют свой внутренний HTML. Они отлично подходят для таких вещей, как слайдеры, аккордеоны, карточки и многослойные макеты — там, где вы чаще всего позволяете HTML и CSS делать основную работу и расширяете возможности с помощью JS в нужной области. Композитные компоненты также отлично подходят для <i>паттернов</i>, где мы можем захотеть объединить примитивные компоненты и HTML в переиспользуемые <i>«макро»</i> компоненты с определённым поведением. Подумайте о таких вещах, как диалоги, fieldsets и баннеры уведомлений — там, где структура и поведение фиксированы, но содержимое внутри остаётся гибким и определяется потребителем.</li><li><b>Примитивные</b> компоненты объявляют и рендерят свой собственный HTML и поставляются с собственной функцией render(). Это, вероятно, самые распространённые компоненты, которые мы будем использовать в дизайн-системе — подумайте о таких вещах, как кнопки, поля ввода, индикаторы загрузки, бейджи и т.д.</li><li><b>Декларативные</b> компоненты — это комбинация обоих типов и могут объединять Light DOM и декларативный Shadow DOM. Если мы не знаем, что нам <i>действительно</i> нужна инкапсуляция с Shadow DOM, мы можем практически игнорировать его для библиотек компонентов. Я не углублялся <i>слишком</i> сильно в это, но мои первые мысли таковы, что декларативные компоненты были бы отличны для полностью инкапсулированных компонентов, таких как веб-редакторы контента или блоки кода с подсветкой синтаксиса/редакторы, где инкапсуляция и изоляция часто критичны.</li></ul><p>На этот раз мы строим примитивный компонент. Наша кнопка будет объявлять и рендерить свой собственный HTML, и мы будем стилизовать её с помощью CSS в ограниченной области.</p><h3>Создаём каркас компонента</h3><p>Мы будем использовать CLI-помощник Elena для генерации папки и файлов, которые нам нужны для нашего компонента.</p><p>Пространства имён компонентов и пользовательские элементыМы используем сугубо учебный префикс my- для нашей кнопки, но зачем вообще нужен префикс? Это возвращает нас к тому, как пользовательские элементы требуют наличия - в имени тега, чтобы избежать конфликтов с нативными HTML-элементами. Если бы у нас был полный контроль, и мы создали компонент &lt;button&gt;, мы бы конфликтовали с настоящим HTML-элементом &lt;button&gt;, и у нас было бы Очень Плохое Время. Поэтому мы просто не можем этого делать — все наши пользовательские элементы должны быть в формате &lt;{prefix}-{component}&gt;.Жёсткого требования, чтобы наши файлы тоже были с дефисом, нет, но лично мне нравится аккуратность, когда имена файлов и папок совпадают с нашим фактическим элементом. Так что выберите префикс и придерживайтесь его. Для моей дизайн-системы Mindful Design я использую префикс md- — так что все мои пользовательские элементы выглядят примерно как &lt;md-button&gt;, &lt;md-card&gt; и т.д. Web Awesome использует wa-. Вы можете использовать всё, что пожелает ваше сердце. Главное — будьте последовательны.</p><p>Из папки packages/components выполните:</p><p>Затем вам будет предложено выбрать, какие функции и язык вы хотите. Для нашей кнопки нужно выбрать:</p><ul><li>Props</li><li>CSS-переменные</li><li>CSS-инкапсуляция</li><li>Комментарии в коде</li></ul><p>Нажмите Enter, затем выберите JavaScript в качестве языка.</p><p>Установите выходную директорию в src/. По умолчанию Elena использует src/components/, но нам не нужен такой уровень вложенности.</p><p>Это создаст каркас наших файлов с несколькими примерными значениями и комментариями, так что мы не будем смотреть на пустые файлы. Нажмите Enter после выбора функций, языка и директории, и Elena сгенерирует папку my-button с соответствующими JS и CSS файлами. О стилизации мы позаботимся позже, сейчас мы хотим спроектировать API нашего компонента.</p><p>Откройте следующий файл:</p><p>Здесь происходит много всего для простого boilerplate, но мы разберём каждый раздел по мере продвижения!</p><h3>Добавление props</h3><p>Компонентные <a href="https://elenajs.com/components/props">props</a> позволят нам управлять стилизацией и поведением компонента декларативным образом. Затем мы можем использовать эти props для создания вариантов наших компонентов. Для нашей кнопки давайте упростим и используем следующие props:</p><ul><li>variant: стилевой вариант нашей кнопки, например «primary», «danger»</li><li>disabled: отключена ли кнопка или нет</li><li>href: куда должна вести кнопка, также определяет, будет ли кнопка рендериться как ссылка или как кнопка</li></ul><p>Это небольшое подмножество props, которые потребуются кнопке в продакшене, но этого достаточно, чтобы двигаться дальше. Если после этого вы почувствуете себя уверенно, можете вернуться и добавить больше props — size или icon prop были бы отличной отправной точкой!</p><p>Давайте добавим эти props в наш компонент кнопки:</p><p>Объявление static props позволяет нам определить конечный массив props, которые будет принимать наш компонент. По умолчанию все эти props будут отражаться на нашем отрендеренном компоненте как HTML-атрибуты. Вам почти всегда нужно, чтобы это было так, особенно если вы используете нестандартные атрибуты вроде disabled, download и т.д.</p><p>Прямо под этим массивом вы найдёте заготовленные значения props по умолчанию, каждое с небольшим комментарием сверху. Давайте последуем примеру Elena и установим значения по умолчанию для добавленных нами props:</p><p>Комментарии над каждым определением — это JSDoc-комментарии. Они могут выглядеть немного непривычно, но позволяют документировать наши компоненты и props и могут служить источником истины для документирования API наших компонентов. Это также даёт нам немного «мягкой типизации» без необходимости использовать TypeScript. Большинство IDE будут подсвечивать или предупреждать вас, если вы установите prop в значение/тип, не указанный в синтаксисе JSDoc.</p><p>В приведённом выше примере мы определяем наш prop variant, задаём ему значение по умолчанию «default» и мягко типизируем его с помощью определения @type. В данном случае мы принимаем только одно из четырёх перечисленных значений.</p><p>На этом этапе это может показаться немного бессмысленным, но следите за своими JSDoc-комментариями по мере создания компонентов. Мы будем использовать их позже. Пока мы на этом, мы могли бы также задать более точное описание для нашего компонента.</p><p>Измените верхний комментарий в следующем файле:</p><p>Мы также удалили определения @cssprop из этого комментария. Они были сгенерированы, потому что мы выбрали «CSS Variables» при создании нашего компонента, и позволяют нам раскрыть кастомные свойства, используемые для стилизации наших компонентов. Если вы работаете над темизируемой или headless библиотекой компонентов, вы, возможно, захотите оставить их, в противном случае я предпочитаю пропускать определение этих свойств и не раскрывать их в своей документации.</p><p>Давайте взглянем на нашу функцию render():</p><p>Если у вас нет тяжёлого случая React Brain, вы, возможно, заметите хотя бы одну из пары проблем: во-первых, button — это не div. Дико, правда? Это не вина Elena, мы просто создали базовый компонент, и div — это, безусловно, самый распространённый HTML-элемент. Нам нужно самим отрендерить правильную, семантическую, доступную разметку.</p><p>Во-вторых, мы только что добавили href как prop, а это атрибут a, а не button. Нам нужно условно рендерить <i>либо</i> a, <i>либо</i> button в зависимости от того, установлен ли href.</p><h3>Условный рендеринг</h3><p>Дискуссия «должны ли ссылки когда-либо стилизоваться как кнопки?» старше, чем бородка вашего отчима, и точно так же как-то ещё сохраняется сквозь века. Я слишком стар и устал, чтобы беспокоиться об этом, а реальность такова, что кнопки-ссылки CTA — одна из самых распространённых вещей, которые вы увидите на сайте, в конкуренции только с баннерами cookie и плохой доступностью в своей повсеместности.</p><p>Так что вы будете делать это, нравится нам это или нет, и вам лучше делать это правильно.</p><p>Самый чистый подход к этому — абстрагировать наш рендеринг, добавив две новые функции:</p><p>Затем мы можем заменить функцию render() нашего компонента:</p><p>Супер просто: если href присутствует, это ссылка, если нет — это кнопка. Нам не нужно добавлять новые props, просто используем тот, что у нас уже есть.</p><p>Мы используем nothing в этой функции, и если вы попробуете собрать/запустить watch прямо сейчас, вы получите ошибку. Это потому, что nothing — это помощник Elena для безопасного рендеринга, ну, <i>ничего</i>.</p><p>Давайте импортируем его в начало нашей кнопки. Отредактируйте первую строку следующего файла:</p><p>Теперь давайте соберём нашу библиотеку компонентов, чтобы включить нашу новую кнопку в продакшенный bundle.js. Отредактируйте packages/components/src/index.js:</p><p>Затем из корня проекта выполните:</p><p>Если повезёт, сборка пройдёт без проблем, и мы наконец-то сможем встроить нашу кнопку в документацию.</p><h3>Предпросмотр нашей кнопки</h3><p>На данный момент у нас есть всё необходимое, чтобы отрендерить нашу кнопку и увидеть её на странице. Потребовалось немного настроек, чтобы дойти до этого, но мы сделали это!</p><p>Благодаря тому, как наш проект настроен, мы теперь можем подключать наши собранные компоненты так, как будто они являются отдельным пакетом. Нам просто нужно настроить VitePress для импорта бандла, который генерирует Elena, и сказать ему обрабатывать наши импортированные компоненты как веб-компоненты (по умолчанию VitePress ожидает Vue-компоненты).</p><p>Создайте docs/.vitepress/theme/index.js:</p><p>Это говорит нашей теме VitePress импортировать файлы бандла, которые сгенерировала Elena. @my-ds/components загружается асинхронно, так как это клиентская часть, а VitePress по умолчанию использует серверный рендеринг. @my-ds/components/dist/bundle.css импортируется напрямую в начале нашего файла, потому что это просто старый добрый CSS.</p><p>Примечание о серверном рендеринге (SSR)Если вы знаете, что вам нужен SSR, есть несколько способов включить его с Elena. В зависимости от вашего фреймворка/генератора сайта на выбор, вам, возможно, потребуется выполнить несколько дополнительных шагов конфигурации. Обратитесь к документации Elena за советами по SSR и на страницу интеграций с фреймворками для более продвинутых интеграций.</p><p>Далее обновите docs/.vitepress/config.mjs:</p><p>Это немного хакерский способ, но по сути он говорит VitePress рассматривать любой тег с - как пользовательский элемент вместо Vue-компонента. Учитывая, что пользовательские элементы требуют -, чтобы избежать конфликтов с нативными HTML-элементами, этого достаточно для наших целей.</p><p>Теперь давайте соберём нашу фактическую документацию по кнопке. Создайте docs/components/button.md:</p><p>Это должно дать нам всё необходимое для предпросмотра нашей кнопки! Запустите процесс разработки, если вы ещё этого не сделали:</p><p>Это запустит документацию VitePress в режиме разработки и одновременно запустит скрипт watch Elena, давая нам довольно удобный опыт live-reload. Перейдите на <a href="http://localhost:5173/components/button.html">http://localhost:5173/components/button.html</a>, и вы должны увидеть свою прекрасную кнопку!</p><p>Держите сервер разработки запущеннымКоманда npm run dev запустит ваш dev-сервер документации и будет держать Elena в фоне, отслеживая изменения компонентов. Вы будете получать live reloads всякий раз, когда вносите изменения, и теперь вы можете плавно вносить и тестировать изменения компонентов и документации.</p><p>Если вы откроете инспектор браузера и посмотрите на отрендеренную кнопку, вы увидите что-то вроде этого:</p><p>Наш хост-элемент &lt;my-button&gt; оборачивает разметку из своей функции render(), и мы имеем наш первый веб-компонент, отрендеренный в браузере! Я знаю, я знаю. Это выглядит совсем не круто, но оно там есть! Давайте придадим ему стиль.</p><h2>Стилизация нашей кнопки</h2><p>Если вы дошли до этого места, то, возможно, заметили явное отсутствие дискуссий о дизайн-токенах. Не потому, что они неважны, а потому что управление токенами и их распространение — это крайне объёмная тема, а эта статья и без того получается очень уж длинной. Скорее всего, мы разберём рабочие процессы с токенами в отдельном посте!</p><p>Пока что мы будем придерживаться простого подхода и использовать стили с ограниченной областью видимости Elena. Это позволяет нам частично нейтрализовать каскад в CSS и гарантировать, что стили не выйдут за пределы оформления отдельного компонента.</p><p>Мы также сделаем кое-что, чего я <b>не рекомендую</b> для production-компонентов, особенно если вы хотите строить темизируемые дизайн-системы, наследующие разумные глобальные стили (спойлер: вы хотите), — а именно, сбросим стили компонента перед применением собственных. В итоге мы получаем компонент, который не пропускает стили наружу (благодаря ограниченной области видимости) и не позволяет глобальным стилям проникать внутрь (благодаря сбросу на уровне компонента).</p><h3>Стили с ограниченной областью видимости</h3><p>Откройте следующий файл:</p><p>По умолчанию Elena генерирует at-правило @scope для любого создаваемого компонента. Вы <i>не обязаны</i> использовать стили с ограниченной областью видимости. Важно помнить: это <b>всё обычный CSS</b>. Мы не делаем ничего дикого или хрупкого вроде CSS-in-JS, мы просто используем конкретную стандартную возможность CSS. Вы с таким же успехом можете писать неограниченный CSS с пространствами имён и получить в целом те же результаты.</p><p>Более того, если вам нужно поддерживать браузеры, которые не поддерживают @scope, то использование пространств имён может быть тем самым подходом, который вам нужен. Для этого примера (и для моей собственной production-работы) меня вполне устраивает @scope — я считаю его гораздо более чистым способом стилизации компонентов.</p><p>Поскольку при создании компонента мы выбрали «CSS Encapsulation», вы видите, что Elena сгенерировала следующее:</p><p>По сути это означает, что наш компонент не будет наследовать стили из более высоких уровней каскада. В зависимости от вашего подхода к стилизации в дизайн-системе, это может быть как тем, что нужно, так и нет. Для <i>этого конкретного сценария</i> это самый простой способ гарантировать полный контроль над каждым компонентом. Однако во многих реальных сценариях вам действительно стоит <i>хотеть</i> некоторой степени наследования или стилей по умолчанию в компонентах.</p><p>Примечание об инкапсулированном сбросеИспользование all: unset и display: revert сбросит любые стили, определённые до тех пор, пока эти правила не встретятся. Это не предотвратит применение дальнейших неограниченных стилей в файлах или тегах </p>]]></content:encoded>
    </item>
    <item>
      <title>Как TypeScript выводит типы переменных: разбор алгоритма</title>
      <link>https://tproger.ru/articles/kak-typescript-vyvodit-tipy-peremennyh-razbor-algoritma</link>
      <comments>https://tproger.ru/articles/kak-typescript-vyvodit-tipy-peremennyh-razbor-algoritma?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-typescript-vyvodit-tipy-peremennyh-razbor-algoritma</guid>
      <description><![CDATA[<p>Разбираем двухфазный алгоритм вывода типовых переменных в TypeScript: сбор кандидатов, разрешение, вариантность, пересечения и NoInfer. Узнайте, почему компилятор ведёт себя именно так.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-typescript-vyvodit-tipy-peremennyh-razbor-algoritma">Как TypeScript выводит типы переменных: разбор алгоритма</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Компиляторы]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 08 Jun 2026 05:15:55 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если вы когда-нибудь ловили себя на мысли, что TypeScript ведёт себя странно с дженериками, знайте: вы не одиноки. TypeScript выводит типовые переменные в две фазы: сначала собирает кандидатов из аргументов и контекста, затем сворачивает список в единственный тип. Иногда компилятор выводит unknown там, где ожидаешь конкретный тип, а иногда — наоборот, слишком поспешно захватывает весь объект вместо его «остаточной» части. В этой статье разберём, как внутри устроен алгоритм вывода типовых переменных — и почему он ведёт себя именно так.</p><p>Это не дословный перевод, а авторский разбор на основе исследования Nicolas Laurent (norswap), дополненный практическими примерами для enterprise-разработки на TypeScript.</p><h2>Что такое вывод типовых переменных</h2><p>В TypeScript, когда вы вызываете функцию с дженериком function foo&lt;T&gt;(x: T), компилятор должен понять, чему равен T. Этот процесс называется <b>type variable inference</b> — выводом типовой переменной. Он происходит в две фазы: сначала TypeScript собирает «кандидатов» из типов аргументов и контекста возврата, а затем «сворачивает» список кандидатов в единственный тип.</p><p>Звучит просто, но на практике алгоритм полон тонкостей: covariant и contravariant позиции, приоритеты кандидатов, странное поведение пересечений и неочевидные ограничения. Разберём всё по порядку.</p><p>TypeScript выводит типовые переменные в две фазы: сбор кандидатов из аргументов и контекста, затем свёртка списка в единственный тип.</p><p>Кандидаты делятся на ковариантные (выходные позиции) и контравариантные (входные позиции); контравариантный результат обычно побеждает.</p><p>При свёртке TypeScript ищет общий супертип в списке кандидатов, но никогда не выбирает тип, которого нет в списке.</p><p>Пересечения типов ведут себя непредсказуемо: иногда TypeScript «снимает» литеральный тип, иногда захватывает весь объект.</p><p>В generic-контексте условные типы (extends ? :) не вычисляются, что ломает инференс через Exclude и подобные утилиты.</p><p>NoInfer&lt;T&gt; (с TypeScript 5.4+) позволяет блокировать вывод типа из конкретной позиции — полезно для API со значениями по умолчанию.</p><h2>Фаза 1: сбор кандидатов</h2><p>Когда TypeScript видит вызов функции с дженериком, он сопоставляет типы аргументов (source types) с типами параметров функции (target types). Каждый раз, когда «прогулка» по типам доходит до «голого» type parameter в target, соответствующий source type записывается как кандидат.</p><h3>Ковариантные и контравариантные кандидаты</h3><p>Кандидаты собираются в два списка. Если type parameter находится в позиции аргумента функции или возвращаемого значения — это <b>ковариантный</b> (output position) кандидат. Если type parameter находится в позиции параметра callback-функции — это <b>контравариантный</b> (input position) кандидат.</p><h3>Условные типы и infer</h3><p>Когда TypeScript встречает условный тип, кандидаты собираются только из той ветки, которая теоретически может сработать. Но важно: <b>само условие не добавляет кандидатов</b>. Запись T extends Foo НЕ добавляет Foo как кандидат для T.</p><p>Это классическая ловушка: кандидаты собираются из одной ветки, а типовая проверка — из другой. TypeScript <b>не вычисляет</b> условные типы на этапе инференса.</p><h3>Распределение по union</h3><p>Если source type — union, а target — не «голый» type parameter, TypeScript проходит по каждой ветке union отдельно и собирает кандидатов из всех веток в один список.</p><h3>Что НЕ учитывается при сборе</h3><ul><li>Условие условного типа (только ветки).</li><li>Constraints (&lt;T extends Foo&gt;) — они проверяются позже, на этапе разрешения.</li><li>Дефолтные значения type parameter (&lt;T = unknown&gt;) — используются только если список кандидатов пуст.</li><li>Супертипы source type.</li></ul><h2>Фаза 2: разрешение кандидатов</h2><p>После сбора TypeScript сворачивает каждый список в единственный тип. Алгоритм зависит от вариантности и содержимого списка.</p><h3>Ковариантные кандидаты</h3><ol><li>Если все кандидаты — литералы одного базового типа, они объединяются в union.</li><li>Иначе ищется кандидат, который является строгим супертипом всех остальных. Если найден — он выбирается.</li><li>Если нет — выполняется left-reduce: начинаем с первого кандидата, идём по списку, заменяя текущий на супертип, если встречаем его.</li></ol><p><b>Важно:</b><br />Если в списке кандидатов нет общего супертипа, TypeScript выбирает <b>первый</b> элемент. Это частая причина неожиданных ошибок при передаче разнородных аргументов одного дженерика.</p><h3>Контравариантные кандидаты</h3><p>Для контравариантных кандидатов логика зеркальная: ищется общий <b>подтип</b>, иначе — left-reduce с подтипами. Если оба списка (ковариантный и контравариантный) непусты, контравариантный результат обычно побеждает — за исключением случаев, когда ковариантный результат является подтипом контравариантного.</p><h3>Приоритеты кандидатов</h3><p>Каждый кандидат помечается приоритетом. Кандидат с более высоким приоритетом <b>стирает</b> все кандидаты с более низким приоритетом. Основные приоритеты: None (0), NakedTypeVariable (1), MappedTypeConstraint (32), ReturnType (128), LiteralKeyof (256).</p><p>Приоритеты MappedTypeConstraint, ReturnType и LiteralKeyof — «комбинационные»: при них результатом становится union или intersection всех кандидатов, а не единственный тип.</p><h2>Пересечения: загадочное поведение</h2><p>Одно из самых неинтуитивных мест — вывод через пересечения. Рассмотрим несколько примеров.</p><p>Почему так? Это эвристики компилятора, направленные на максимизацию полезности захваченного типа. В первом случае полезнее получить весь объект, во втором — сохранить литеральную точность строки. Порядок операндов пересечения не влияет на результат.</p><h2>Вывод в generic-контексте</h2><p>Когда инференс происходит внутри другой generic-функции, условные типы <b>не могут быть вычислены</b>, потому что type variables ещё не связаны. Это ломает паттерны вроде Exclude, если вызывать их через generic-обёртку.</p><p>В типовых enterprise-проектах, где активно используются типовые утилиты для валидации API (например, в проектах на NestJS или с Zod), это ограничение часто приводит к необходимости явно прописывать generic-аргументы или реструктурировать типы.</p><h2>NoInfer: как управлять выводом</h2><p>Начиная с TypeScript 5.4, в языке есть встроенный вспомогательный тип NoInfer&lt;T&gt;. Он блокирует сбор кандидатов для T из той позиции, где используется NoInfer&lt;T&gt;.</p><p>Это особенно полезно в библиотечном коде, где нужно разделить «источник истины» для вывода типа и позиции, которые должны ему подчиняться.</p><h2>FAQ</h2><h2>Выводы</h2><p>Теперь вы знаете, как TypeScript выводит типы переменных: через сбор кандидатов из аргументов, свёртку списка с учётом вариантности и приоритетов, а затем проверку результата на соответствие constraints. Алгоритм вывода типовых переменных — это тщательно продуманная, но не лишённая сюрпризов система. Две фазы (сбор и разрешение), вариантность, приоритеты и эвристики для пересечений создают поверхность, которую сложно угадать интуитивно, но которую можно понять, зная правила.</p><p>Ключевые практические выводы для разработчиков:</p><ol><li>Если функция принимает несколько аргументов одного дженерика — убедитесь, что они совместимы, иначе TypeScript выберет первый кандидат и отвергнет остальные.</li><li>Не полагайтесь на супертипы для инференса: они не попадают в список кандидатов.</li><li>В generic-контексте условные типы «замораживаются» — проектируйте API с этим ограничением.</li><li>Используйте NoInfer&lt;T&gt; в библиотечном коде для точного контроля над источниками вывода типов.</li><li>При странном поведении с пересечениями — перепишите типы явно или добавьте constraint.</li></ol><blockquote>Понимание алгоритма вывода типовых переменных позволяет писать более надёжные типовые абстракции и тратить меньше времени на отладку сложных generic-конструкций.</blockquote><p><b>Источники:</b></p><ul><li><a href="https://norswap.com/typescript-type-variable-inference">How TypeScript infers type variables — Nicolas Laurent (norswap)</a></li><li><a href="https://norswap.com/typescript-distribute">How TypeScript distributes unions — Nicolas Laurent (norswap)</a></li><li><a href="https://www.typescriptlang.org/docs/handbook/2/generics.html">TypeScript Handbook: Generics</a></li></ul><p>Если у вас есть примеры неожиданного поведения TypeScript с дженериками — делитесь в комментариях. Соберём коллекцию «типовых загадок» вместе.</p>]]></content:encoded>
    </item>
    <item>
      <title>Cloudflare приобрела VoidZero — создателей Vite, Vitest и Rolldown</title>
      <link>https://tproger.ru/news/cloudflare-poglotila-voidzero-sozdatelej-vite-vitest-i-rolldo</link>
      <comments>https://tproger.ru/news/cloudflare-poglotila-voidzero-sozdatelej-vite-vitest-i-rolldo?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/cloudflare-poglotila-voidzero-sozdatelej-vite-vitest-i-rolldo</guid>
      <description><![CDATA[<p>Cloudflare приобрела компанию VoidZero, стоящую за Vite, Vitest, Rolldown и Oxc. Проекты сохранят открытую лицензию MIT и независимость. Разбираем детали</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/cloudflare-poglotila-voidzero-sozdatelej-vite-vitest-i-rolldo">Cloudflare приобрела VoidZero — создателей Vite, Vitest и Rolldown</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Cloudflare]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 05 Jun 2026 11:19:42 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если вы используете Vite — ничего не сломается, но вот что изменится в долгосрочной перспективе. 4 июня 2026 года Cloudflare <a href="https://blog.cloudflare.com/voidzero-joins-cloudflare/" rel="noopener">объявила</a> о приобретении VoidZero — компании, основанной создателем Vue.js Эваном Ю и стоящей за ключевыми инструментами JavaScript-экосистемы: <b>Vite</b>, <b>Vitest</b>, <b>Rolldown</b>, <b>Oxc</b> и <b>Vite+</b>. Сделка означает, что вся команда VoidZero переходит в Cloudflare, но сами проекты останутся открытыми, независимыми и под лицензией MIT.</p><p>Cloudflare приобрела VoidZero — компанию-основателя Vite, Vitest, Rolldown, Oxc и Vite+.</p><p>Вся команда VoidZero переходит в Cloudflare, включая Эвана Ю.</p><p>Проекты сохраняют открытый исходный код, лицензию MIT и независимость от поставщика.</p><p>Cloudflare выделяет $1 млн на фонд экосистемы Vite для поддержки сопровождающих.</p><p>Vite набирает около 129 млн загрузок в неделю; плагин Cloudflare для Vite — 14 млн.</p><p>Дашборд Cloudflare уже работает на Vite, а CLI компания переведёт на него в будущем.</p><p>VoidZero появилась как попытка собрать в единую экосистему наиболее востребованные инструменты фронтенд-разработки. Vite — система сборки, ставшая фактическим стандартом для современных JavaScript-приложений. Vitest отвечает за тестирование, Rolldown заменяет Rollup на Rust, а Oxc — это платформа инструментов для JavaScript/TypeScript на Rust, включающая компилятор, линтер Oxlint, форматировщик и другие компоненты.</p><p>Cloudflare подчёркивает, что не собирается перекраивать планы развития проектов под свои нужды. По аналогии с <a href="https://blog.cloudflare.com/astro-joins-cloudflare/" rel="noopener">приобретением Astro</a> ранее в этом году, команды сохраняют автономию, а код остаётся открытым. Главное отличие в том, что Vite — это не просто фреймворк, а фундамент, на котором строятся Vue, SvelteKit, Nuxt, Astro, Solid, Qwik, Angular, React Router, TanStack Start и даже Next.js — в экспериментальном проекте vinext.</p><h2>Что меняется для разработчиков</h2><p>В краткосрочной перспективе — ничего. Vite, Vitest, Rolldown, Oxc и Vite+ продолжат развиваться по прежним планам. Эван Ю и команда VoidZero остаются лидерами проектов. Cloudflare обещает направлять инженерные ресурсы на развитие инструментов, а не на их смену бренда или закрытие.</p><p>В долгосрочной перспективе Cloudflare намерена построить собственный CLI cf поверх Vite. Цель — чтобы команды cf dev, cf build и cf deploy чувствовали себя как расширение привычного vite dev. Это должно упростить развёртывание Vite-приложений на платформу Cloudflare Workers, сохранив при этом переносимость кода между любыми хостингами.</p><h2>Почему это важно</h2><p>Vite сегодня — один из немногих инструментов, которые объединяют всю JavaScript-экосистему. По данным Cloudflare, Vite набирает <b>129 млн загрузок в неделю</b>, а официальный плагин @cloudflare/vite-plugin — <b>14 млн</b>. С ростом ИИ-агентов, которые генерируют код и выполняют итерации с ним в цикле, скорость сборки, тестирования и линтинга становится критичной. Весь стек VoidZero оптимизирован для таких сценариев: Rust-инструменты работают на порядок быстрее аналогов на JavaScript.</p><p>Кроме того, Cloudflare выделяет <b>$1 млн</b> в фонд экосистемы Vite. Деньги пойдут на поддержку сопровождающих и участников — администрировать фонд будет ядро команды Vite. Это редкий случай, когда крупная инфраструктурная компания инвестирует напрямую в открытый исходный код, не пытаясь монополизировать его.</p><h2>Выводы</h2><p>Приобретение VoidZero — значимый корпоративный вклад в открытую инфраструктуру JavaScript. Cloudflare получает влияние на инструмент, который используют миллионы разработчиков, а сообщество — гарантии независимости и $1 млн на развитие экосистемы.</p><p>Для разработчиков практических изменений в ближайшие месяцы не предвидится: команды — на месте, а плагин для Cloudflare продолжит развиваться. Долгосрочная ставка — на унификацию CLI и углубление интеграции Vite с пограничной платформой Cloudflare.</p><p>Источник: <a href="https://blog.cloudflare.com/voidzero-joins-cloudflare/" rel="noopener">Cloudflare Blog — VoidZero is joining Cloudflare</a></p><p>Хотите попробовать Vite на Cloudflare прямо сейчас — выполните npm create vite@latest, а затем npx wrangler deploy.</p>]]></content:encoded>
    </item>
    <item>
      <title>Пора прощаться с ESLint? Как Oxlint меняет правила игры в JavaScript-разработке</title>
      <link>https://tproger.ru/articles/pora-proshhatsya-s-eslint-kak-oxlint-menyaet-pravila-igry-v-javasc</link>
      <comments>https://tproger.ru/articles/pora-proshhatsya-s-eslint-kak-oxlint-menyaet-pravila-igry-v-javasc?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/pora-proshhatsya-s-eslint-kak-oxlint-menyaet-pravila-igry-v-javasc</guid>
      <description><![CDATA[<p>Oxlint на Rust обгоняет ESLint в 50–100 раз по скорости и требует минимальной настройки. Разбираем бенчмарки и сценарии миграции.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/pora-proshhatsya-s-eslint-kak-oxlint-menyaet-pravila-igry-v-javasc">Пора прощаться с ESLint? Как Oxlint меняет правила игры в JavaScript-разработке</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 04 Jun 2026 12:05:14 GMT</pubDate>
      <content:encoded><![CDATA[<p>Линтер <b>Oxlint</b>, написанный на Rust, в 50–100 раз быстрее привычного ESLint и работает сразу после установки. Разбираем, когда миграция оправдана, а когда лучше подождать.</p><p><b>ESLint</b> — это де-факто стандарт статического анализа JavaScript-кода. Инструмент работает поверх Node.js и V8, поддерживает сотни плагинов и позволяет настраивать правила под любой проект. По данным опроса State of JavaScript 2025, ESLint остаётся самым популярным вспомогательным инструментом среди фронтенд-разработчиков.</p><p>Новый проект развивается в рамках экосистемы <b>Oxc</b>, поддерживаемой командой <b>VoidZero</b>.</p><p>Oxlint на Rust обгоняет ESLint в 50–100 раз на крупных репозиториях вроде Vue Core и React Router.</p><p>Из коробки включено 107 правил, тогда как ESLint требует ручной настройки даже для базовых сценариев.</p><p>Поддержка ESLint-плагинов экспериментальная, но список доступных правил постоянно растёт.</p><p>Миграция возможна постепенно: оба линтера можно запускать параллельно.</p><p>Если ваш проект завязан на редкие плагины или пользовательские правила — спешить не стоит.</p><h2>Почему ESLint начинает раздражать</h2><p>Несмотря на зрелость экосистемы, у ESLint накопился приличный багаж архитектурных ограничений:</p><ul><li>Производительность. Код ESLint выполняется в однопоточном режиме поверх JavaScript-движка. В небольших проектах это незаметно, но в монорепозиториях с сотнями тысяч строк проверка легко растягивается на две минуты и больше.</li><li>Конфигурационный ад. Новичкам приходится разбираться в иерархии конфигов, flat config, shared presets и compatibility layers. Документация исчерпывающая, но порог входа остаётся высоким.</li><li>Минимум из коробки. Базовая установка ESLint практически ничего не проверяет. Для получения хоть какой-то пользы нужно ставить плагины, изучать правила и собирать конфигурацию с нуля — в отличие от Prettier или Biome, которые работают сразу после установки.</li></ul><h2>Чем Oxlint лучше привычного линтера</h2><h3>Скорость, которую можно измерить</h3><p>Главное преимущество Oxlint — скорость. В тестах на репозитории <b>Vue Core</b> с type-aware правилами Oxlint справляется за <b>1,3 секунды</b>, тогда как ESLint с typescript-eslint тратит <b>133,8 секунды</b>. Это почти в 100 раз быстрее. На репозитории <b>React Router</b> разрыв меньше, но всё равно впечатляет: <b>435 мс</b> против <b>29,5 с</b> — ускорение в 68 раз.</p><h3>Type-aware линтинг без тормозов</h3><p>ESLint для type-aware правил использует typescript-eslint, который перед проверкой запускает полный анализ через tsc. Это наследует все накладные расходы компилятора TypeScript. Oxlint делает это иначе: type-aware функциональность реализована через oxlint-tsgolint на Go, который в связке с TypeScript 7 и компилятором tsgo работает в 20–40 раз быстрее привычного пайплайна.</p><h3>Настройка за минуту, а не за час</h3><p>После установки Oxlint сразу активирует 107 правил. Конфигурация проще, документация понятнее, а сообщения об ошибках структурированы так, что и человек, и LLM-ассистент разберутся с первого взгляда.</p><h3>Постепенная миграция без боли</h3><p>Oxlint не требует выбросить ESLint в один день. Инструменты можно запускать параллельно: Oxlint берёт быструю проверку на pre-commit, а ESLint остаётся в CI до полного перехода. Экспериментальная поддержка JavaScript-плагинов ESLint уже работает, хотя и не покрывает всю экосистему.</p><h2>Реальные цифры: бенчмарки на популярных репозиториях</h2><p>Автор оригинального материала воспроизвёл тесты на ноутбуке HP EliteBook 1040 G7 (16 ГБ ОЗУ, 4 физических ядра, 8 потоков). Результаты для Vue Core с type-aware правилами:</p><p>Результаты для React Router (без type-aware правил):</p><p>Цифры подтверждают заявленные разработчиками 50–100-кратное ускорение. Для разработчика это разница между «пойду за кофе, пока линтер работает» и «результат на экране мгновенно».</p><h2>Когда ESLint всё ещё нужен</h2><p>Несмотря на впечатляющие цифры, спешить со сносом ESLint не всегда разумно. Вот сценарии, где старый инструмент остаётся предпочтительнее:</p><ul><li>Редкие плагины и пользовательские правила. Если ваш проект завязан на специфические ESLint-плагины, которых ещё нет в Oxlint, миграция потребует дополнительной работы.</li><li>Малые проекты. В репозиториях до 10–20 тысяч строк разница между 1 секундой и 30 секундами линтинга не критична.</li><li>Сложные рабочие процессы. Глубокая интеграция ESLint в CI/CD, пользовательские форматтеры и специфические пайплайны могут быть дорого переносить.</li><li>Сообщество и экосистема. ESLint остаётся доминирующим линтером. Вокруг него больше обучающих материалов, примеров конфигураций и поддержки со стороны LLM-ассистентов.</li></ul><h2>Как мигрировать с ESLint на Oxlint</h2><p>Команда Oxlint подготовила утилиту @oxlint/migrate, которая автоматически преобразует конфигурацию ESLint в формат Oxlint. Выбор пути зависит от текущего состояния проекта:</p><ol><li>Для ESLint v9/v10+ с flat config: запустите npx @oxlint/migrate — утилита преобразует поддерживаемые правила и сообщит о несовместимых.</li><li>Для ESLint v8 и старше (в v10 поддержка legacy-конфигов полностью удалена): сначала мигрируйте на flat config через npx @eslint/migrate-config, затем примените @oxlint/migrate.</li><li>Если нужны type-aware правила: добавьте флаг --type-aware к команде npx @oxlint/migrate и установите oxlint-tsgolint.</li><li>Для экспериментальной поддержки JS-плагинов: используйте флаг --js-plugins в команде npx @oxlint/migrate.</li><li>Не уверены в безопасности? Запустите оба линтера параллельно на несколько недель и сравните результаты.</li></ol><p><b>Совет:</b><br />Начните миграцию с новых модулей или микрофронтендов, а не с устаревшего кода, где линтер и так давно отключён.</p><h2>Выводы</h2><p>ESLint не умер, но его эпоха безраздельного господства подходит к концу. Oxlint демонстрирует, что статический анализ JavaScript может быть быстрым, простым в настройке и дружелюбным к разработчику. Для большинства современных проектов переход уже оправдан экономикой времени: сэкономленные минуты на каждом коммите за год превращаются в десятки часов продуктивной работы.</p><blockquote>Для большинства современных проектов Oxlint — это уже не перспективная альтернатива, а вполне зрелый инструмент по умолчанию.</blockquote><p>Источник: <a href="https://blog.logrocket.com/retire-eslint-migrate-oxlint/" rel="noopener noreferrer">LogRocket — Retire ESLint: How (and why) to migrate to Oxlint</a></p><p>Репозиторий проекта: <a href="https://github.com/oxc-project/oxc" rel="noopener noreferrer">github.com/oxc-project/oxc</a>. Документация: <a href="https://oxc.rs" rel="noopener noreferrer">oxc.rs</a>.</p><p>Попробуйте запустить npx @oxlint/migrate на своём проекте и сравните цифры. Возможно, вы больше никогда не захотите ждать, пока закончится npm run lint.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как правильно использовать поля HTML-форм: гайд для разработчиков</title>
      <link>https://tproger.ru/articles/kak-pravilno-ispolzovat-polya-html-form-polnyj-gajd</link>
      <comments>https://tproger.ru/articles/kak-pravilno-ispolzovat-polya-html-form-polnyj-gajd?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-pravilno-ispolzovat-polya-html-form-polnyj-gajd</guid>
      <description><![CDATA[<p>Разбираем типы полей HTML-форм, правила доступности, ARIA-атрибуты и лучшие практики. Узнайте, как создавать удобные и конверсионные формы для любых устройств.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-pravilno-ispolzovat-polya-html-form-polnyj-gajd">Как правильно использовать поля HTML-форм: гайд для разработчиков</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[Дизайн интерфейсов и UX]]></category>
      <category><![CDATA[Фронтенд-разработка с нуля]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 04 Jun 2026 09:55:25 GMT</pubDate>
      <content:encoded><![CDATA[<p>Неправильно свёрстанная форма — серьёзный источник раздражения пользователей на сайте. Поле email без подходящей клавиатуры на телефоне, чекбокс без label, который невозможно попасть пальцем, или форма, которая отправляется при случайном нажатии Enter — всё это ежедневно встречается даже на крупных проектах. Разбираем, как избежать этих ошибок и сделать формы удобными для всех пользователей.</p><h2>Что такое поля форм</h2><p>Поля форм — это интерактивные элементы HTML, через которые пользователь взаимодействует с веб-страницей. Браузер отображает их по-разному в зависимости от типа: для email показывает клавиатуру с символом @, для даты — календарь, для файла — диалог выбора документа.</p><p>Главное правило: под каждую задачу существует своё поле. Использовать универсальный &lt;input type="text"&gt; везде — значит лишать пользователя встроенных удобств браузера.</p><p>{'id': 'a94adbf9d2', 'data': {'text': '<b>Правильный тип input</b> — залог удобства: браузер сам подставит нужную клавиатуру и проверит формат.'}, 'type': 'paragraph'}</p><p>{'id': 'd3748dda0e', 'data': {'text': '<b>Label обязателен</b> для каждого поля: связывайте его через атрибут for с id поля.'}, 'type': 'paragraph'}</p><p>{'id': 'c924682827', 'data': {'text': '<b>Группируйте связанные поля</b> в &lt;fieldset&gt; с &lt;legend&gt; — это помогает средствам чтения с экрана и структурирует форму.'}, 'type': 'paragraph'}</p><p>{'id': '49afc5a27c', 'data': {'text': '<b>Не забывайте name</b>: без него данные не попадут в запрос при отправке формы.'}, 'type': 'paragraph'}</p><p>{'id': 'ab33c8052b', 'data': {'text': '<b>Используйте &lt;button type="submit"&gt;</b> вместо устаревшего &lt;input type="submit"&gt; — это гибче и семантичнее.'}, 'type': 'paragraph'}</p><h2>Основные элементы форм</h2><h3>input — универсальный инструмент ввода</h3><p>Элемент &lt;input&gt; — самый распространённый в формах. Его внешний вид и поведение полностью зависят от атрибута type. Браузер поддерживает свыше 20 типов: от классического text до специализированных date, tel, range и даже color.</p><p>Когда вы выбираете конкретный тип, браузер берёт на себя три задачи: отображает подходящий интерфейс, показывает оптимальную экранную клавиатуру на мобильных устройствах и применяет встроенную проверку. Например, type="email" проверит наличие символа @ до отправки на сервер.</p><p>Вот типы input, которые стоит знать каждому фронтенд-разработчику:</p><ul><li>text — текстовая строка, базовый тип.</li><li>email — проверяет наличие @ и домена, показывает email-клавиатуру.</li><li>tel — цифровая клавиатура на мобильных устройствах.</li><li>number — ограничивает ввод числами, добавляет стрелки.</li><li>date — нативный календарь браузера.</li><li>checkbox и radio — выбор одного или нескольких вариантов.</li><li>file — загрузка файлов с нативным диалогом выбора.</li><li>range — ползунок для выбора значения из диапазона.</li><li>color — нативный выбор цвета.</li><li>search — поле поиска с кнопкой очистки.</li></ul><p>Атрибут required делает поле обязательным для заполнения, а pattern позволяет задать регулярное выражение для проверки введённых данных без JavaScript.</p><h3>label — связываем текст с полем</h3><p>Без подписи поле ввода теряет смысл. Элемент &lt;label&gt; решает эту задачу и одновременно делает форму доступной для пользователей с ограничениями зрения.</p><p>Есть два способа связать label с полем. Первый — явный: атрибут for на label должен совпадать с id на input. Второй — вложенный: input размещается внутри label. Оба варианта работают, но явная связь через for гибче: позволяет располагать label и input в разных частях разметки.</p><p><b>Важно:</b><br />Клик по label автоматически переводит фокус в связанное поле. Это удобно на десктопе и критично на мобильных устройствах, где площадь касания имеет значение.</p><h3>textarea — когда одной строки мало</h3><p>Для длинных текстов — комментариев, отзывов, описаний — используйте &lt;textarea&gt;. В отличие от &lt;input&gt;, он поддерживает переносы строк и растягивается по содержимому. Атрибуты rows и cols задают начальные размеры, а CSS — финальное оформление.</p><h3>select и datalist — выбор из вариантов</h3><p>Когда нужно предложить пользователю готовый список вариантов, на помощь приходит &lt;select&gt;. Он отображает выпадающий список, в котором можно выбрать один или несколько пунктов. По умолчанию выбран первый элемент, но через атрибут selected можно задать другой.</p><p>Альтернатива — &lt;datalist&gt;. Он работает как автодополнение: пользователь может как выбрать вариант из списка, так и ввести свой собственный текст. Это удобно для полей вроде «профессия» или «название компании», где невозможно предусмотреть все варианты.</p><h3>fieldset и legend — группируем поля</h3><p>Формы с десятком полей выглядят как стена текста. Чтобы структурировать их, используйте &lt;fieldset&gt; с обязательным дочерним элементом &lt;legend&gt;. Такая группировка помогает пользователям ориентироваться и критична для средств чтения с экрана: они объявляют legend перед каждым полем в группе.</p><h3>ARIA-атрибуты для сложных форм</h3><p>Для скринридеров и пользователей с ограничениями зрения важно не только правильное использование label, но и дополнительные ARIA-атрибуты. Например, aria-required="true" сообщает о необходимости заполнения, а aria-describedby связывает поле с текстом подсказки или сообщения об ошибке.</p><p>Атрибут aria-live="polite" на контейнере с ошибками позволяет скринридеру озвучить сообщение без прерывания текущего действия пользователя. Это особенно важно для динамических форм, где ошибки появляются после асинхронной проверки на сервере.</p><h3>Как отправить форму</h3><p>Для отправки данных используйте &lt;button type="submit"&gt;. Это семантично, стилизуется гибче, чем &lt;input type="submit"&gt;, и поддерживает вложенный HTML-контент — например, иконку рядом с текстом.</p><p><b>Важно:</b><br />Если у кнопки не указан атрибут type, браузер считает её кнопкой отправки по умолчанию. Чтобы избежать случайной отправки, всегда явно указывайте type="button" для кнопок, которые не должны отправлять форму.</p><p>Помимо клика по кнопке, форма отправляется при нажатии клавиши Enter в однострочных полях ввода. В многострочном &lt;textarea&gt; Enter добавляет перенос строки. Это стандартное поведение браузера, которое стоит учитывать при проектировании многошаговых форм: случайная отправка на середине заполнения раздражает пользователей.</p><h2>Чек-лист: правильная форма за 5 минут</h2><ul><li>Каждое поле имеет связанный &lt;label&gt; через for и id.</li><li>Выбран подходящий type для &lt;input&gt; — не везде text.</li><li>У всех полей есть атрибут name, иначе данные не уйдут на сервер.</li><li>Связанные поля объединены в &lt;fieldset&gt; с &lt;legend&gt;.</li><li>Для длинных текстов используется &lt;textarea&gt;, а не многострочный input.</li><li>Кнопка отправки — &lt;button type="submit"&gt;, а не устаревший input.</li><li>Форма работает без JavaScript: базовая проверка и отправка происходят на уровне HTML.</li></ul><h2>Выводы</h2><p>Поля HTML-форм — это не просто визуальные элементы, а полноценный интерфейс взаимодействия пользователя с вашим приложением. Правильный выбор типов input, связь через label, группировка в fieldset, ARIA-атрибуты и семантичная кнопка отправки — всё это влияет на удобство, доступность и конверсию.</p><blockquote>Доступность не добавляется в проект в конце — она закладывается на этапе разметки. Правильно свёрстанная форма работает для всех пользователей без дополнительных усилий.</blockquote><p>Если хотите углубиться в тему — изучите <a href="https://web.dev/learn/forms/">полный курс по формам от Google</a>. Там разбираются проверка данных, оформление, доступность и даже usability-тестирование поведения пользователей при заполнении форм.</p><p>Источник: <a href="https://web.dev/learn/forms/form-fields/">web.dev — Help users enter data in forms</a></p>]]></content:encoded>
    </item>
    <item>
      <title>317 npm-пакетов скомпрометированы вредоносом Mini Shai-Hulud</title>
      <link>https://tproger.ru/news/317-npm-paketov-skomprometirovany-vredonosom-mini-shai-hulud</link>
      <comments>https://tproger.ru/news/317-npm-paketov-skomprometirovany-vredonosom-mini-shai-hulud?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/317-npm-paketov-skomprometirovany-vredonosom-mini-shai-hulud</guid>
      <description><![CDATA[<p>19 мая взломан npm-аккаунт atool: 637 вредоносных версий 317 пакетов за 22 минуты. Крадут AWS-ключи, GitHub PAT, данные 1Password. Проверьте зависимости.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/317-npm-paketov-skomprometirovany-vredonosom-mini-shai-hulud">317 npm-пакетов скомпрометированы вредоносом Mini Shai-Hulud</a>»</p>]]></description>
      <category><![CDATA[Информационная безопасность]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 19 May 2026 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если вы используете echarts-for-react, size-sensor или пакеты из семейства @antv — проверьте package-lock.json прямо сейчас. 19 мая 2026 года за 22 минуты был взломан npm-аккаунт atool: атакующий опубликовал 637 вредоносных версий 317 пакетов, суммарно набирающих десятки миллионов загрузок в месяц.</p><p>317 npm-пакетов скомпрометированы через взломанный аккаунт atool: под угрозой echarts-for-react (3,8 млн загрузок/мес), size-sensor (4,2 млн) и более 250 пакетов @antv.</p><p>Вредонос — тот же тулкит Mini Shai-Hulud, что три недели назад атаковал SAP: одинаковая архитектура сканера, те же регулярные выражения для поиска учётных данных.</p><p>Payload объёмом 498 КБ крадёт учётные данные AWS (включая EC2 metadata и ECS), GitHub PAT, токены npm, SSH-ключи, данные 1Password и Bitwarden.</p><p>Двойная эксфильтрация: через публичные GitHub-репозитории с зашифрованными Git-объектами и HTTPS-запросы к t.m-kosche[.]com под видом OpenTelemetry-трейсов.</p><p>Вредонос перехватывает AI-агентов: внедряет SessionStart-хуки в Claude Code и Codex, а также задачи runOn:folderOpen в VS Code.</p><h2>Масштаб атаки</h2><p>Аккаунт atool на npm поддерживает 547 пакетов. Атакующий <a href="https://safedep.io/mini-shai-hulud-strikes-again-314-npm-packages-compromised/">опубликовал</a> вредоносные версии двумя волнами: первая — с 01:39 до 01:56 UTC (~317 версий), вторая — с 02:05 до 02:06 UTC (~314 версий). В результате 309 пакетов получили по два заражённых релиза каждый.</p><ul><li><b>size-sensor</b> — 4,2 млн загрузок/мес</li><li><b>echarts-for-react</b> — 3,8 млн загрузок/мес</li><li><b>@antv/scale</b> — 2,2 млн загрузок/мес</li><li><b>timeago.js</b> — 1,15 млн загрузок/мес</li><li><b>@antv/x6</b>, <b>@antv/g</b>, <b>@antv/g6</b> и более 250 других пакетов @antv</li></ul><p>Важно: атакующий не переставил тег latest — но это не защищает. npm выбирает наибольшую версию, удовлетворяющую semver-диапазону, независимо от тега. Проект с "echarts-for-react": "^3.0.6" при следующей чистой установке получит вредоносную 3.2.7.</p><h2>Как работает вредонос</h2><p>498-килобайтный обфусцированный Bun-скрипт запускается через preinstall-хук. Дополнительно 630 из 637 версий добавляют в optionalDependencies ссылку на «сиротские» коммиты в репозитории <b>antvis/G2</b> на GitHub с поддельным авторством. Этот механизм позволяет доставить payload даже при блокировке preinstall-хуков: npm резолвит github:-зависимости по SHA без проверки истории ветки.</p><p>Скрипт собирает учётные данные по всей цепочке AWS: переменные окружения, файлы конфигурации, EC2 Instance Metadata Service (169.254.169.254), ECS container metadata (169.254.170.2), Secrets Manager. Помимо этого — токены Kubernetes, HashiCorp Vault, GitHub PAT, токены npm, SSH-ключи и хранилища паролей: 1Password, Bitwarden, pass, gopass.</p><p>Эксфильтрация идёт двумя параллельными каналами: украденные данные коммитятся как Git-объекты в публичные GitHub-репозитории с названиями в формате Dune-вселенной (<i>sardaukar-sandworm-42</i>, <i>fremen-stillsuit-7</i> и т.п.), а также отправляются RSA+AES-зашифрованными POST-запросами к t.m-kosche[.]com — замаскированными под данные OpenTelemetry.</p><p>Для закрепления в системе вредонос устанавливает systemd-сервис kitty-monitor (или LaunchAgent на macOS), который каждый час опрашивает GitHub Commit Search API в поиске RSA-PSS-подписанных команд по ключевому слову firedalazer. Это классический GitHub dead-drop C2-бэкдор.</p><h2>Связь с атакой на SAP</h2><p>Исследователи SafeDep идентифицировали payload как <b>Mini Shai-Hulud</b> — тот же тулкит, который три недели назад был использован в компрометации SAP. Совпадают архитектура сканера, набор регулярных выражений для поиска учётных данных и паттерны обфускации. Название отсылает к Дюне Фрэнка Герберта: Shai-Hulud — гигантский песчаный червь, пожирающий всё на своём пути.</p><h2>Что делать</h2><ol><li>Проверить package-lock.json или yarn.lock — найти версии из списка скомпрометированных пакетов (полный список в источнике).</li><li>Удалить node_modules и переустановить зависимости после того, как npm отзовёт вредоносные версии.</li><li>Проверить наличие kitty-monitor.service (Linux) или com.user.kitty-monitor.plist (macOS).</li><li>Проверить файл ~/.claude/settings.json — не должно быть SessionStart-хуков, запускающих node .claude/setup.mjs.</li><li>Ротировать все учётные данные: AWS IAM, GitHub PAT, npm-токены, SSH-ключи — если машина устанавливала заражённые пакеты.</li></ol><h2>Индикаторы компрометации</h2><p>Источник: <a href="https://safedep.io/mini-shai-hulud-strikes-again-314-npm-packages-compromised/">SafeDep — Mini Shai-Hulud Strikes Again: 317 npm Packages Compromised</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Vite+: один CLI вместо разрозненного стека — полный гайд</title>
      <link>https://tproger.ru/articles/vite-odin-cli-vmesto-pyati-instrumentov-polnyj-gajd</link>
      <comments>https://tproger.ru/articles/vite-odin-cli-vmesto-pyati-instrumentov-polnyj-gajd?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/vite-odin-cli-vmesto-pyati-instrumentov-polnyj-gajd</guid>
      <description><![CDATA[<p>Полный гайд по Vite+ — CLI от VoidZero, объединяющему Vite, Oxlint, Oxfmt и Vitest. Установка, конфигурация и миграция проекта. Проверьте, подходит ли вам.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/vite-odin-cli-vmesto-pyati-instrumentov-polnyj-gajd">Vite+: один CLI вместо разрозненного стека — полный гайд</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 18 May 2026 07:39:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Современный JavaScript-проект обычно собирает инструменты по одному: Vite для сборки, ESLint для линтинга, Prettier для форматирования, Vitest для тестов, NVM для версий Node.js — и отдельные скрипты, которые всё это склеивают. Это работает, но превращается в отдельную точку обслуживания. Каждый инструмент живёт в своём конфиг-файле, с собственными релизами и режимами отказа. Vite+ предлагает выход: один бинарник vp, который заменяет большую часть этого стека.</p><p><b>Vite+</b> — бесплатный open-source CLI от VoidZero, объединяющий Vite, Vitest, Oxlint, Oxfmt, Rolldown, tsdown, Vite Task и менеджмент версий Node.js в одном инструменте.</p><p>Vite+ объединяет 7+ инструментов (Vite, Vitest, Oxlint, Oxfmt, Rolldown, Vite Task, vp env) под единым CLI-бинарником vp.</p><p>Требует Node.js 20.19+ или 22.12+. При более старой версии обновление обязательно.</p><p>Для новых Vite-проектов — удобен сразу. Для крупных production-кодовых баз с кастомными ESLint-плагинами — тестировать осторожно.</p><p>Единая точка конфигурации: vite.config.ts вместо .eslintrc, .prettierrc, vitest.config.ts и .nvmrc.</p><p>Команда vp check запускает форматирование, линтинг и проверку типов за один шаг.</p><h2>Что входит в Vite+</h2><p>Vite+ спроектирован так, чтобы один CLI заменил набор привычных команд. Вот что он консолидирует:</p><ul><li><b>Vite</b> — дев-сервер и production-сборка через vp dev / vp build</li><li><b>Rolldown</b> — bundler следующего поколения, используемый Vite 8 вместо Rollup</li><li><b>Oxlint</b> — быстрый линтер для JS/TS, совместимый с большинством ESLint-правил</li><li><b>Oxfmt</b> — форматтер кода, заменяющий Prettier для большинства проектов</li><li><b>Vitest</b> — тесты, интегрированные с Vite-экосистемой</li><li><b>Vite Task</b> — запуск задач в монорепозитории с кэшированием (альтернатива Turborepo)</li><li><b>vp env</b> — управление версиями Node.js без NVM или FNM</li></ul><p>Ключевое преимущество — не только скорость, но и согласованность: меньше конфиг-файлов, меньше точек, в которых что-то может разъехаться.</p><h2>Когда Vite+ имеет смысл</h2><p>Vite+ подходит не для каждой ситуации. Вот ориентир:</p><ul><li><b>Используйте</b>, если начинаете новый Vite-проект — меньше конфигурации с самого старта</li><li><b>Используйте</b>, если хотите единую команду для форматирования, линтинга и проверки типов</li><li><b>Используйте</b>, если нужно pin-нуть Node.js-версию в проекте без NVM/FNM</li><li><b>Осторожно</b>, если production-кодовая база зависит от нишевых ESLint-плагинов или кастомных правил</li><li><b>Осторожно</b>, если в монорепозитории уже настроен Turborepo или Nx</li><li><b>Осторожно</b>, если поведение Prettier жёстко зафиксировано и команда не готова к разнице в форматировании</li></ul><h2>Что нужно для начала</h2><p>Важная версионная оговорка: <b>Vite 8 требует Node.js 20.19+ или 22.12+</b>. Если на машине установлена более старая версия — обновите перед установкой Vite+.</p><p>Для работы с этим гайдом понадобится:</p><ul><li>Node.js 20.19+ или 22.12+</li><li>Базовое знакомство с командной строкой</li><li>Терминал на macOS, Linux или Windows</li><li>Существующий Vite-проект, если хотите протестировать миграцию</li></ul><p>ESLint, Prettier, Vitest, NVM или FNM глобально устанавливать не нужно — Vite+ берёт это на себя.</p><h2>Установка Vite+</h2><p>Vite+ устанавливается как единый бинарник vp. На macOS или Linux: Официальный сайт проекта: <a href="https://vite.plus">vite.plus</a>.</p><p>На Windows в PowerShell:</p><p>После установки перезапустите терминал и проверьте CLI:</p><h2>Создание проекта</h2><p>Для создания нового проекта используется команда vp create. Интерактивные подсказки предложат выбрать фреймворк, шаблон и пакетный менеджер:</p><p>После создания проекта переходим в директорию и запускаем дев-сервер:</p><p>Команда vp dev делает то же, что npm run dev, pnpm dev или yarn dev — только теперь команда единая для всей команды, независимо от пакетного менеджера.</p><h2>Конфигурация в vite.config.ts</h2><p>Вся конфигурация Vite+ собирается в одном файле — vite.config.ts. Вместо разбросанных .eslintrc, .prettierrc, vitest.config.ts и .nvmrc — один типизированный файл. Базовая конфигурация выглядит так:</p><h3>Форматирование с Oxfmt</h3><p>Добавьте секцию fmt для настройки форматирования:</p><p>Запуск форматирования: vp fmt. Для большинства проектов Oxfmt заменяет Prettier — но перед миграцией production-кодовой базы прогоните его на отдельной ветке и проверьте diff: Oxfmt может форматировать иначе в отдельных случаях.</p><h3>Линтинг с Oxlint</h3><p>Oxlint работает быстро и поддерживает большинство ESLint-совместимых правил. Главное ограничение — совместимость плагинов. Если проект зависит от нишевых ESLint-плагинов или кастомных правил, перед полной заменой ESLint нужна тщательная проверка.</p><h3>Тесты с Vitest</h3><p>Запуск тестов: vp test. Vitest хорошо интегрируется с Vite-экосистемой и переиспользует ту же ментальную модель и инструментарий.</p><h2>Единая проверка с vp check</h2><p>После настройки форматирования, линтинга и тестов — запустите единую проверку качества:</p><p>Команда прогоняет форматирование, линтинг и проверку типов за один вызов. Если введена ошибка типа:</p><p>Vite+ сообщит о проблеме в терминале. Для автоматического исправления того, что можно исправить автоматически:</p><p>Важная оговорка: --fix не чинит всё подряд. Форматирование и часть lint-проблем — да, ошибки типов и логические баги — нет. Это инструмент автоисправления, а не замена ревью кода.</p><h2>Проверка staged-файлов перед коммитом</h2><p>В традиционном стеке для pre-commit проверок используют Husky и lint-staged. Vite+ консолидирует часть этого рабочего процесса через конфигурацию staged:</p><p>Vite+ предоставляет команду и конфигурацию staged-файлов — git hook при этом нужно настроить самостоятельно (Vite+ не делает это автоматически). После настройки это предотвращает попадание проблем с форматированием и типами в основную ветку.</p><h2>Миграция существующего Vite-проекта</h2><p>Для уже существующего Vite-проекта есть команда миграции:</p><p>Она переносит конфигурацию Oxlint, Oxfmt и lint-staged в vite.config.ts. Важно: не воспринимайте это как финальный шаг. Миграция — начало ревью, а не конец. Рекомендуемый порядок:</p><ol><li>Запустить vp migrate</li><li>Запустить vp check</li><li>Проверить изменения через git diff</li><li>Просмотреть сгенерированный конфиг, изменения в package.json и diff форматирования</li><li>Только после ревью — мёржить</li></ol><h2>Управление версиями Node.js через vp env</h2><p>Vite+ включает менеджмент версий Node.js через vp env. Зафиксировать конкретную версию для проекта:</p><p>Это гарантирует одинаковую версию рантайма у всех разработчиков и снижает риск environment-специфичных багов, особенно когда требования Vite к Node.js меняются между мажорными версиями. Можно заменить .nvmrc или FNM для команд, хотящих управлять всем через Vite+.</p><h2>Vite+ против традиционного инструментария</h2><p>Вот как Vite+ соотносится с привычным стеком по ключевым задачам:</p><ul><li><b>Дев-сервер</b>: npm run dev → vp dev</li><li><b>Production-сборка</b>: Vite + Rollup-конфиг → vp build</li><li><b>Форматирование</b>: Prettier + .prettierrc → Oxfmt через vite.config.ts</li><li><b>Линтинг</b>: ESLint + конфиг плагинов → Oxlint через vite.config.ts</li><li><b>Тесты</b>: отдельный vitest.config.ts → vp test</li><li><b>Проверка качества</b>: несколько package.json-скриптов → vp check</li><li><b>Версия Node.js</b>: .nvmrc или FNM → vp env pin</li><li><b>Staged-проверки</b>: Husky + lint-staged → staged-конфиг Vite+ с ручной настройкой hook'а</li><li><b>Задачи монорепозитория</b>: Turborepo/Nx → vp run через Vite Task</li></ul><h2>Текущие ограничения</h2><p>Vite+ перспективен, но не является универсальной заменой для всех JavaScript-проектов. Перед широким внедрением важно учесть:</p><ul><li><b>Зрелость экосистемы</b> — Vite+ новее инструментов, которые он консолидирует</li><li><b>Совместимость ESLint</b> — Oxlint поддерживает большинство правил, но не каждый плагин и кастомное правило</li><li><b>Форматирование</b> — Oxfmt может давать не полностью идентичный Prettier вывод в отдельных случаях</li><li><b>Фреймворк-специфичность</b> — Vite+ наиболее силён в Vite-экосистеме; framework-специфичное поведение нужно тестировать</li><li><b>Размер установки</b> — Vite 8 тяжелее Vite 7 из-за lightningcss и Rolldown</li><li><b>Изменения рабочего процесса</b> — замена привычных скриптов на vp требует документирования и адаптации команды</li></ul><blockquote>Vite+ — не волшебная замена каждого JavaScript-инструмента. Это серьёзная попытка сократить фрагментацию frontend-инструментария, сделав Vite точкой входа в более интегрированный рабочий процесс разработки.</blockquote><h2>Выводы</h2><p>Vite+ делает практическую ставку: JavaScript-инструментарий легче поддерживать, когда бандлер, линтер, форматтер, тест-ранер, task runner и менеджер рантайма спроектированы работать вместе. Для новых Vite-проектов это сразу даёт меньше конфиг-файлов, меньшую поверхность команд и единый vp check для стандартных проверок.</p><p>Для крупных production-приложений лучший подход — постепенное внедрение: протестировать на ветке, проверить diff форматирования, убедиться в покрытии линтинга, проверить поведение CI. Инструмент не ставит точку в эволюции frontend-инструментария — он указывает направление: меньше фрагментации, больше интеграции.</p><p>Источник: <a href="https://blog.logrocket.com/vite-plus-guide-cli-javascript-tooling/">Vite+ guide: One CLI for JavaScript tooling</a> — LogRocket Blog, Emmanuel John, 12 мая 2026 г. Репозиторий Vite+: <a href="https://github.com/voidzero-dev/vite-plus">github.com/voidzero-dev/vite-plus</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Атака на npm и PyPI: 404 вредоносные версии в TanStack, Mistral AI, UiPath за пять часов</title>
      <link>https://tproger.ru/news/ataka-na-npm-i-pypi-404-vredonosnye-versii-v-tanstack-mistral</link>
      <comments>https://tproger.ru/news/ataka-na-npm-i-pypi-404-vredonosnye-versii-v-tanstack-mistral?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/ataka-na-npm-i-pypi-404-vredonosnye-versii-v-tanstack-mistral</guid>
      <description><![CDATA[<p>11 мая 2026 опубликовано 404 вредоносные версии в 170+ npm-пакетах и 2 PyPI. Под ударом TanStack, Mistral, UiPath. Что делать прямо сейчас.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/ataka-na-npm-i-pypi-404-vredonosnye-versii-v-tanstack-mistral">Атака на npm и PyPI: 404 вредоносные версии в TanStack, Mistral AI, UiPath за пять часов</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 12 May 2026 12:11:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если у вас в зависимостях есть что-то из @tanstack/*, @mistralai/* или @uipath/* — проверьте lockfile прямо сейчас. В ночь с 11 на 12 мая 2026 года атакующий опубликовал 404 вредоносных версии в 170+ npm-пакетах и двух PyPI-пакетах за пятичасовое окно. Самое неприятное: payload самовоспроизводится, подкладывая .claude/settings.json и .vscode/tasks.json в репозитории жертв.</p><p>Это <a href="https://safedep.io/mass-npm-supply-chain-attack-tanstack-mistral/">одна из крупнейших координированных атак на реестры пакетов в 2026 году</a>, и первая, которая в одной кампании задела сразу и npm, и PyPI. StepSecurity и Socket трекают её как «mini-shai-hulud». Разбираемся, что произошло, кто пострадал и что делать.</p><ul><li>404 вредоносные версии в 170+ npm-пакетах и 2 PyPI-пакетах, опубликованы в течение пятичасового окна 11 мая.</li><li>Под удар попали: @tanstack (42 пакета роутера для React, Vue, Solid), @mistralai (3 SDK), @uipath (65 пакетов автоматизации), @opensearch-project/opensearch (1,3 млн загрузок в неделю), Guardrails AI.</li><li>Два механизма триггера в npm: preinstall-хук (@mistralai) и optionalDependency с prepare-скриптом (@tanstack). На PyPI — инжекция в __init__.py, срабатывает при импорте, а не при установке.</li><li>Payload крадёт GitHub-токены, npm-токены, AWS IAM-credentials и HashiCorp Vault-credentials. Эксфильтрация — через onion-routed мессенджер Session, поэтому C2-домен заблокировать нельзя.</li><li>Самораспространение: payload использует украденный GitHub-токен, чтобы закоммитить .claude/settings.json и .vscode/tasks.json в feature-ветки чужих репозиториев через GraphQL-мутацию createCommitOnBranch.</li><li>Безопасные версии: mistralai ≤ 2.4.5, guardrails-ai ≤ 0.10.0. По npm — пиньте версии и регенерируйте lockfile.</li></ul><h2>Что произошло</h2><p>В ночь с 11 на 12 мая 2026 года автоматическая система детекта SafeDep засекла массовый burst странных публикаций на npm. Атакующий действовал не точечно, как в случае с axios в марте 2026, а целыми scope-ами: внутри одного scope падали сразу все пакеты. К утру стали известны границы кампании — 170 npm-пакетов, 404 вредоносные версии, плюс 2 PyPI-пакета.</p><h3>Кого затронуло</h3><ul><li><b>TanStack</b> (42 пакета, 84 версии): целый экосистем роутера — @tanstack/react-router, @tanstack/vue-router, @tanstack/solid-router, их devtools, SSR-плагины и build-инструменты. У @tanstack/react-router — 3 миллиона загрузок в неделю.</li><li><b>Mistral AI</b> (3 пакета, 9 версий): core SDK @mistralai/mistralai и его обёртки под Azure и GCP. По три вредоносные версии на каждый.</li><li><b>UiPath</b> (65 пакетов, 65 версий): весь scope @uipath — SDK агентов, оркестратор, инструменты RPA, пакеты для интеграции.</li><li><b>OpenSearch</b> (@opensearch-project/opensearch): официальный JavaScript-клиент, 4 затронутые версии — 3.5.3, 3.6.2, 3.7.0, 3.8.0. 1,3 миллиона загрузок в неделю.</li><li><b>Guardrails AI</b> (guardrails-ai==0.10.1 на PyPI): фреймворк validation guardrails для Python.</li><li><b>Mistral AI на PyPI</b> (mistralai==2.4.6): официальный SDK; легитимного релиза 2.4.6 не существовало, последняя версия — 2.4.5 от 7 мая.</li></ul><h2>Как работает атака</h2><h3>На npm — два механизма триггера</h3><p>Для <b>Mistral AI</b> атакующий заменил блок scripts в package.json на preinstall-хук. Этот хук скачивает Bun runtime и запускает payload setup.mjs, который, в свою очередь, запускает основной обфусцированный бинарь router_init.js.</p><p>Для <b>TanStack</b> атакующий пошёл хитрее. В легитимный package.json добавлен optionalDependency, указывающий на вредоносный коммит в реальном репозитории tanstack/router на GitHub (коммит #79ac49eedf774dd4b0cfa308722bc463cfe5885c). В этом коммите лежит prepare-скрипт, который снова тянет Bun и запускает тот же payload.</p><p>Преимущество атакующего: коммит в публичном репозитории, который ссылается из optionalDependency, не вызывает алертов автоматических сканеров — внешне это легитимная зависимость.</p><h3>На PyPI — инжекция в __init__.py</h3><p>PyPI устроен иначе: sandboxed install через pip download и pip wheel не выполняет код пакета. Поэтому атакующий не стал прятать payload в install-хуке. Вместо этого в __init__.py добавили 15 строк, срабатывающих при первом import:</p><p>Никакой обфускации: URL, путь и команда выполнения — открытым текстом. Проверка sys.platform запускает dropper только на Linux. macOS- и Windows-сборки несут трояны, но dropper там не сработает. Cloudflare помечает домен git-tanstack.com как phishing-сайт.</p><h2>Что крадёт payload</h2><p>Внутри обфусцированного router_init.js — модульный credential stealer с дедикейтными провайдерами под разные источники секретов:</p><ul><li>GitHub PAT, OAuth и App-installation токены: паттерны ghp_*, gho_*, ghs_*.</li><li>GitHub Actions OIDC JWT — формата ghs_NNN_xxx.yyy.zzz.</li><li>npm publish tokens (npm_*).</li><li>AWS IAM credentials через зондирование metadata-эндпоинта 169.254.169.254/latest/meta-data/iam/security-credentials/.</li><li>HashiCorp Vault — пробу на localhost:8200.</li></ul><p>Сканер регулярок прогоняется по содержимому переменных окружения, файлам конфигурации и истории shell. Цель — максимизировать lateral movement из CI/CD-окружений и dev-машин в публичное облако.</p><h2>Самораспространение через Claude Code и VS Code</h2><p>Самая интересная и пугающая часть payload — self-replication механизм. Имея украденный GitHub-токен, payload использует GraphQL-мутацию createCommitOnBranch, чтобы закоммитить вредоносные файлы в репозитории жертвы:</p><ul><li>.claude/settings.json и .claude/setup.mjs — конфиг для Claude Code, запускающий payload при открытии проекта.</li><li>.vscode/tasks.json и .vscode/setup.mjs — то же самое для VS Code.</li><li>.claude/router_runtime.js — полная копия 2,2-мегабайтного payload, чтобы следующая стадия не зависела от внешнего хоста.</li></ul><p>Цели коммитов выбираются хитро: payload запрашивает до 50 веток через GraphQL и отфильтровывает main, master, develop и release (типичный набор). Коммиты идут в feature- и topic-ветки, где меньше шансов нарваться на ревью. В коммит-сообщении добавляется случайный Co-authored-by, чтобы выглядеть «коллективно».</p><p>Итог цепочки: любой разработчик, который git pull заражённую ветку и откроет проект в VS Code или Claude Code, запустит payload без явного действия. Затем его токены крадутся — и цикл повторяется.</p><h2>Эксфильтрация через Session — почему домен не закрыть</h2><p>Атакующий не использовал классический C2-домен. Вместо этого payload содержит полную реализацию клиента <a href="https://getsession.org/">Session</a> — onion-routed мессенджера на сети Oxen. Payload связывается с pre-pinned seed-нодами (seed1.getsession.org, seed2.getsession.org, seed3.getsession.org), получает список snode-ов и отправляет украденные креды через peer-to-peer-сеть.</p><p>Для больших данных payload использует filev2.getsession.org/file/ — централизованный файловый сервер Session. Это единственная фиксированная точка инфраструктуры; всё остальное — динамическое peer-to-peer-разрешение через swarm. Защитники не могут отозвать swarm так, как отзывают домен.</p><blockquote>Шифрование на ed25519 и x25519, перебор snode-адресов в рантайме — блокировка C2 на уровне DNS или firewall здесь не работает.</blockquote><h2>Что делать прямо сейчас</h2><h3>Для npm-проектов</h3><p>Проверьте lockfile на наличие вредоносных версий по затронутым scope-ам:</p><p>Если что-то найдено — запиньте пакеты на известные безопасные версии и перегенерируйте lockfile. После этого ротейтьте все секреты, к которым имела доступ ваша dev-машина или CI: GitHub PAT, npm-токены, AWS-ключи, Vault-токены.</p><h3>Для Python-проектов</h3><p>Проверьте установленные версии:</p><p>Если у вас mistralai==2.4.6 или guardrails-ai==0.10.1 — окружение скомпрометировано. Безопасные версии: mistralai ≤ 2.4.5 и guardrails-ai ≤ 0.10.0.</p><h3>Дополнительная гигиена</h3><ul><li>Проверьте feature- и topic-ветки своих репозиториев на свежие коммиты с подложенными файлами в .claude/ и .vscode/ — особенно если у вас под рукой Claude Code или VS Code с активным GitHub-токеном.</li><li>Запретите автоматический запуск preinstall- и postinstall-скриптов через флаг --ignore-scripts.</li><li>Просканируйте репозитории на наличие .claude/router_runtime.js — это копия payload, размер около 2,2 МБ.</li></ul><h2>Indicators of Compromise (IoC)</h2><h2>FAQ</h2><h2>Выводы</h2><p>Эта атака подсветила два архитектурных риска, которые мы коллективно проигнорировали. Первый — preinstall/postinstall-скрипты в npm по дефолту запускаются от лица пользователя; одно зависимое дерево из 100 пакетов даёт атакующему 100 шансов запуститься на dev-машине. Второй — IDE-конфиги в репозиториях. Удобство «открыл проект и сразу работает» оборачивается атакующим прямо тем же удобством: пушнул .claude/setup.mjs в feature-ветку — и payload запускается у каждого, кто склонирует ветку.</p><p>Защита здесь — это --ignore-scripts в npm, изолированные среды для install (контейнеры, devcontainers), review-обязательность для всех веток, отслеживание <i>незапланированных</i> .claude/- и .vscode/-файлов в pull-requests.</p><p>Полный технический разбор с дизассемблированным payload, swarm-логикой и appendix-ом со всеми пакетами — <a href="https://safedep.io/mass-npm-supply-chain-attack-tanstack-mistral/">в посте SafeDep</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Cloudflare переписала Next.js за $1 100: как ИИ сломал модель коммерческого open source</title>
      <link>https://tproger.ru/translations/cloudflare-perepisala-next-js-za-1-100-kak-ii-slomal-model-ko</link>
      <comments>https://tproger.ru/translations/cloudflare-perepisala-next-js-za-1-100-kak-ii-slomal-model-ko?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/cloudflare-perepisala-next-js-za-1-100-kak-ii-slomal-model-ko</guid>
      <description><![CDATA[<p>Cloudflare за неделю создала vinext — замену Next.js на Vite. Один инженер и $1 100 на токены ИИ. Разбираем технику vinext и последствия для open source.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/cloudflare-perepisala-next-js-za-1-100-kak-ii-slomal-model-ko">Cloudflare переписала Next.js за $1 100: как ИИ сломал модель коммерческого open source</a>»</p>]]></description>
      <category><![CDATA[Cloudflare]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 10 May 2026 06:02:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Cloudflare потратила одну рабочую неделю и $1 100 на токены ИИ, чтобы переписать Next.js — один из самых сложных web-фреймворков с десятилетней историей. Инструмент получил название vinext, работает на Vite вместо проприетарного Turbopack и разворачивается в Cloudflare Workers одной командой. Этот эксперимент ставит под вопрос фундамент, на котором стоят коммерческие open source-компании.</p><p>Разбираем ситуацию по материалу <a href="https://blog.pragmaticengineer.com/the-pulse-cloudflare-rewrites-next-js-as-ai-rewrites-commercial-open-source/">Pragmatic Engineer</a> — охватывает технические подробности vinext, экономику и последствия для коммерческого open source.</p><p>Cloudflare выпустила <b>vinext</b> — замену Next.js на базе Vite, которая деплоится в Cloudflare Workers одной командой.</p><p>Один инженер + ИИ-агент OpenCode + Opus 4.5 = одна рабочая неделя вместо <b>нескольких лет</b> инженерного труда.</p><p>По заявлению Cloudflare: сборка до <b>4 раз быстрее</b>, клиентские бандлы на <b>57% меньше</b>.</p><p>vinext покрывает <b>94% API Next.js</b>, но официально экспериментальна: не прошла нагрузочного тестирования.</p><p>ИИ удешевил переписывание сложного ПО примерно в <b>100 раз</b> — под угрозой оказались проприетарные «рвы» коммерческих open source-проектов.</p><p>Vercel ($9 млрд оценка) потеряла ключевое конкурентное преимущество: уникальный формат билда Next.js.</p><h2>Next.js и Vercel: как устроен коммерческий «ров»</h2><p>Next.js — самый популярный полностековый React-фреймворк. По данным Stack Overflow Developer Survey 2025, его используют около половины разработчиков на React. Проект открытый, но содержится преимущественно силами <a href="https://vercel.com">Vercel</a>.</p><p>Хитрость в том, что Next.js собирает проекты с помощью <b>Turbopack</b> — опционального инструмента Vercel, написанного на Rust (используется прежде всего в dev-режиме). Результат сборки — проприетарный, недокументированный формат. Инженер Netlify Эдуардо Боукаш объяснял это так:</p><blockquote>Формат вывода сборки Next.js является проприетарным и недокументированным — он используется в деплоях Vercel для инициализации нужной инфраструктуры. Это означает, что любые другие хостинг-провайдеры вынуждены опираться на недокументированные API, которые могут менять поведение без предупреждения в минорных и патч-релизах.</blockquote><p>В итоге сложилась система: Next.js бесплатен и открыт, но наилучший опыт разворачивания — только на Vercel. Альтернативные провайдеры вынуждены угадывать поведение недокументированного формата. Это умная стратегия, превращающая open source-проект в воронку монетизации.</p><p>Чтобы сломать эту схему, нужно заменить Turbopack на стандартный инструмент — например, <b>Vite</b>, который лидирует в экосистеме JS по данным State of JS 2025. Именно это и сделал Cloudflare.</p><h2>Что такое vinext и как его создали</h2><p>Идея проста: убрать Turbopack, поставить Vite, сделать так, чтобы приложения на Next.js собирались в стандартный формат и деплоились на любой платформе — в том числе на Cloudflare Workers.</p><p>Cloudflare публично заявила, что на это ушла одна рабочая неделя одного инженера, который использовал агент <a href="https://opencode.ai">OpenCode</a> (open source coding agent) совместно с моделью Claude Opus 4 (в источнике — «Claude Opus 4 (в источнике — «Opus 4.5»)»; такой модели в публичном портфеле Anthropic нет, по всей видимости имеется в виду Opus 4):</p><blockquote>На прошлой неделе один инженер и модель ИИ с нуля пересобрали самый популярный front-end фреймворк. Результат — vinext (произносится «ви-некст»): drop-in замена Next.js на Vite, которая деплоится в Cloudflare Workers одной командой. В ранних бенчмарках сборка production-приложений ускорилась до 4 раз, а клиентские бандлы уменьшились до 57%. И у нас уже есть клиенты, которые запустили его в production. Вся работа обошлась примерно в $1 100 на токены.</blockquote><p>Ядро Next.js за 10 лет выросло примерно до 194 000 строк кода. vinext занимает около 67 000 строк — более компактная реализация, которая не поддерживает устаревшие API и охватывает 94% публичного API Next.js. Оставшиеся 6% — сложные граничные случаи.</p><h3>Что именно сделал Cloudflare</h3><ul><li>Взял публичный API Next.js</li><li>Переписал поведение через Vite</li><li>Создал формат вывода сборки, совместимый с поведением оригинала</li><li>Добавил <b>Agent Skill</b> для миграции существующих Next.js-проектов одной командой</li></ul><p>Миграционный скилл — отдельная деталь, показательная для эпохи ИИ. Он работает с Claude Code, OpenCode, Cursor, Codex и другими агентами:</p><p>Cloudflare не только использовала ИИ для создания vinext, но и встроила ИИ в процесс его распространения — чтобы миграция клиентских проектов тоже была автоматической.</p><h2>ИИ делает «невозможное» тривиальным</h2><p>До появления современных ИИ-агентов переписать Next.js «с нуля» было теоретически возможным, но практически исключённым. Это требовало бы многолетних усилий команды инженеров. При этом сообщество сомневалось бы в долгосрочной поддержке любого форка — Vercel доказывала свою надёжность 10 лет, а любой новый игрок не имеет этого кредита доверия.</p><p>Теперь ситуация изменилась. По оценке Pragmatic Engineer, ИИ ускорил создание vinext примерно в <b>100 раз</b>. Cloudflare завершила проект, измеряемый в инженерных годах, за одну инженерную неделю.</p><p>Важно, что всеобщий рост продуктивности от ИИ в среднем куда скромнее. По данным The Pragmatic Summit (Сан-Франциско, 2026), самооценка разработчиков даёт около 10% прироста. Ключевое условие для 100-кратного ускорения — наличие <b>полного тестового покрытия</b>, которое позволяет ИИ-агентам верифицировать каждый шаг.</p><blockquote>ИИ во много раз эффективнее на «механических» задачах, где корректность можно проверить тестами, по сравнению с открытыми задачами или теми, что требуют творчества.</blockquote><p>Сам факт, что Next.js имеет исчерпывающее тестовое покрытие — это одновременно его сила и слабость: ИИ использовал тест-сюит как спецификацию для переписывания. Cloudflare даже поблагодарила команду Vercel в объявлении:</p><blockquote>Мы хотим отметить команду Next.js. То, что их API хорошо задокументирован, а тест-сюит настолько полный, — один из главных факторов, которые сделали этот проект возможным.</blockquote><h2>Качество под вопросом: экспериментальность vs маркетинг</h2><p>Cloudflare открыла своё объявление сильным тезисом: «клиенты уже запустили в production». Vercel немедленно указала на уязвимости в безопасности, а CEO Гильермо Рауч связал проект со стереотипом «вайб-кодинга» — небрежной работы без понимания деталей.</p><p>Претензия оказалась обоснованной: важная деталь про «production» была закопана в тысяче слов после начала объявления:</p><blockquote>Мы хотим быть честными: vinext экспериментален. Ему ещё нет недели, и он не прошёл реального нагрузочного тестирования. (...) Мы работаем с National Design Studio на одном из их <b>бета-сайтов</b>, CIO.gov.</blockquote><p>«Клиент в production» у Cloudflare — это бета-сайт без значимого трафика. Это нетипично для компании, обычно отличающейся точностью формулировок. Vercel имела право поднять вопрос безопасности.</p><p>Тем не менее это не отменяет главного вывода: ИИ способен снизить стоимость разработки примерно в 100 раз и выдать работоспособный результат за приемлемую сумму. Доработка безопасности и надёжности потребует дополнительного времени — но это уже другой разговор.</p><h2>Новая угроза для коммерческого open source</h2><p>Cloudflare и Vercel — известные соперники в борьбе за платформу разработчиков. CEO обеих компаний регулярно обмениваются ударами в публичном пространстве.</p><p>Но реальная ставка здесь — бизнес-модель коммерческого open source. Стратегия Vercel была классической:</p><ol><li>Создать и поддерживать Next.js, обеспечив лучший developer experience.</li><li>Оптимизировать Vercel под специфический (и недокументированный) формат вывода Next.js.</li><li>Большинство разработчиков, выбравших Next.js, деплоят на Vercel — ради лучшей интеграции.</li><li>Повторять годами, пока бизнес не оценят в $9 млрд (оценка октября 2025 года).</li></ol><p>В основе стратегии лежали два предположения: (1) переписать Next.js дорого и (2) даже если кто-то это сделает, разработчики усомнятся в жизнеспособности альтернативы. ИИ обнулил оба предположения.</p><h3>Аналогия с WordPress и WP Engine</h3><p>Похожая история произошла с WordPress и WP Engine в 2024 году. WP Engine «пиратила» усилия Automattic: почти не вкладывалась в R&amp;D, зато продавала WordPress как managed service — дешевле, чем Automattic, тратящая на разработку сотни миллионов.</p><p>Разница в том, что Vercel удавалось избегать «фрирайдеров» — благодаря проприетарному формату билда. Теперь этого барьера больше нет: Cloudflare будет синхронизировать каждое обновление Next.js с vinext через ИИ-агентов, и это не потребует серьёзных инвестиций.</p><h2>Как защититься: стратегии для коммерческих open source-проектов</h2><p>Если ИИ позволяет конкурентам тривиально переписать ваш продукт, каковы варианты защиты?</p><h3>Закрыть тест-сюит</h3><p>Один из очевидных ответов — сделать тесты приватными. SQLite, например, держит свой наиболее полный тест-сюит (TH3) закрытым и продаёт доступ к нему как сервис. Open source-проект для визуального редактирования tldraw объявил о переносе тестов в закрытый репозиторий (хотя потом оказалось, что это была шутка).</p><p>Саймон Виллисон прокомментировал тренд:</p><blockquote>За последние несколько месяцев стало очевидно, что полный тест-сюит достаточен для написания совершенно новой реализации любой open source-библиотеки с нуля — потенциально на другом языке.</blockquote><h3>Другие варианты защиты</h3><ul><li><b>Уменьшить open core, увеличить закрытую часть.</b> Перенести расширенные сервисы из source available в полностью закрытый код.</li><li><b>Профессиональный support.</b> ИИ может повысить качество поддержки при правильном применении — а это сложно скопировать.</li><li><b>Живое сообщество.</b> Meetup'ы и реальная аудитория создают связь, которая выходит за рамки кода. Трудно представить встречи vinext-пользователей — а встречи Next.js-разработчиков уже существуют.</li><li><b>Инфраструктура как ров.</b> В мире, где ПО легко скопировать, владение и управление инфраструктурой становится важнейшим преимуществом: меньшая задержка, выше надёжность, лучшая цена.</li></ul><h2>Что это значит для индустрии</h2><p>Для не-коммерческого open source перспективы выглядят иначе. ИИ упрощает создание и поддержку форков, перенос проектов на другие языки и добавление новых функций. Это может означать расцвет открытых проектов, которые не зависят от коммерческой логики.</p><p>С другой стороны, появится класс «миграционных агентов», которые провайдеры будут создавать для перетягивания клиентов. Cloudflare уже встроила такой агент в vinext. Это «AI-native» стратегия захвата рынка, которую скопируют другие.</p><p>Конкуренция в технологической индустрии становится жёстче и быстрее. По словам Laura Tacho с The Pragmatic Summit:</p><blockquote>ИИ — это ускоритель, мультипликатор, и он движет организации в разных направлениях.</blockquote><p>В частном случае vinext — пока неясно, насколько популярным он станет и насколько глубок «ров» Vercel вокруг экосистемы Next.js в целом. Переписать фреймворк — не то же самое, что стать жизнеспособной платформой-as-a-service. Доверие, стабильность, поддержка и developer experience накапливались 10 лет.</p><h2>Выводы</h2><p>Cloudflare переписала Next.js не из любви к open source, а чтобы разрушить конкурентное преимущество Vercel. Это честная бизнес-война. Но побочный эффект оказался важнее самого конфликта: стало ясно, что ИИ-агенты превращают переписывание сложного ПО из многолетней инвестиции в задачу одной недели.</p><p>Для разработчиков это хорошая новость: экосистема Next.js получила стандартизированный формат сборки, а деплой на разные платформы станет проще. Для компаний, строящих бизнес на коммерческом open source, — сигнал тревоги: проприетарные технические «рвы» больше не защищают так, как раньше.</p><p>Что по-настоящему защищает — живое сообщество, инфраструктура мирового класса, качество поддержки и репутация, накопленная годами. vinext может стать удобной альтернативой деплоя. Но стать полноценной заменой экосистемы Next.js — задача несравнимо более сложная.</p><p>Как вы оцениваете угрозу, которую ИИ создаёт для коммерческого open source? Читайте также: <a href="https://tproger.ru/">другие материалы об ИИ и open source на tproger.ru</a>.</p><p>Источники: <a href="https://blog.pragmaticengineer.com/the-pulse-cloudflare-rewrites-next-js-as-ai-rewrites-commercial-open-source/">Pragmatic Engineer — The Pulse</a>; <a href="https://blog.cloudflare.com/vinext/">Cloudflare блог — объявление vinext</a>; State of JS 2025.</p>]]></content:encoded>
    </item>
    <item>
      <title>Vue без Node: как Julia Evans тестирует компоненты в браузере</title>
      <link>https://tproger.ru/translations/vue-bez-node-kak-julia-evans-testiruet-komponenty-v-brauzere</link>
      <comments>https://tproger.ru/translations/vue-bez-node-kak-julia-evans-testiruet-komponenty-v-brauzere?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/vue-bez-node-kak-julia-evans-testiruet-komponenty-v-brauzere</guid>
      <description><![CDATA[<p>Julia Evans показывает, как тестировать Vue-компоненты прямо в браузере без Node, Deno и сборки. QUnit, mountComponent, waitFor — полный перевод поста.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/vue-bez-node-kak-julia-evans-testiruet-komponenty-v-brauzere">Vue без Node: как Julia Evans тестирует компоненты в браузере</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Vue]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 07 May 2026 14:30:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если у вас pet-проект на Vue, в котором вы боитесь что-то менять, потому что без билда и без тестов любое изменение похоже на лотерею, — рабочий способ обойтись без Node, Deno и сборщика всё-таки есть. Julia Evans (автор «Wizard Zines» и блога jvns.ca) описала минимальный рецепт в посте от 2 мая 2026: компоненты кладутся в window._components, функция mountComponent рендерит их в скрытый div, тесты QUnit запускаются прямо со страницы. Это перевод поста.</p><p>Заметка короткая и практическая. Сначала — краткий вывод, потом полный текст Julia.</p><ul><li><b>Что в посте:</b> рабочий минимум для тестирования Vue-компонентов в браузере без Node, Deno и сборки. Тесты запускаются на отдельной странице через QUnit (тестовый фреймворк, который рендерится прямо в браузере), компоненты экспортируются в window._components, рендерятся через mountComponent в скрытый div вне viewport.</li><li><b>Как ждать DOM:</b> Julia пишет свою функцию waitFor(), которая раз в 20 мс проверяет условие и сдаётся через 2 секунды. Без sleep()-затычек, обычным опросом DOM — паттерн, к которому рано или поздно приходит любая фронтенд-команда.</li><li><b>Заполнение форм требует событий:</b> в Vue недостаточно присвоить value или checked — нужно диспатчить событие (input для текстов, change для чекбоксов), иначе реактивность Vue не отслеживает изменение.</li><li><b>Test coverage — Chrome это умеет из коробки:</b> панель Coverage показывает покрытие JS и CSS-кода. Чтобы хорошо работало, Julia рекомендует выключить sourcemaps в DevTools и смотреть покрытие по бандлу.</li><li><b>Открытые вопросы:</b> как запускать те же тесты в CI, как уйти от привязки к CSS-классам в селекторах (правильнее — getByRole или data-testid), и стоит ли переехать на Testing Library/Vue Test Utils.</li></ul><h2>Контекст: я хочу тестировать без Node</h2><p>Привет! Один из моих долгосрочных проектов — выяснить, как писать frontend-JavaScript без Node и любого другого серверного JS-рантайма.</p><p>Главная проблема, в которую я постоянно упираюсь в своих frontend-проектах: я не знаю, как писать для них тесты. Раньше я пробовала Playwright, но он казался медленным и неуклюжим — всё время приходилось запускать новые browser-процессы, и оркестрация требовала Node-кода.</p><p>В итоге я просто не тестирую свой frontend, и это неприятно. Обычно я и не обновляю проекты особо часто, поэтому болезненность не сильно проявляется, но было бы хорошо вносить изменения с большей уверенностью!</p><p>Так что подходящий способ frontend-тестирования давно лежит у меня в списке желаний.</p><h3>Идея: просто запускать тесты во вкладке браузера</h3><p>Alex Chan когда-то написал отличный пост — «Testing JavaScript without a (third-party) framework» — в ответ на одну из моих предыдущих заметок этой серии. Там он показал, как сделать крошечный фреймворк юнит-тестирования, который запускается прямо со страницы в браузере.</p><p>Мне тогда очень понравился подход, но в посте речь шла только про unit-тесты, а мне нужны были end-to-end интеграционные тесты для моих Vue-компонентов, и я не знала, как это сделать.</p><p>Поэтому, когда на днях знакомый Marco в разговоре сказал «знаешь, ты же можешь просто запускать тесты Vue-компонентов в браузере», я подумала: «эй, надо попробовать ещё раз!»</p><p>Я всё это сделала только вчера, так что наверняка многое можно улучшить, — но хочу записать пару наблюдений по ходу дела, пока не забыла.</p><p>Это было слегка непросто: документация Vue обычно предполагает, что вы используете Node как часть build-процесса (там много «шаг 1: npm install ЧТО-ТО»), а я не хотела использовать Node, Deno и так далее. Но на практике оказалось не очень сложно.</p><p>Проект, который я буду здесь тестировать, — это <a href="https://jvns.ca/blog/2024/06/24/zine-feedback-site/" rel="noopener">сайт фидбэка по моим зинам</a>, который я написала в 2023.</p><h3>Тестовый фреймворк: QUnit</h3><p>Я использовала QUnit — тестовый фреймворк для JavaScript, который умеет запускаться прямо со страницы в браузере без Node-окружения (когда-то именно его использовала команда jQuery). Он отлично работает, но рассказать про его внутреннее устройство мне особо нечего — поэтому ограничусь этим. Думаю, подход Alex с самописным фреймворком тоже сработал бы. Я следовала <a href="https://qunitjs.com/intro/" rel="noopener">официальной инструкции</a>.</p><p>Что я оценила в QUnit — кнопка «rerun test», которая запускает только один конкретный тест. У меня в тестах много network requests, поэтому возможность запустить только один тест сильно облегчает дебаг.</p><h3>Шаг 1: подготовить компонент к тестированию</h3><p>Первое, что я сделала — настроила свои Vue-компоненты для тестового окружения.</p><p>В основном приложении я положила все компоненты в window._components, примерно так:</p><p>Затем смогла написать функцию mountComponent, которая делает то же самое, что мой обычный mount-код в production (рендерит крошечный шаблон с нужным компонентом).</p><p>Отличия только два:</p><ul><li>Можно опционально передать дополнительные данные, чтобы использовать их как props.</li><li>Компонент монтируется во временный невидимый div, который удаляется из DOM по завершении теста. Div позиционирован за пределами viewport (position: absolute; top: -10000, ...), так что его не видно.</li></ul><p>Вот как выглядит вызов mountComponent:</p><p>А вот её код. Здесь qunit-fixture — это служебный div, который QUnit добавляет в тестовую страницу и автоматически очищает после каждого теста (его id зашит в QUnit, нужно только положить &lt;div id="qunit-fixture"&gt;&lt;/div&gt; в HTML тестовой страницы):</p><p>Результат — div, в котором можно программно кликать, заполнять формы, проверять, что появился нужный контент, и так далее. (Примечание переводчика: в оригинальной версии у Julia функция возвращала просто div, но дальше во всех вызовах используется const {div} = mountComponent(...). С return div такая деструктуризация даст undefined — поэтому мы поправили на return {div}. И добавили явный app.mount(div), который тоже опущен в оригинале.)</p><h3>Шаг 2: добавить fixture-данные</h3><p>Поскольку я писала end-to-end интеграционные тесты, в которых клиентский JS должен работать в связке с сервером, в БД нужны были тестовые данные. Я написала ~25 строк SQL для подготовки тестовых данных и добавила endpoint в dev-сервер, который запускает этот SQL и сбрасывает тестовые данные в известное состояние.</p><p>Затем просто вызываю await reset() в начале каждого теста, которому нужны тестовые данные.</p><p>Моя reset() на самом деле не всегда полностью сбрасывает всё — не очень хорошо, но для старта рабочий вариант, и его всегда можно улучшить.</p><h3>Шаг 3: базовый тест</h3><p>Так выглядит базовый тест! По сути мы рендерим div и проверяем, что в нём есть приблизительно правильные данные.</p><p>Это все базовые кирпичики! А теперь — несколько проблем, на которые я наткнулась по ходу.</p><h2>Как ждать рендера: проблема и waitFor()</h2><p>В моих тестах много сетевых запросов, и нужно время, чтобы они завершились, а Vue потом сделал с результатами своё дело и обновил DOM.</p><p>Думаю, мы давно усвоили: вставлять sleep()-затычки и надеяться, что тайминги совпадут, — медленно, нестабильно и доводит до бешенства. Поэтому нужен другой способ.</p><p>Насколько я понимаю, обычный путь — найти способ по DOM понять, можно ли двигаться дальше. Что-то вроде «если эта кнопка видна — значит, можно действовать».</p><p>Поэтому я написала маленькую функцию waitFor(), которая опрашивает условие каждые 20 мс. Таймаут — 2 секунды.</p><p>Версия в посте Julia не показана прямо — приведём свою, минимально работающую (и идейно соответствующую её описанию):</p><p>Использование выглядит так:</p><p>Похоже, существует много реализаций этой идеи, и они продуманы лучше моей (беглый поиск в Google: <a href="https://github.com/dgtlife/qunit-wait-for" rel="noopener">qunit-wait-for</a>, Playwright expect.poll).</p><h2>Понять, чего именно ждать, — нетривиально</h2><p>Иногда мне казалось, что я нашла правильную точку для ожидания в DOM («просто дождись, пока появится этот textarea!»), но на практике из-за внутренних деталей программы нужно было ждать чего-то другого, что было сложно зацепить.</p><p>В итоге я добавила в один компонент произвольное значение в DOM, когда он завершал важное действие (вроде data-this-thing-is-ready=true). Это не очень-то красиво.</p><p>Думаю, правильный способ починить такую проблему теста — рефакторинг, который заодно делает приложение более надёжным для пользователя. Если в DOM есть элемент, с которым пользователю на самом деле ещё нельзя взаимодействовать — может быть, его и не стоит показывать?</p><h2>Добавлять CSS-классы для селекторов? Спорный вопрос</h2><p>В итоге я добавила несколько классов на HTML-элементы — нужно было их находить в тестах, чтобы кликать или ждать появления в DOM.</p><p>Я могу позже изменить этот подход — тестовые фреймворки для frontend обычно советуют избегать CSS-классов и использовать что-то вроде getByRole (выбор по семантической роли элемента, например «кнопка» или «поле формы») или, в крайнем случае, data-testid (специальный data-атрибут, который добавляют исключительно ради тестов и игнорируют в production-стилях).</p><p>Похоже, миграция на getByRole решает обе проблемы сразу: и приложение становится более доступным для скринридеров, и тесты — устойчивее к рефакторингу разметки.</p><h2>Заполнение форм — отдельная боль</h2><p>Чтобы заполнить форму, недостаточно просто выставить value — нужно ещё диспатчить событие, чтобы Vue понял, что элемент изменился. И для checkbox, и для textarea нужны разные события.</p><p>Это слегка раздражает — и заставляет понять, зачем вообще могут понадобиться UI-тестовые библиотеки. Например, Testing Library (набор фреймворк-агностичных утилит для тестирования UI с упором на доступность) и Vue Test Utils (официальная библиотека Vue для unit-тестов компонентов). Их подходы к формам выглядят иначе:</p><ul><li>Пример заполнения формы из <a href="https://testing-library.com/docs/example-input-event/" rel="noopener">Testing Library</a> выглядит совершенно иначе, чем то, что делаю я.</li><li>У <a href="https://test-utils.vuejs.org/guide/essentials/forms" rel="noopener">Vue Test Utils</a> раздел про работу с формами выглядит так, будто сильно упрощает всё это.</li></ul><h2>Test coverage — Chrome это умеет из коробки</h2><p>Мне хотелось понять, какое у меня test coverage, и оказывается, в Chrome есть встроенная функция code coverage для JS и CSS!</p><p>Мой JS собирается в один файл bundle.js через esbuild — поэтому я могу просто посмотреть на bundle.js и увидеть, какие строки не покрыты тестами.</p><p>Процесс получился чуть капризный: пришлось выключить sourcemaps в Chrome DevTools, чтобы заработало, и есть специфическая, не самая очевидная последовательность действий, чтобы увидеть данные покрытия.</p><h2>Это было прикольно</h2><p>Как обычно с такими постами: я никогда особо не работала frontend- или backend-разработчиком (кроме как для себя!) и чувствую, будто постоянно учусь делать совсем базовые штуки.</p><p>Мне реально кайфовалось от этого. Мои frontend-проекты всегда кажутся хрупкими, потому что они без тестов; и, может быть, однажды у меня появится тест-сьют, в котором я буду уверена.</p><p>Что я ещё обдумываю:</p><ul><li>Пока писала пост, нашла frontend-библиотеку Testing Library — у неё много рекомендаций, как писать тесты, которые сильно отличаются от моих первоначальных идей. Я попробовала переписать всё на Testing Library — получилось неплохо, посмотрим, что из этого выйдет. Они распространяют .umd.js файл, который работает без Node.</li><li>Не до конца понимаю, как относиться к тому, что эти тесты никак не запускаются из командной строки. Может быть, есть простой способ работать в основном в браузере, но иметь возможность погонять и в CI, если нужно?</li></ul><h2>Что забрать с собой</h2><blockquote>Мои frontend-проекты всегда кажутся хрупкими, потому что они без тестов. Может быть, однажды у меня появится тест-сьют, в котором я буду уверена.</blockquote><p>Главный вывод поста: для маленьких персональных или библиотечных проектов «тестовая страница в браузере» — совершенно рабочий путь. Не нужно тащить Node, build-сервер и jsdom только ради того, чтобы у вас были тесты. Чек-лист минимальной настройки:</p><ol><li>Положить компоненты в window._components (или другое глобальное пространство имён).</li><li>Подключить QUnit (или другой фреймворк, который умеет запускаться прямо со страницы) в HTML-файл с тестами.</li><li>Написать mountComponent(template, data), которая создаёт временный div и монтирует туда Vue-app.</li><li><b>Опционально</b> (если у компонента есть бэкенд): сделать endpoint /api/reset_test_data на dev-сервере для сброса БД к фикстуре.</li><li>Реализовать waitFor(condition, timeout=2000) для ожидания DOM-условий вместо sleep().</li><li>Для форм диспатчить input (текстовые поля) и change (чекбоксы) после изменения свойств элементов. Для кликов хватит обычного .click() на элементе.</li><li>Для покрытия — Chrome DevTools, вкладка Coverage; sourcemaps выключить.</li></ol><p>Оригинал поста Julia Evans — на <a href="https://jvns.ca/blog/2026/05/02/testing-vue-components-in-the-browser/" rel="noopener">jvns.ca</a>. Альтернативы для тех, кто хочет больше готового: <a href="https://test-utils.vuejs.org/" rel="noopener">Vue Test Utils</a> (через jsdom в Node), <a href="https://testing-library.com/" rel="noopener">Testing Library</a> (с UMD-сборкой работает без Node).</p>]]></content:encoded>
    </item>
    <item>
      <title>30 дней: блочный конструктор README — один DOM, два хозяина</title>
      <link>https://tproger.ru/articles/30-dnej-blochnyj-konstruktor-readme-odin-dom-dva-hozyaina</link>
      <comments>https://tproger.ru/articles/30-dnej-blochnyj-konstruktor-readme-odin-dom-dva-hozyaina?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Islam Mada]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/30-dnej-blochnyj-konstruktor-readme-odin-dom-dva-hozyaina</guid>
      <description><![CDATA[<p>Блочный конструктор README-файлов написанный с нуля за 30 дней: кастомный WYSIWYG без сторонних редакторов, своя Markdown Object Model на TypeScript, contenteditable в связке с React, немного боли от браузерных API и ноль.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/30-dnej-blochnyj-konstruktor-readme-odin-dom-dva-hozyaina">30 дней: блочный конструктор README — один DOM, два хозяина</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Redux]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Markdown]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 07 May 2026 03:15:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы живём в эпоху когда можно написать в чат «сделай мне CRUD» и получить рабочий код через десять секунд что в принципе удобно. И это, если честно, главная причина почему я периодически намеренно лезу в что-то сложное руками — чтобы не разучиться думать о том что происходит внутри.</p><p>ИИ я использую. Но в этом проекте он был исключительно быстрой документацией — особенно когда добрался до selection/range API, про которые до этого знал чуть меньше чем ничего. Реализация все равно была за мной.</p><p>Так вот — ReadGen. Блочный конструктор README-файлов. Месяц, 2-3 часа в день, React и TypeScript и небольшая пачка дополнительных библиотек для разумного облегчения жизни. Важно понимать что это <b>не коммерческий продукт и не претендует на решение чьей-то боли.</b> Просто техническая задача которую я давно хотел разобрать.</p><p>Демо:<a href="http://readgen-eight.vercel.app"> readgen-eight.vercel.app</a></p><p><a href="http://readgen-eight.vercel.app"></a>Репозиторий:<a href="https://github.com/islamxm/readgen"> github.com/islamxm/readgen</a></p><h2>Почему</h2><p>contenteditable — старый добрый дед среди браузерных API. То что он старый - это да, а вот доброта - под вопросом.</p><p>Он искренне хочет помочь, сам расставляет &lt;div&gt; и &lt;br&gt;, сам решает что делать с Enter, сам форматирует как считает нужным. Проблема в том что его никто не спрашивал. Когда тебе нужен предсказуемый блочный редактор с контролируемой структурой данных — такая самодеятельность становится основным источником боли.</p><p>Готовые текстовые движки эту боль скрывают. Я хотел её почувствовать лично. Понять где именно браузер начинает делать по-своему и что с этим делать. Это и был настоящий вопрос проекта.</p><p>Поначалу казалось что задача не супер сложная, но довольно быстро понял что полноценный текстовый редактор это бесконечные edge cases которые не влезут ни в какой месяц. Граница была проведена жёстко: плоская структура блоков, никакой иерархии. Всё остальное — потом, в другой жизни.</p><p>Так задача сузилась до честного и реалистичного объёма, блочный конструктор README без вложенности, с предсказуемым ядром и нормальным экспортом.</p><h2>Три OM</h2><p>Между реальным DOM и виртуальным DOM React затесался ещё один — MOM, Markdown Object Model. Пафосное название для того что по сути является flat map структурой документа. Но только по сути — на деле это целое ядро, состоящее из парсера, валидатора, гардов, движка, сериализатора. Это по сути модули без внешних зависимостей (кроме dexie.js в подмодуле storage) которые принимают данные, делают свое дело и отдают, впрочем как и принято в среде порядочных разработчиков.</p><p>Архитектурно MOM живёт как отдельный изолированный модуль — ведёт себя как shared слой в FSD, но это не набор утилит а самостоятельное ядро. Всё остальное приложение построено на гибриде FSD и layered подхода, но MOM существует по своим правилам.</p><h2>Один DOM, два хозяина</h2><p>Вот где настоящий конфликт. Структурой документа, порядком блоков, состоянием UI управляет React. Но внутри редактируемых блоков он полностью отступает. Там нет react рендеринга, только innerHTML и чистый DOM. Синхронизация происходит в эффекте, источник истины — содержимое contenteditable, и именно оттуда формируется MOM.</p><p>Два хозяина поделили территорию. React взял всё снаружи, DOM взял всё внутри. С одной стороны казалось бы все круто, так и должно быть, но на самом деле это порождает новый пласт проблем.</p><p>Отдельная история внутри этой истории — курсор. Позицию нужно постоянно сохранять и восстанавливать через selection/range API, и синхронизировать это с React. Это отдельная боль внутри общей боли.</p><p>Деструктивная синхронизация DOM — innerHTML = '' перед каждой манипуляцией выглядит грубо, зато работает. Можно было конечно написать свой reconciliation но учитывая исходные рамки по времени я решил отказаться.</p><h2>Генераторы в сериализаторе</h2><p>Экспорт документа в .md — финальная точка всей архитектуры. MOM → Serializer → файл. Впервые за все время своего осознанного программирования я решил тут применить генераторы в деле.</p><p>Каждый тип узла имеет свою функцию-генератор. Первый yield — контент блока, второй yield "" — пустая строка-разделитель. Тут мы избавляемся от аккумулятора который нужно тащить в глубь и код делаем более плоским и понятным. Array.from(gen).join("\n") в конце собирает всё в строку.</p><h2>Drag and drop на Pointer Events</h2><p>Кастомный drag and drop с анимацией как отдельный хук. И тут я познакомился с довольно таки мощными событиями класса pointer</p><p>о существовании которого я знал но почему то всегда обходился с touch</p><p>и <i>mouse</i> ивентами.</p><p>Pointer Events унифицируют всё что раньше требовало отдельных mouse и touch обработчиков. Без pointercancel drag зависает если браузер решает прервать жест сам. setPointerCapture — чтобы события не терялись когда курсор уходит за пределы контейнера.</p><h2>Компромиссы</h2><p>MOMRaw — честное признание границы. Все что не попало в текущую версию движка (таблицы, инлайн-картинки, инлайн-код и т.д.) живёт в одном компромиссном блоке. Вставляй сырой Markdown или HTML, смотри превью через внешнюю библиотеку в изолированном Shadow DOM.</p><p>То же самое с блоком кода - MOMCode. Для этого я подключил библиотеку uiw, думаю в будущем заменю его на более современное и понятное решение.</p><p>Виртуализации нет, README на 10 000 блоков не существует в природе, проблема надуманная ну или просто не успел (выбирайте сами).</p><p>Мелочи которые просто есть и работают: менеджер глобальных шорткатов, тултипы для вставки блоков, эмодзи и форматирования текста, миниатюры документов через modern-screenshot, хранение в IndexedDB без бэкенда.</p><h2>Заключение</h2><p>Месяц закончился. contenteditable я теперь знаю лично — и он знает меня. Selection/Range из страшного незнакомца превратился в рабочий инструмент. Понял где React уместен, а где лучше отойти и дать DOM делать своё дело. Нашёл наконец задачу где генераторы оказались не паттерном ради паттерна а реально лучшим решением.</p><p>Демо:<a href="http://readgen-eight.vercel.app"> readgen-eight.vercel.app</a></p><p><a href="http://readgen-eight.vercel.app"></a>Репозиторий:<a href="https://github.com/islamxm/readgen"> github.com/islamxm/readgen</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Как я написал E2EE-мессенджер на Spring Boot и WebCrypto — и почему сервер не видит сообщения</title>
      <link>https://tproger.ru/articles/kak-ya-napisal-e2ee-messendzher-na-spring-boot-i-webcrypto-i-poch</link>
      <comments>https://tproger.ru/articles/kak-ya-napisal-e2ee-messendzher-na-spring-boot-i-webcrypto-i-poch?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Василенков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-ya-napisal-e2ee-messendzher-na-spring-boot-i-webcrypto-i-poch</guid>
      <description><![CDATA[<p>Разбор архитектуры E2EE-мессенджера на Spring Boot 3, React и WebCrypto: X3DH, symmetric ratchet, AES-GCM, WebSocket, multi-device и ограничения реализации.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-ya-napisal-e2ee-messendzher-na-spring-boot-i-webcrypto-i-poch">Как я написал E2EE-мессенджер на Spring Boot и WebCrypto — и почему сервер не видит сообщения</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Android]]></category>
      <category><![CDATA[Stack Overflow]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Опрос]]></category>
      <category><![CDATA[IDE]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Tor]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[HR]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[MIT]]></category>
      <category><![CDATA[Алиса]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Мессенджер]]></category>
      <category><![CDATA[5g]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Взлом]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[1C]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Grafana]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 05 May 2026 05:35:02 GMT</pubDate>
      <content:encoded><![CDATA[<figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/240e412b-4307-49f9-b24f-bde7e0a7fd9a.webp" alt="" /></figure><p>Я Java-разработчик и в основном работаю с backend: Spring Boot, базы данных, интеграции, авторизация, WebSocket — всё то, что обычно находится за интерфейсом.</p><p>В какой-то момент я поймал себя на мысли: я каждый день пользуюсь мессенджерами, но плохо понимаю, как они устроены внутри. Окей, JWT, WebSocket, PostgreSQL, Redis — это понятно. Но что технически означает фраза "end-to-end encryption"? Как сервер доставляет сообщения, если он не должен их читать? Где живут ключи? Что хранится в базе? Что происходит, если у пользователя два устройства?</p><p>Решил разобраться через практику. Написал мессенджер с нуля. Назвал Chaos Messenger.</p><p>Сразу честно: криптографическую часть я изучал вместе с Claude и ChatGPT — читал спецификации X3DH и Double Ratchet, разбирал примеры, задавал вопросы, пока не сложилась цельная картина. Frontend тоже делался с активной помощью ChatGPT: я backend-разработчик, React для меня не основная среда. Но архитектура, backend, интеграция WebCrypto, модель конвертов, хранение сообщений и принципиальные решения — мои.</p><p>Для меня AI здесь был не заменой понимания, а инструментом — примерно как документация, Stack Overflow и ревью коллег. Без понимания threat model и архитектуры такой проект всё равно не собрать.</p><p>В статье расскажу, как работает E2EE изнутри: как устанавливается сессия через X3DH, как каждое сообщение получает отдельный ключ через Symmetric Ratchet, почему сервер хранит только зашифрованные конверты, и какие ошибки я допустил по дороге.</p><p>Стек: Spring Boot 3, React 18, WebCrypto API, PostgreSQL, Redis, WebSocket/STOMP, Prometheus, Grafana.</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/4b028f37-2de1-46ea-a247-567f74fb3081.webp" alt="" /></figure><h2>Важная оговорка про web-E2EE</h2><p>Когда я говорю, что сервер не может прочитать сообщения, я имею в виду backend, базу данных, WebSocket-слой и уже сохранённые ciphertext-конверты. У них нет ключей и plaintext.</p><p>Но у web-E2EE есть отдельная проблема: frontend-код тоже приходит с сервера. Теоретически скомпрометированный сервер может отдать изменённый JavaScript, который украдёт ключи или plaintext до шифрования. Это ограничение не конкретно моего проекта, а браузерной модели в целом.</p><p>Поэтому корректная формулировка такая: backend не получает ключи и не может расшифровать уже переданные или сохранённые сообщения. Защита от подмены клиентского кода — отдельный слой безопасности: подпись сборок, независимая верификация клиента, desktop/mobile-приложения, reproducible builds.</p><h2>Почему обычный подход не работает</h2><p>Большинство "мессенджеров" на GitHub выглядят примерно так:</p><p>Сервер знает всё. Видит каждое сообщение. Если БД утекла — утекла вся переписка. Если сервер взломали — читай что хочешь. Если завтра компания решит продать данные — технически ничего не мешает.</p><p>E2EE решает это радикально: backend не получает ключи и не хранит plaintext. Сообщение шифруется на устройстве отправителя до отправки в сеть, а расшифровывается только на устройстве получателя.</p><p>Это уже не вопрос политики конфиденциальности в стиле "мы обещаем не читать". Это архитектурное ограничение: если у сервера нет ключа, он не может превратить ciphertext обратно в текст.</p><p>Звучит как магия. На самом деле — два протокола и немного WebCrypto.</p><h2>Главная идея: конверты</h2><p>Представь что Алиса хочет написать Бобу. Вместо того чтобы положить письмо на стол и надеяться что никто не прочитает — она кладёт его в запечатанный конверт. Конверт может открыть только Боб своим ключом. Сервер просто передаёт конверт не заглядывая внутрь.</p><p>Именно так это работает в коде. В базе данных у меня это выглядит так:</p><p>Когда я впервые увидел</p><p>в своей БД вместо текста — стало понятно, что модель наконец работает правильно: сервер создал сообщение, доставил его, сохранил метаданные, но так и не узнал содержимое.</p><p>А вот что сервер возвращает при запросе списка чатов через API:</p><p>Не</p><p>. Не</p><p>. Буквально</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/b466ad91-20da-4e64-8b78-f6e08d2851d5.webp" alt="" /></figure><p>(DevTools → Network → ответ API с</p><p>)</p><h2>Откуда берутся ключи: X3DH</h2><p>Главный вопрос: как Алиса и Боб получают общий секрет, если они никогда раньше не общались? И как сделать это так, чтобы сервер только помог передать публичные данные, но сам не смог вычислить итоговый ключ?</p><p>Для этого используется X3DH — Extended Triple Diffie-Hellman, протокол из экосистемы Signal. Его задача — установить общий секрет между двумя устройствами, используя долгосрочные и временные ключи.</p><h2>Что хранится на сервере</h2><p>Когда пользователь регистрирует устройство, он загружает на сервер пакет публичных ключей:</p><p>На сервер уходят только публичные части. Приватные ключи сериализуются и хранятся локально в браузере — и никогда не покидают устройство в сеть.</p><p>Здесь важно сказать честно: хранение приватных ключей в</p><p>Более строгий вариант — использовать Web Crypto API с</p><p>, чтобы приватный ключ жил внутри браузерного crypto runtime и его нельзя было экспортировать в байты. Но у этого подхода есть практическая сложность: ключи нужно переживать между перезагрузками страницы, синхронизировать с IndexedDB, аккуратно восстанавливать состояние устройства и не сломать UX.</p><p>В браузерных E2EE-приложениях обычно приходится выбирать между несколькими вариантами:</p><ul><li>Сериализуемые ключи в localStorage или IndexedDB — проще реализовать, но нужно очень серьёзно относиться к XSS и целостности frontend-кода.</li><li>extractable: false + IndexedDB — безопаснее, но сложнее в реализации и восстановлении состояния.</li><li>Нативное secure storage вроде Android Keystore или iOS Secure Enclave — лучший вариант для мобильных клиентов, но он недоступен обычному web-приложению.</li></ul><p>В текущей версии Chaos Messenger используется первый вариант. Это осознанный компромисс для pet/open-source проекта и удобного запуска в браузере. Переход на non-extractable ключи и более строгую модель хранения стоит в roadmap.</p><p>Ключевой момент: backend всё равно не получает приватные ключи и не может расшифровать сохранённые ciphertext-конверты. Но защита ключей на клиенте — отдельная задача, и её нельзя честно замалчивать.</p><h2>Установка сессии</h2><p>Когда Алиса открывает переписку с Бобом впервые, происходит следующее:</p><p>В классическом X3DH четвёртая DH-операция с one-time prekey опциональна: она выполняется, если сервер выдал доступный OPK получателя. В моей реализации устройство публикует набор one-time prekeys при регистрации, поэтому первое сообщение обычно использует DH4. Если OPK закончились, сессию всё равно можно установить через остальные DH-компоненты, но это уже менее сильный вариант.</p><p>Боб, получив конверт с эфемерным публичным ключом Алисы, повторяет те же операции со своими приватными ключами и получает тот же самый</p><p>. Математика симметрична.</p><p>Сервер в этот момент видит только публичные ключи и зашифрованный конверт. Он помогает устройствам найти друг друга, но не участвует в вычислении секрета.</p><p>Получить</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/e7bbacf3-4fd2-4710-863a-2c710b1f6759.webp" alt="" /></figure><h2>Как шифруется каждое сообщение: Symmetric Ratchet</h2><p>X3DH даёт нам стартовый</p><p>. Но использовать один и тот же ключ для всех сообщений — плохая идея. Если использовать один ключ для всей переписки, компрометация этого ключа сразу открывает весь поток сообщений.</p><p>Решение — симметричный ratchet. После каждого сообщения цепочка ключей продвигается вперёд:</p><p>Визуально это выглядит так:</p><p>используется для шифрования одного сообщения через AES-GCM, после чего уничтожается. Если атакующий компрометирует</p><p>— он прочитает только второе сообщение.</p><p>В рамках такой симметричной цепочки это даёт forward secrecy назад по цепочке: зная текущий или отдельный</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/939a74f7-60c5-4816-85f0-05b5afdc9845.webp" alt="" /><figcaption>(диаграмма схемы chainKey → messageKey)</figcaption></figure><p>Само шифрование сообщения:</p><p>А вот что уходит на сервер — живой пример из DevTools:</p><p>Сервер получает</p><p>и</p><p>. Расшифровать без</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/b5e24cda-6fd9-4b15-9f27-3eb9b4f843b0.webp" alt="" /></figure><h2>Важная оговорка: это ещё не полный Double Ratchet</h2><p>В этом проекте реализован Symmetric Ratchet — цепочка, где из</p><p>для каждого сообщения выводится отдельный</p><p>Это защищает прошлые сообщения: если атакующий узнает текущий ключ или отдельный</p><p>, он не сможет откатить HMAC назад и получить старые ключи.</p><p>Но это не полный Double Ratchet из Signal Protocol.</p><p>В полном Double Ratchet есть ещё DH ratchet step: стороны периодически выполняют новый Diffie-Hellman обмен и обновляют root key. Это даёт break-in recovery — возможность восстановить безопасность будущих сообщений после компрометации части состояния.</p><p>В моей реализации DH ratchet step пока нет. Если атакующий получит актуальное состояние сессии на устройстве и сможет продолжать его читать, он сможет расшифровывать будущие сообщения до переустановки сессии. Это честное ограничение текущей версии, и оно стоит первым пунктом в roadmap.</p><h2>Мультиустройство: один пользователь, несколько конвертов</h2><p>Первый неочевидный момент: в E2EE сообщение адресуется не просто пользователю, а конкретным устройствам пользователя.</p><p>Если у Боба два устройства — телефон и ноутбук — нужен отдельный encrypted envelope для каждого устройства. Сервер не может взять один конверт, расшифровать его и "переупаковать" для второго устройства: у него нет ключей и он не знает plaintext.</p><p>Значит при отправке сообщения нужно зашифровать его отдельно для каждого устройства каждого участника чата.</p><p>Для чата где у каждого по 2 устройства — 4 конверта на одно сообщение. Для группы из 10 человек — потенциально 20 конвертов. Это нормально, это цена безопасности.</p><h2>Сервер: хранение и доставка конвертов</h2><p>На сервере сообщение создаётся с контентом</p><p>, а конверты сохраняются отдельно:</p><p>После сохранения — fanout по WebSocket. Каждое устройство получает свой конверт и только его:</p><p>Это важное отличие от обычного WebSocket-чата. В обычном чате сервер рассылает одно и то же событие всем участникам. В E2EE-чате сервер рассылает разные события разным устройствам: payload для каждого устройства содержит свой</p><p>Топик</p><p>— строго персональный. Устройство А не получает конверт устройства Б. Никакого broadcast — только адресная доставка.</p><h2>Архитектура целиком</h2><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/de0e3257-d893-459f-86ed-7ed8eace5d56.webp" alt="" /></figure><h2>Баг который долго не замечал</h2><p>В панели чатов показывается превью последнего сообщения. Я реализовал это через</p><p>Запускаю — в списке чатов у всех написано</p><p>.</p><p>Конечно. Сервер же не знает что там написано.</p><p>Я полчаса думал как решить это на сервере. Потом дошло: нельзя решить это на сервере — у него нет ключей. Решение только на клиенте.</p><p>После того как пользователь открыл чат и сообщения расшифровались — кешируем последнее в памяти:</p><p>Это хороший пример того, как E2EE меняет привычное мышление backend-разработчика. В обычном приложении preview — это поле в SQL-запросе. В E2EE-приложении preview — это локальное клиентское состояние, потому что только клиент видел plaintext.</p><p>Простое решение. Но чтобы к нему прийти нужно было полностью принять идею что сервер здесь просто не при делах — и перестать пытаться решить задачу на его стороне.</p><h2>Rate limiting: дыра которую легко не заметить</h2><p>Эндпоинт</p><p>отправляет SMS с кодом. Без защиты любой скрипт может дёргать его тысячи раз — это называется SMS pumping fraud, SMS стоят реальных денег.</p><p>Redis у нас уже был для хранения онлайн-статусов. Добавил rate limiting поверх него:</p><p>При превышении — HTTP 429 с заголовком</p><p>. Клиент знает через сколько секунд можно повторить.</p><p>Важный нюанс: в текущей реализации, если Redis недоступен, сервис не блокирует авторизацию полностью. Для pet-проекта это приемлемый компромисс: лучше рискнуть одним лишним SMS, чем положить вход в приложение.</p><p>В production я бы сделал строже: fallback in-memory лимит на инстанс, отдельные лимиты по IP и телефону, антифрод-логику и алерты на всплески отправки кодов.</p><h2>Авторизация WebSocket</h2><p>Отдельная история — авторизация WebSocket соединений. HTTP-эндпоинты защищены Spring Security автоматически, но WebSocket — другое дело. STOMP-соединение устанавливается один раз, и нужно проверять JWT при каждом подключении.</p><p>Отдельно важно не только проверить JWT, но и связать WebSocket-соединение с конкретным устройством. Пользователь может быть один, но устройств у него несколько, а encrypted envelope адресован именно</p><p>Поэтому при подключении я проверяю не только токен, но и</p><p>: устройство должно быть зарегистрировано и принадлежать текущему пользователю. Иначе легко случайно превратить per-device E2EE-доставку обратно в обычный broadcast по пользователю.</p><h2>Что получилось — живые скрины</h2><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/1fdfa006-f5c6-4731-a40f-50559be8832d.webp" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/c99164a5-021c-49ac-bcc4-3e38f9cd1c31.webp" alt="" /></figure><p>Что реализовано:</p><ul><li>E2EE-модель с per-device encrypted envelopes</li><li>X3DH session setup + Symmetric Ratchet + AES-GCM</li><li>Мультиустройство</li><li>Личные и групповые чаты</li><li>Realtime доставка через WebSocket/STOMP</li><li>Статусы SENT → DELIVERED → READ</li><li>Редактирование и soft delete сообщений</li><li>Online presence, typing indicator</li><li>Фото-вложения</li><li>Поиск пользователей</li><li>Rate limiting на SMS через Redis</li><li>Prometheus метрики + Grafana дашборд</li><li>Swagger UI с JWT авторизацией</li><li>24 backend-теста на Testcontainers, 12 frontend на Vitest, E2E на Playwright</li><li>GitHub Actions CI</li></ul><p>Что ещё не сделано:</p><ul><li>Полный Double Ratchet с DH ratchet step и break-in recovery</li><li>Ротация signed prekey и аккуратное пополнение one-time prekeys</li><li>Более строгая модель хранения приватных ключей на клиенте: non-extractable CryptoKey + IndexedDB</li><li>Защита от подмены frontend-кода: подпись сборок, независимая верификация клиента, reproducible builds</li><li>Android-клиент с Android Keystore</li><li>Реальный SMS-провайдер вместо кода в backend-логах</li><li>Push-уведомления без утечки содержимого сообщений</li><li>Более строгая metadata-модель для групповых чатов</li></ul><h2>Главный инсайт</h2><p>E2EE — это архитектурное решение, а не библиотека.</p><p>Нельзя взять обычный Spring Boot чат и просто "включить шифрование". Нужно с самого начала проектировать систему так, чтобы backend не был участником доверенной зоны: он не должен получать plaintext, не должен иметь ключи и не должен уметь пересобирать сообщение из данных в базе.</p><p>Это меняет почти всё:</p><ul><li>структуру БД — вместо текста появляются encrypted envelopes</li><li>API — сервер отдаёт [encrypted], а не preview сообщения</li><li>WebSocket — доставка идёт не по пользователю, а по конкретному устройству</li><li>мультиустройство — одно сообщение превращается в несколько ciphertext-конвертов</li><li>frontend — становится полноценной криптографической частью системы, а не просто UI</li></ul><p>Второй инсайт: мессенджер — это не "чат с WebSocket". В E2EE-модели это система доставки зашифрованных конвертов с адресацией по устройствам. Как только это принимаешь, многие странные на первый взгляд решения становятся логичными.</p><h2>Репозиторий</h2><p>Код открыт: <a href="https://github.com/vaazhen/chaos-messenger">github.com/vaazhen/chaos-messenger</a></p><p>В репозитории есть README на русском и английском, диаграммы, скриншоты, security audit, Docker Compose и запуск одной командой.</p><p>Проект не претендует на уровень production-криптомессенджера вроде Signal. Это учебный и инженерный open-source прототип, цель которого — показать, как E2EE меняет архитектуру backend, frontend и realtime-доставки.</p><p>Если вы делали что-то похожее — особенно интересно сравнить подходы к ротации prekey-ов, хранению non-extractable ключей в браузере и реализации DH ratchet step. Вопросы и критика приветствуются.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как связаны Atomic CSS и Функциональное программирование</title>
      <link>https://tproger.ru/articles/kak-svyazany-atomic-css-i-funkcionalnoe-programmirovanie</link>
      <comments>https://tproger.ru/articles/kak-svyazany-atomic-css-i-funkcionalnoe-programmirovanie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Рамазан Максютов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-svyazany-atomic-css-i-funkcionalnoe-programmirovanie</guid>
      <description><![CDATA[<p>Почему подход Atomic CSS называют функциональным? Именно на этот вопрос я постараюсь ответить в данной статье! Сначала я опишу вам базовые принципы ФП, а затем расскажу про основы Atomic CSS, проводя аналогии с функциональным программированием. </p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-svyazany-atomic-css-i-funkcionalnoe-programmirovanie">Как связаны Atomic CSS и Функциональное программирование</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 27 Apr 2026 07:46:42 GMT</pubDate>
      <content:encoded><![CDATA[<p>Всем привет, друзья!</p><p>Меня зовут Рамазан, я Frontend-разработчик и энтузиаст, который любит искать свежий взгляд на привычные вещи в Web-разработке.</p><p>Думаю, что вы слышали когда-либо про функциональное программирование (далее - ФП). Это парадигма для которой характерно применение чистых функций и сохранение иммутабельности данных. Существует ряд языков, в которых принципы ФП преобладают: Haskell, OCaml и Elixir. Однако и другие языки вроде JavaScript, Python, C++ поддерживают этот подход, хотя только им не ограничиваются.</p><p>Но внимательный читатель посмотрит на название и спросит: "Функциональное программирование - это хорошо. А причём здесь Atomic CSS?". Сейчас отвечу! Всё дело в том, что ещё на заре появления атомарного подхода у него существовало и другое название - Functional CSS. Кто-то довольно часто использует его и сейчас, чтобы не путать с одноимёнными <a href="https://css-tricks.com/the-atomics/" rel="nofollow">терминами</a>. Но почему этот подход к CSS-стилизации называют функциональным?</p><p>Именно на этот вопрос я постараюсь ответить в данной статье! Сначала я опишу вам базовые принципы ФП, а затем расскажу про основы Atomic CSS (далее - ACSS), проводя аналогии с функциональным программированием. Кроме того, я постараюсь на простых примерах показать, какие проблемы решаются, если применять Atomic CSS при стилизации. Когда я готовил материалы для этой статьи, я много опирался на <a href="https://www.youtube.com/watch?v=7g0BHu0kWXo" rel="nofollow">этот </a>и на <a href="https://www.youtube.com/watch?v=uHVqbCPnOwU" rel="nofollow">этот </a>доклады. Наконец, хотел заметить, что это перевод моей оригинальной <a href="https://www.freecodecamp.org/news/atomic-and-functional-css" rel="nofollow">статьи</a> на английском языке — так что можете глянуть и её.</p><p>Ну что, держитесь крепче - мы начинаем!</p><h2>Что нужно знать</h2><p>Чтобы разобраться в этой статье, все, что вам нужно, - это базовое понимание HTML, CSS и JavaScript. Также в статье имеется несколько примеров, в которых мы будем использовать Atomic CSS фреймворк [mlut](https://mlut.style/), но вам не обязательно знать его синтаксис, потому что я привёл расшифроку его утилит в рукописном CSS.</p><h2>Базовые принципы ФП</h2><p>Функциональное программирование - это большая область, в которой написано немало сложных статей и которой посвящен целый <a href="https://www.cambridge.org/core/journals/journal-of-functional-programming" rel="nofollow">научный журнал</a>. Поэтому я в своей статье сосредоточусь на том, чтобы изучить только базовые принципы ФП и провести аналогию с ними в Atomic CSS.</p><p>Этот подход основывается на том, что все необходимые для нас действия в программе мы должны совершать посредством вызова некоторых функций и их композиций.</p><p>Давайте я приведу основные понятия, через которые попытаюсь раскрыть суть данного подхода:</p><p>1) Чистые функции;</p><p>2) Иммутабельность;</p><p>3) Композиция функций.</p><h3>Чистые функции</h3><p>Функция называется чистой, если она:</p><p>- при одних и тех же входных параметрах возвращает одно и то же значение;</p><p>- не имеет побочных эффектов (изменений внешних значений или сущностей).</p><p>Приведу для наглядности пару примеров.</p><p>Первая функция чистая, ведь, если мы будем вводить одни и те же аргументы, то будем получать один и тот же результат. Кроме того, никакие глобальные переменные эта функция не меняет, а объекты не мутирует.</p><p>Вторая функция не попала в понятие чистоты по обоим пунктам. Она меняет внешнюю переменную `s` и использует для вычислений другую внешнюю переменную `c`, которая может меняться.</p><p>Чистые функции позволяют писать более предсказуемый код. Приложение может разрастись, а функция с неявным параметром, который может меняться со временем, может привести к большим сложностям в дебаге и поддержке кода.</p><h3>Иммутабельность</h3><p>Иммутабельность - это принцип, в соответствии с которым объекты данных не должны меняться после создания. Чтобы произвести изменения над данными, необходимо создать новый их экземпляр и работать с ним.</p><p>Поначалу может показаться, что таким образом мы лишаем себя гибкости в процессе разработки и уменьшаем скорость работы программы. Но в действительности, если в языке или рантайме есть оптимизации для иммутабельных данных, соблюдение этого принципа помогает избегать многих ошибок и использовать параллельные вычисления.</p><p>Вот довольно простой пример: возьмём компонент React, который отображает задачу в списке дел. Состояние задачи описывается объектом. И чтобы React правильно перерисовал состояние компонента задачи, когда пользователь что-то в нём изменяет, необходимо передать в качестве нового состояния не измененный старый объект, а новый экземпляр объекта с текущим состоянием. Вот пример, где для состояния задачи используется иммутабельный объект:</p><p>Здесь мы увидим, что нажатие на кнопку приведёт к изменению состояния задачи и вызовет повторную прорисовку компонента. Однако мы могли бы определить функцию `toggleDone()` иным способом, используя мутации объекта:</p><p>При использовании такого обработчика событий эффект не будет достигнут, поскольку ссылка на объект остаётся прежней, несмотря на то что сам объект был изменён.</p><h3>Композиция функций</h3><p>Композиция функций подразумевает применение результата выполнения одной функции в качестве аргумента другой функции. Приведём пример программы, которая делает первую букву каждого слова заглавной:</p><p>Здесь мы определили функцию `compose(f1, f2)`, которая возвращает композицию функций, переданных ей в аргументах. Дальше мы используем эту функцию для создания функции `capitalize()`, которая будет как раз делать только первые буквы слов заглавными посредством композиции функций `lower()` и `upperEveryFirst()`. Сначала выполняется первая из них, и она возвращает строку со всеми строчными буквами в аргумент второй функции. Вторая же функция делает первую букву каждого слова заглавной.</p><p>В больших проектах и при необходимости сложных вычислений такие композиции могут быть и несколько больше, а логика в них может быть куда более богатой. И в этих случаях такой подход к проведению вычислений помогает разбить очень объёмные преобразования на серию относительно простых и компактных функций, которые применяются одна за другой. Это повышает удобство разработки, рефакторинга и отладки кода.</p><h2>Как принципы ФП находят отражение в Atomic CSS</h2><p>Теперь, когда мы узнали немного про функциональное программирование, попробуем ответить на вопрос: "А причём здесь Atomic CSS?". Проведём аналогию между этими подходами на уровне описанных выше принципов.</p><h3>О том, что такое Atomic CSS</h3><p>Но сначала уделим немного времени тому, что такое Atomic CSS. Это методология вёрстки, в которой мы используем маленькие атомарные CSS-правила, каждое из которых делает одно действие. Эти классы называют утилитами. Обычно они применяют одно CSS-свойство, но не обязательно одно. Например, во фреймворке [mlut](https://mlut.style/) утилите `Bgc-red` соответствует свойство `background-color: red`, а утилите `-Sz50p` - сразу два свойства `width: 50%` и `height: 50%`.</p><p>Современные Atomic CSS фреймворки, типа mlut и Tailwind, внутри используют так называемый JIT-движок. Это компонент, который генерирует CSS на основе только тех утилит, которые вы использовали в разметке.</p><h3>Чистота</h3><p>Чистота в CSS определяется тем, какими селекторами, а если конкретнее - классами, задаётся стилизация элементов. В чистом CSS поведение элемента должно определяться исключительно теми классами, которые привязаны к нему в атрибуте `class`. То есть в файле со стилями в идеале не должно быть селекторов вроде `section`, `div &gt; ul`.</p><p>Во-первых, они задают слишком общие правила, которые с большой вероятностью должны быть нарушены или дополнены в той или иной части проекта. Поэтому, когда мы будем стилизовать конкретные элементы, нам придётся постоянно держать в голове эти стили, чтобы понять, как аккуратно достичь необходимой стилизации, ничего не испортив.</p><p>Во-вторых, нарушается чистота CSS для каждого конкретного элемента. Допустим, мы имеем следующую разметку:</p><p>А CSS будет таким:</p><p>В итоге мы получим, что первая кнопка окрашивается в красный цвет, а вложенная - в зелёный. На таком примере это кажется довольно безобидным, но это будет приносить неудобства, когда структура проекта сильно разрастётся. И здесь мы видим, что результат работы собственных стилей класса `.greeting` зависит от того, где соответствующий элемент находится. Это аналогично тому, что функция при одних и тех же вводных данных даёт разные результаты в зависимости от того, где вызывается.</p><p>Atomic CSS позволяет такого эффекта избежать. В этом подходе в большинстве случаев стили применяются только к тем элементам, для которых прописаны соответствующие классы. Если же необходимо сделать несколько одинаковых элементов, то одна и та же утилита прописывается в атрибут `class` каждого такого элемента.</p><p>Аналогичный пример можно переписать на mlut вот так:</p><p>Где JIT-движок mlut сгенерирует такие стили:</p><p>Здесь мы уже видим, что стили элементов в таком подходе будут зависеть только от классов, которые им приписаны.</p><p><i>Замечание</i>: здесь, правда, стоит заметить, что синтаксис mlut позволяет делать вещи, которые отходят от строгого понятия чистоты CSS. Иногда это бывает необходимо для создания относительно более сложных эффектов. Допустим, мы хотим реализовать такую карточку, при наведении на которую меняется цвет фона у вложенной в неё кнопки. Тогда на mlut нужно будет написать следующее:</p><h3>Иммутабельность</h3><p>Под иммутабельностью CSS я буду подразумевать то, что стили элементов не переписываются. Иммутабельность в ACSS означает, что утилиты, как правило, не мутируют друг друга. Например, в BEM основные стили задаёт блок или элемент, а модификатор эти стили мутирует. А в других подходах, где используются комбинированные селекторы, это мутирование происходит чаще и менее явно.</p><p>Попробую привести простой пример. Пусть у нас есть карточка с товаром, которая может быть в своём дефолтном состоянии, либо в состоянии выделения. В BEM это бы выглядело примерно следующим образом:</p><p>В этом примере мы видим, что класс `product-card` задаёт красный цвет фона карточки по умолчанию. И, чтобы как-то обозначить выбранную карточку посредством другого цвета фона, нам приходится добавлять класс-модификатор, который изменяет цвет с красного на зелёный. И делает он это посредством переписывания свойства `background-color`, то есть с помощью мутирования стилей блока.</p><p>В подходе Atomic CSS эта проблема решается, так как утилиты позволяют задавать CSS-свойства независимо друг от друга и задавать модификации, не прибегая к мутации. Вот так будет выглядеть данный пример, если использовать mlut:</p><h3>Композиция</h3><p>В функциональном программировании повсеместно используются композиции функций. В атомарном CSS аналогом композиции функций служит композиция утилит при стилизации элементов. Как в ФП мы посредством последовательного применения множества простых функций получаем сложное поведение, так и в ACSS посредством множества простых утилит мы можем получить нетривиальную стилизацию.</p><p>Для примера покажу простой смайлик, который сделан посредством только лишь Atomic CSS:</p><p>Так будет выглядеть наш результат:</p><figure><img src="https://media.tproger.ru/user-uploads/134620/2026-03-31/3d47e497-4595-4167-af65-b9b4ece345c8.webp" alt="Простой CSS арт" /><figcaption>Простой CSS арт</figcaption></figure><p>Таким образом, применяя утилиты одну за другой, мы получили даже небольшой CSS-арт.</p><h2>Заключение</h2><p>Подводя итоги, скажу, что Atomic CSS действительно воплощает в себе базовые принципы функционального программирования. Конечно, не буквально, но в том смысле, который актуален для Frontend-разработчиков и верстальщиков. Я был бы рад услышать ваши дополнения и возражения в комментариях - будет интересно их почитать и над ними подумать!</p><p>Напоследок скажу: смотрите на привычные вещи свежим взглядом!</p><p>И, как обычно, успехов вам в увлекательном пути Frontend-разработки!</p>]]></content:encoded>
    </item>
    <item>
      <title>Pipeline operator в JavaScript: Hack vs F# — почему TC39 выбрал не тот вариант, считает лид RxJS</title>
      <link>https://tproger.ru/translations/pipeline-operator-v-javascript-hack-vs-f-pochemu-tc39-vybral</link>
      <comments>https://tproger.ru/translations/pipeline-operator-v-javascript-hack-vs-f-pochemu-tc39-vybral?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/pipeline-operator-v-javascript-hack-vs-f-pochemu-tc39-vybral</guid>
      <description><![CDATA[<p>Лид RxJS Бен Леш объясняет, чем Hack-вариант оператора пайплайна проигрывает F# для библиотек вроде RxJS и Ramda. Разбираем плюсы и минусы обоих.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/pipeline-operator-v-javascript-hack-vs-f-pochemu-tc39-vybral">Pipeline operator в JavaScript: Hack vs F# — почему TC39 выбрал не тот вариант, считает лид RxJS</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 13 Apr 2026 17:00:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если вы пишете на RxJS, Ramda или просто часто применяете несколько функций подряд к одному значению — эта дискуссия про вас. Перевод статьи <a href="https://benlesh.com/posts/tc39-pipeline-proposal-hack-vs-f-sharp/">Бена Леша</a> (RxJS Core Team) о двух вариантах оператора |&gt; в TC39: компромиссном Hack-стиле и более «функциональном» F#-стиле, который в комитете отклонили — и почему это, по мнению автора, ошибка.</p><p>Я хочу поделиться всем, что знаю про предложение оператора пайплайна, который сейчас находится на stage 2 в TC39 (это значит «спецификация утверждена в общих чертах, но детали и финальный синтаксис ещё могут меняться»). Это будет, конечно, в какой-то мере предвзятый рассказ — статья моя — но я постараюсь представить обе стороны, при этом отстаивая свою позицию. Если заметите, что я что-то упустил, — напишите мне в DM в Twitter, я стараюсь отвечать на все.</p><p>ВАЖНО. Перед тем как мы погрузимся в это: есть люди, включая меня, у которых очень сильные чувства по этому поводу. Пожалуйста, придерживайте их. Те, кто работает над этими стандартами, — хорошие люди, пытающиеся сделать как лучше для сообщества, даже если мы с ними не согласны.</p><ul><li>Pipeline operator |&gt; в TC39 решает одну боль: «передать значение через цепочку функций без вложенных вызовов». Сейчас в stage 2 принят Hack-вариант, F#-вариант — отклонён, хотя его поддерживала функциональная часть сообщества.</li><li>Hack-вариант: 2 |&gt; ^ ** 2 |&gt; ^ - 1, где ^ — placeholder для значения слева. Работает с любым выражением, не требует higher-order functions, но плохо дружит с библиотеками типа RxJS.</li><li>F#-вариант: 2 |&gt; squared |&gt; subtractOne, без специального символа. Передаёт значение последним аргументом функции справа. Идеально для библиотек с unary-функциями (RxJS, Ramda, fp-ts).</li><li>Главная претензия Леша: библиотеки, которые годами популяризировали идею пайплайна функций (RxJS, Ramda), с Hack-оператором практически не получают выигрыша — там надо писать map(fn)(^) вместо просто map(fn).</li><li>Альтернатива: продвигать F#-pipeline + дополнить proposal-ом partial application (тоже в TC39 на stage 1). Тогда получаем лучшее из обоих миров.</li></ul><h2>Что такое pipeline operator?</h2><p>Если коротко, pipeline — это оператор, в данном случае |&gt;, который позволяет разработчику «прокинуть» вычисленное выражение из левой части (LHS) в какую-то функцию или выражение справа (RHS). Этот приём реализован в куче языков мира программирования, и мы сейчас в удивительной ситуации, когда TC39 — комитет, который управляет стандартом ECMAScript, на котором основан JavaScript — рассматривает добавление такого оператора в стандарт. Конкурировало много proposals, два выделились больше всего: Hack pipeline (сейчас на stage 2) и F# pipeline (отклонён, несмотря на популярность).</p><h2>Как сейчас «пайпят» функции в JS</h2><p>Первое, что нужно понять, — что вообще значит «пайпить» функции. В сегодняшнем JavaScript есть, по сути, только один способ: через «функциональный пайп». Это распространённая утилита из функционального программирования, которая существует в библиотеках вроде Ramda и RxJS, и многих других.</p><p>Идея в том, чтобы применить к значению серию функций в указанном порядке, передавая возвращаемое значение каждой следующей. Сами утилиты делаются довольно просто.</p><p>Сильная сторона функциональных пайпов в том, что функции переносимы и композируемы. Можно переписать пример выше, чтобы он стал читаемее и состоял из переиспользуемых частей.</p><h2>Где это пригождается</h2><p>Конечно, реальные сценарии для пайпинга функций сильно сложнее простой математики. Самый частый — повторное применение функций к разным наборам данных. Главный пример (и да, я о нём поговорю) — RxJS.</p><p>RxJS использует пайп-функции для трансформации observables. Раньше у Observable были только методы класса для трансформаций. Это работало в целом нормально, но при таком количестве возможных операций и того, что методы плохо «трясутся» (tree-shake) современными бандлерами, оказалось, что огромный набор методов не годится для сообщества. Мы пробовали «prototype patching», когда модули добавляют методы к Observable «по меню», но это породило кучу других проблем (об этом — в другой статье). В итоге мы остановились на пайп-функциях. Преимущество: вы платите бандл-сайзом только за то, что используете. Импортируете и применяете нужные операторы, остальное «вытряхивается».</p><p>В случае RxJS простая pipe-функция (как в первом примере) используется внутри метода pipe у Observable, где this передаётся первым аргументом. Все операторы — filter, map, concatMap — это higher-order functions: они принимают аргументы для настройки, а возвращают унарные пайпуемые функции вида (source) =&gt; result.</p><p>Это значит, что общая реализация RxJS-функций неизбежно сложная в плане функциональной композиции — все они в основном (arg) =&gt; (source) =&gt; result, чтобы внутреннее (source) =&gt; result можно было пайпить.</p><h2>Hack pipeline (текущее предложение TC39)</h2><p>Текущий вариант оператора, который TC39 продвигает, называется Hack pipeline. Назван по языку Hack (диалект PHP, созданный в Facebook), где есть оператор пайплайна, моделью для которого он и стал. Стоит отметить, что предложение находится на stage 2, и я описываю состояние на момент написания.</p><p>В Hack-варианте значение из LHS передаётся в выражение справа через специальный символ-плейсхолдер. В официальной спецификации в качестве presumptive token указан %, но финальный выбор не сделан — обсуждаются варианты %, ^, ^^, @@, #. В примерах ниже автор использует ^.</p><p>Базовый пример и тот же через именованные функции:</p><p>RxJS — обратите внимание на лишние (^) после каждого оператора:</p><p>Существующие non-unary API:</p><p>Внутри async-функции:</p><p>Throw внутри шага пайпа — приходится оборачивать в функцию (см. минусы ниже):</p><h3>Плюсы Hack pipeline</h3><ul><li><b>Эксплицитность.</b> Многие сторонники Hack любят его за то, что он более явный, чем F#: видно, что именно выполняется.</li><li><b>Не требует higher-order functions.</b> Поскольку значение из LHS подаётся как специальный символ в RHS, нет необходимости создавать обёртки-функции, чтобы воспользоваться этим значением.</li><li><b>Работает с любой существующей функцией.</b> Hack pipeline можно применять к любой JavaScript-функции без дополнительной работы.</li><li><b>Большинство выражений «просто работают».</b> Хотите запайпить в ^ + ^? Пожалуйста.</li><li><b>Можно await/yield из окружающего контекста.</b> Если |&gt; внутри async-функции — внутри него можно делать await. Внутри генератора или async-генератора — можно использовать yield в RHS.</li></ul><h3>Минусы Hack pipeline</h3><ul><li><b>«Магический» символ нельзя переименовать.</b> В текущем предложении нет способа переименовать ^, кроме как обернуть RHS в функцию.</li><li><b>«Магический» символ нужно искать.</b> Где именно используется значение из LHS, решает разработчик и куда поставит ^. В обычном текстовом редакторе вам, возможно, придётся играть в «найди шапочку», чтобы понять, где значение применяется.</li><li><b>Некоторые выражения просто не работают.</b> Запайпить можно в большинство выражений, но очевидно, что некоторые не сработают — например, выражение, в котором есть ещё один |&gt;.</li><li><b>Плохо работает с существующими функционально-пайпуемыми библиотеками.</b> На мой взгляд — самый важный минус. Библиотеки, которые годами популяризировали пайпинг функций, выиграют от Hack pipeline куда меньше: например, RxJS пришлось бы вызывать map как source$ |&gt; map(fn)(^).</li><li><b>Нет прямого способа бросить исключение в шаге пайпа без функции.</b> Тут много нюансов, но если вы решили, что не можете сложить значения через ^ + ^, нет чистого механизма выбросить TypeError, кроме как обернуть сложение в функцию или, может быть, в скобки (это не специфицировано). В async-контексте это может стать совсем мутно.</li><li><b>Может убить proposal partial application.</b> Иметь в языке сразу две похожие, но разные фичи — наверняка запутает. Partial application очень похож: тоже использует магический символ для применения значения к выражению, возвращая функцию, принимающую столько аргументов, сколько раз символ встречается. (Очень круто, хотя слегка путает.)</li><li><b>Едва отличается от использования let и =.</b> Тяжело объяснить без кода: по сути это слегка более эффективный способ написать примерно то же самое (см. ниже).</li></ul><h2>F# pipeline</h2><p>F# pipeline — другой вариант предложения, у которого было много сторонников за пределами TC39, но его пропустили в пользу Hack pipeline (со ссылкой на «плюсы», описанные выше). Назван так по самой известной реализации — в языке F#.</p><p>Идея F# pipeline в том, что значение из LHS передаётся как последний аргумент функции справа. Это идеально работает с унарными функциями — точно с такими, что и при функциональном пайпинге выше.</p><p>Базовый пример и тот же через именованные функции:</p><p>RxJS — никаких дополнительных обвязок, операторы используются как есть:</p><p>Существующие non-unary API — через стрелочные обёртки, причём промежуточные значения можно именовать:</p><p>В async-функции прямого аналога нет — пишите как обычно:</p><p>Throw внутри шага пайпа — без всяких обвязок:</p><h3>Плюсы F# pipeline</h3><ul><li><b>Имплицитность.</b> Передаёт значение из LHS в функцию справа предсказуемо и неявно. Нет вопросов «куда ушло значение» — оно всегда передаётся последним аргументом функции справа. А в самом частом случае унарной функции — единственным.</li><li><b>Работает с существующими функционально-пайпуемыми библиотеками.</b> Из коробки. Всё сообщество JavaScript, которое хотело пайпить функции и пайпило их, выиграет от нового оператора. Например, RxJS сможет вызывать map просто как source$ |&gt; map(fn).</li><li><b>Работает с любой функцией через arrow-обёртки.</b> Если использовать стрелочные функции с F#-pipeline, можно использовать любой существующий API в точности так же, как с Hack — только с бонусом, что значение можно назвать.</li><li><b>Не требует магического символа.</b> Не нужно вводить в JavaScript специальный символ. Через стрелочные функции вы используете обычные аргументы.</li><li><b>Разработчики, знакомые с функциональным программированием, получат дополнительную силу.</b> Кто умеет создавать унарные higher-order functions (например, (...args) =&gt; (in) =&gt; out) — может строить интересные переиспользуемые паттерны, недоступные в Hack.</li><li><b>Интересные паттерны с await/yield.</b> С F#-pipeline можно асинхронно получить функцию, которая примет значение из LHS: value |&gt; await getSomeFunc(). В генераторе можно получить ссылку на функцию через корутину с yield.</li><li><b>Код в RHS переиспользуем.</b> В отличие от Hack, всё что находится в правой части F#-оператора, можно положить в переменную и переиспользовать — потому что оно обязано вычисляться в реальную ссылку на функцию.</li><li><b>Хорошо работает с records/tuples (тоже stage 2).</b> Можно деструктурировать в RHS через обычные стрелочные функции. В Hack планов как работать с деструктуризацией из «магического» символа я не видел.</li><li><b>Хорошо работает с partial application (stage 1).</b> Partial application добавляет к F#-pipeline все «приятные» возможности Hack — и даёт лучшее из обоих миров. По моему мнению, если бы partial application уже существовал, Hack pipeline даже не было бы на столе.</li></ul><h3>Минусы F# pipeline</h3><ul><li><b>Требует функцию в RHS.</b> Чаще всего это будет стрелочная функция, но может быть и higher-order, если кто-то хочет переиспользовать.</li><li><b>Не позволяет такое же использование await/yield.</b> Использовать оба можно, но они должны разрешаться или yield-ить ссылку на функцию. Это не полное ограничение, но другое поведение. Кроме того, неочевидно, насколько полезен await после пайпа в принципе.</li><li><b>Продвинутое использование разнесёт higher-order functions.</b> Кто-то считает это минусом — упомяну. HOF — новая сложность для части людей. Я лично думаю, что это будет катализатором лучшего понимания HOF (которые на самом деле — просто другой способ замкнуться над состоянием через функцию, не через класс с методом). В любом случае, простые функции «просто работают» через стрелочную обёртку: |&gt; (x) =&gt; plainFunc(x, x).</li></ul><h2>Резюме автора</h2><p>Я знаю, что в статье много мнения, но надеюсь, этого хватит, чтобы заставить ещё несколько человек подумать над проблемой — и заставить очень важных и умных людей, работающих над предложением, остановиться и пересмотреть некоторые принятые решения. По-моему, жаль, что предложение в текущем виде уже на stage 2.</p><p>Можно получить лучшее из обоих миров одним из способов: (1) разрешить Hack pipeline неявно вести себя как F# pipeline, когда «магический» символ отсутствует; или (2) переключиться на F# pipeline и одновременно протолкнуть предложение partial application. На бумаге второй вариант даёт JavaScript-разработчикам куда более мощный набор инструментов.</p><p>Я долго ругаюсь на эту тему, это правда. Делаю что считаю лучшим для JavaScript-сообщества с тем небольшим влиянием, которое у меня есть. Если бы Hack-предложение «просто работало» с пайпуемыми унарными функциями — у меня не было бы к нему вопросов, и эта статья не была бы написана.</p><p>Надеюсь, обе стороны спора найдут информацию полезной. Также надеюсь, что мы решим вопрос так, чтобы «обеих сторон» больше не было, и мы все двигались вперёд вместе.</p><h2>От переводчика</h2><p>Статья написана в 2021 году, но за прошедшее время мало что изменилось: pipeline operator <a href="https://github.com/tc39/proposal-pipeline-operator">всё ещё на stage 2</a>, проектное решение не пересматривалось. Hack-вариант остаётся официальным, F# — отклонён. Параллельный proposal partial application — на stage 1.</p><p>Если нужен пайплайн уже сейчас — Babel-плагин @babel/plugin-proposal-pipeline-operator поддерживает Hack и F# режимы. Для Hack обязательно указывать topicToken, иначе плагин упадёт:</p><p>Для российских команд практическая мысль: пока оператора нет в стандарте, не торопитесь добавлять Babel-плагин ради эстетики — это лишний build-step и потенциальная сложность поддержки. Чистый pipe(value, ...fns) из ramda или собственная утилита в 5 строк дают 90% выигрыша по читаемости без внешних зависимостей.</p><p>Оригинал статьи: <a href="https://benlesh.com/posts/tc39-pipeline-proposal-hack-vs-f-sharp/">benlesh.com</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>9 нативных API браузера вместо npm-пакетов</title>
      <link>https://tproger.ru/translations/9-nativnyh-api-brauzera-vmesto-npm-paketov</link>
      <comments>https://tproger.ru/translations/9-nativnyh-api-brauzera-vmesto-npm-paketov?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/9-nativnyh-api-brauzera-vmesto-npm-paketov</guid>
      <description><![CDATA[<p>9 встроенных API браузера вместо npm-пакетов: requestIdleCallback, :focus-within, container queries, dialog, Speech API и другие — с примерами кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/9-nativnyh-api-brauzera-vmesto-npm-paketov">9 нативных API браузера вместо npm-пакетов</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 10 Apr 2026 07:45:04 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если вы привыкли ставить npm-пакет под каждую задачу — вот девять вещей, под которые давно существует нативный API браузера. Кода меньше, багов меньше, бандл легче. Статья польской разработчицы Sylwia Łask собрала самые показательные примеры — перевели и адаптировали для русскоязычного читателя.</p><ul><li>requestIdleCallback — запустить фоновую задачу, когда браузер простаивает</li><li>:focus-within — стилизовать родителя, внутри которого есть элемент в фокусе</li><li>navigator.onLine + события offline/online — детектировать пропажу интернета</li><li>requestAnimationFrame — плавная анимация без рывков</li><li>Container queries — адаптив относительно размеров контейнера, а не viewport</li><li>crypto.getRandomValues — криптографически стойкие случайные ID без коллизий</li><li>&lt;dialog&gt; — нативный модал с доступностью из коробки</li><li>Web Speech API — распознавание речи без библиотек (только Chromium)</li><li>@supports — CSS feature detection без костылей</li></ul><h2>1. «Запустим это потом» → requestIdleCallback</h2><p>Хотите собирать аналитику, предзагружать данные или генерировать что-то в фоне, не конкурируя с рендером 200 компонентов? requestIdleCallback запускает ваш код в те моменты, когда браузер простаивает. На первый взгляд это кажется узкоспециальной фичей, но на практике кейсов много: сбор аналитики о поведении пользователя, несрочная фоновая обработка картинок, предварительная подготовка данных, которые пригодятся позже.</p><p>Поддержка: современные браузеры. В Safari исторически отсутствовал, так что fallback на setTimeout всё ещё полезен.</p><h2>2. «Почему мой input не подсвечивается?» → :focus-within</h2><p>Стилизовать элемент с фокусом — просто. А как стилизовать родителя, внутри которого какой-то элемент получил фокус, — задача, которую обычно решают сорока строками JavaScript со слушателями focus и blur. Всё это не нужно: :focus-within делает то же самое одной CSS-строкой.</p><p>Поддержка: везде, где это хоть сколько-нибудь важно.</p><h2>3. «Покажем офлайн-режим» → navigator.onLine</h2><p>Вечная боль любого PWA — что делать, когда у пользователя пропал интернет (он уехал в лес или зашёл в лифт). Можно писать сложные if-ы — а можно просто слушать события offline и online. На offline складываем данные в IndexedDB, на online отправляем на сервер.</p><p>Поддержка: широкая. Одна оговорка: «онлайн» не равно «ваш бэкенд доступен». Это проверка уровня сетевого соединения, а не доступности конкретного сервиса.</p><h2>4. «Плавная анимация, но проклятая» → requestAnimationFrame</h2><p>Хотите, чтобы анимация не дёргалась на слабых ноутбуках, а батарея садилась медленнее? Привычка «60 fps = setInterval каждые 16 мс» — плохая идея, и вот почему. Классика, которую все видели:</p><p>Интуитивно понятно, что это плохая идея. Лагает. К счастью, есть requestAnimationFrame — он синхронизирован с циклом перерисовки браузера, поэтому анимация действительно плавная.</p><p>Поддержка: везде.</p><h2>5. «Карточка должна адаптироваться, но только здесь» → container queries</h2><p>Одну и ту же карточку можно положить в узкий сайдбар, в основную ленту или в лайтбокс — и чтобы она корректно подстраивалась под каждое место без знания о том, на каком она экране. Раньше media queries были привязаны к размеру viewport, то есть «ко всей странице». Container queries позволяют применять стили в зависимости от размера конкретного контейнера. Компонент становится самодостаточным: куда положили — под то и подстроился.</p><p>Поддержка: современные браузеры. Если целитесь в старые — добавьте fallback через обычные media queries.</p><h2>6. «Случайный ID, что может пойти не так?» → crypto.getRandomValues</h2><p>Именно так рождаются баги:</p><p>Выглядит как «достаточно случайная» криптография с AliExpress — и работает, пока не перестаёт. Во-первых, всё зависит от реализации движка, мы не знаем, что происходит под капотом. Во-вторых, определённые паттерны вполне возможны, а при большом количестве ID вы фактически напрашиваетесь на коллизии.</p><p>К счастью, есть нативное решение. Не серебряная пуля, но crypto.getRandomValues заметно лучше: больше энтропии, нет странных паттернов, вероятность коллизий резко снижается. Браузер просто делает это правильно. А если вам нужен не произвольный набор байтов, а именно UUID, в современных браузерах есть ещё короче: crypto.randomUUID() выдаёт готовый UUID v4 одной строкой — тоже криптографически стойко и без ручной возни с байтами.</p><p>Поддержка: широкая.</p><h2>7. «Нам нужен модал» → dialog</h2><p>Больше не нужно ставить 12-килобайтную библиотеку ради модального окна, которое так любят пользователи. Нативный &lt;dialog&gt; даёт клавиатурную навигацию, фокус-трап и корректную работу со скринридерами прямо из коробки — всё то, что в самописных модалах обычно забывают или делают криво.</p><p>Поддержка: современные браузеры.</p><h2>8. «Голосовой ввод был бы крутой фичей» → Web Speech API</h2><p>Собирались ставить transformers.js, потому что понадобилось распознавание речи? У браузера для этого уже есть Web Speech API. Chromium-браузеры поддерживают его напрямую, Safari — через префиксную версию webkitSpeechRecognition (код ниже как раз это учитывает), в Firefox поддержки нет. Для демо и ассистивных фич — отлично, в проде лучше иметь запасной план на случай Firefox.</p><p>Поддержка: Chromium и Safari (через webkit-префикс), в Firefox до сих пор нет.</p><h2>9. «Не сломает ли это CSS?» → @supports</h2><p>Хотите выкатить backdrop-filter или :has() и при этом не сломать вёрстку в браузере, где фича ещё не поддерживается? Оборачиваем в @supports — и спокойны: браузер, который знает фичу, получит красивую версию, остальные — дефолтный fallback.</p><p>Поддержка: очень хорошая.</p><h2>Когда всё-таки нужна библиотека</h2><p>Библиотеки — это отлично, и иногда они действительно необходимы. Но иногда вы ставите зависимость на то, что браузер решил годы назад. Перед npm install полезно спросить себя (или поискать в MDN): «А браузер не умнее меня в этом вопросе?» Иногда ответ — да. И это нормально.</p><p>Источник: <a href="https://dev.to/sylwia-lask/9-things-youre-overengineering-the-browser-already-solved-them-o99">Sylwia Łask — 9 things you're overengineering: the browser already solved them</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Баг в Bun мог стать причиной утечки исходного кода Claude Code — его знали за 20 дней до инцидента</title>
      <link>https://tproger.ru/news/bag-v-bun-mog-stat-prichinoj-utechki-ishodnogo-koda-claude-code--</link>
      <comments>https://tproger.ru/news/bag-v-bun-mog-stat-prichinoj-utechki-ishodnogo-koda-claude-code--?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Михайлишин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/bag-v-bun-mog-stat-prichinoj-utechki-ishodnogo-koda-claude-code--</guid>
      <description><![CDATA[<p>Баг в Bun генерировал source maps в production mode. Через 20 дней утекли 500 000+ строк кода Claude Code через npm. Разбираем хронологию, причины и уроки.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/bag-v-bun-mog-stat-prichinoj-utechki-ishodnogo-koda-claude-code--">Баг в Bun мог стать причиной утечки исходного кода Claude Code — его знали за 20 дней до инцидента</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Утечка данных]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 01 Apr 2026 03:16:48 GMT</pubDate>
      <content:encoded><![CDATA[<p>31 марта 2026 года через npm утекли более 500 000 строк исходного кода Claude Code — флагманского ИИ-инструмента Anthropic. Причиной стал файл source map (карта исходного кода, связывающая минифицированный бандл с оригинальными файлами), который не должен был попасть в публичный пакет. Мы <a href="https://tproger.ru/news/ishodnyj-kod-claude-code-utyok-cherez-source-map-v-npm---512-000-s">уже разбирали</a>, что нашли внутри. Теперь сообщество обратило внимание на возможную техническую причину — баг в Bun, рантайме, который Anthropic сама <a href="https://bun.com/blog/bun-joins-anthropic">приобрела</a> четырьмя месяцами ранее.</p><ul><li>За 20 дней до утечки в баг-трекере Bun зафиксирован <a href="https://github.com/oven-sh/bun/issues/28001">баг #28001</a>: source maps отдаются в production mode, хотя документация утверждает обратное.</li><li>Claude Code собирается и работает на Bun — рантайме, который Anthropic приобрела в декабре 2025 года.</li><li>Утечка произошла через файл source map размером 59,8 МБ, попавший в npm-пакет @anthropic-ai/claude-code версии 2.1.88.</li><li>Anthropic назвала инцидент «ошибкой при упаковке релиза, вызванной человеческим фактором». Формальный постмортем не опубликован.</li><li>Прямого подтверждения связи между багом #28001 и утечкой нет, но техническое совпадение слишком точное, чтобы его игнорировать.</li></ul><h2>Хронология: от покупки Bun до утечки</h2><p><b>2 декабря 2025</b> — Anthropic <a href="https://bun.com/blog/bun-joins-anthropic">объявляет о покупке Bun</a>, JavaScript-рантайма, на котором работает Claude Code. Создатель Bun Джаред Самнер (Jarred Sumner) переходит в Anthropic. Компания обещает сохранить Bun как open-source проект с MIT-лицензией. К этому моменту Claude Code уже <a href="https://www.anthropic.com/news/anthropic-acquires-bun-as-claude-code-reaches-usd1b-milestone">приносит $1 млрд</a> годовой выручки.</p><p><b>11 марта 2026</b> — в баг-трекере Bun появляется <a href="https://github.com/oven-sh/bun/issues/28001">issue #28001</a>: при использовании Bun.serve() с параметром development: false source maps всё равно генерируются и отдаются клиенту. Документация Bun явно указывает, что в production mode source maps должны быть отключены. Баг зафиксирован на версии 1.3.10.</p><p><b>31 марта 2026</b> — разработчик Чаофан Шоу (Chaofan Shou, <a href="https://x.com/shoucccc">@shoucccc</a>) обнаруживает, что в npm-пакете @anthropic-ai/claude-code версии 2.1.88 лежит файл cli.js.map размером 59,8 МБ. Внутри — полный исходный код: более 500 000 строк TypeScript в ~1900 файлах. Твит набирает более 21 миллиона просмотров.</p><h2>Что за баг в Bun</h2><p>Баг #28001 зафиксирован в подсистеме Bun.serve() — встроенном HTTP-сервере Bun с функцией бандлинга. Суть проблемы: даже когда разработчик явно задаёт development: false, source maps всё равно генерируются и включаются в собранные файлы. Это противоречит <a href="https://bun.com/docs/bundler/fullstack#fullstack-dev-server">документации Bun</a>.</p><p>Claude Code собирается в один файл для публикации в npm через бандлер Bun. Формально баг #28001 описывает проблему в Bun.serve(), а не в автономном бандлере bun build. Однако обе подсистемы используют один и тот же механизм генерации source maps, и сообщество считает вероятным, что проблема затрагивает обе. На момент утечки issue оставался открытым и без фикса.</p><p>Само по себе наличие source map в сборке — ещё полбеды. Проблема в том, что в конвейере публикации Claude Code не было ни записи *.map в .npmignore, ни ограничения поля files в package.json. Если бы любой из этих защитных механизмов работал, source map не попала бы в npm-пакет даже при наличии бага. Сочетание двух факторов — баг в рантайме и отсутствие фильтрации при публикации — привело к утечке.</p><h2>Почему сообщество обратило на это внимание</h2><p>На <a href="https://www.reddit.com/r/programming/comments/1s8t8hp/">Reddit</a> и Hacker News историю обсуждают не столько из-за технической сложности бага — он тривиален. Внимание привлекла ирония ситуации:</p><ul><li>Anthropic купила Bun в декабре 2025 года. Claude Code — главный продукт компании, и он работает именно на Bun.</li><li>Баг в Bun, связанный с source maps, был известен за 20 дней до утечки, но не был исправлен.</li><li>Anthropic позиционирует себя как «safety-first» ИИ-компанию — и при этом допустила два инцидента с утечками за одну неделю: утечка черновиков блога о модели Mythos 26 марта и source map 31 марта.</li><li>Компания активно рассказывает, что её ИИ пишет значительную часть кода Bun — и именно в этом коде нашёлся баг, приведший к утечке кода ИИ.</li></ul><blockquote>Случайно отправить source map в npm — ошибка, которая кажется невозможной, пока не вспомнишь, что значительная часть кодовой базы, вероятно, была написана тем самым ИИ, которого ты релизишь.</blockquote><h2>Что нашли внутри: краткая сводка</h2><p>Мы подробно разбирали содержимое утечки в <a href="https://tproger.ru/news/ishodnyj-kod-claude-code-utyok-cherez-source-map-v-npm---512-000-s">предыдущем материале</a>. Ключевые находки:</p><ul><li><b>KAIROS</b> — нерелизованный режим автономного фонового агента, который работает как демон, подписывается на GitHub-вебхуки и «видит сны» ночью, консолидируя память.</li><li><b>Undercover Mode</b> — режим, при котором Claude скрывает свою природу ИИ и убирает атрибуцию Co-Authored-By в коммитах. Принудительного отключения нет.</li><li><b>ANTI_DISTILLATION</b> — внедрение фейковых инструментов в API-запросы для отравления обучающих данных конкурентов.</li><li><b>BUDDY</b> — тамагочи в терминале с 18 видами существ, уровнями редкости и RPG-характеристиками.</li><li>Внутренние бенчмарки, кодовые имена моделей (Capybara, Fennec, Numbat) и 44 фича-флага.</li></ul><h2>Реакция Anthropic</h2><p>Anthropic удалила npm-пакет в течение нескольких часов и <a href="https://www.theregister.com/2026/03/31/anthropic_claude_code_source_code/">заявила</a>, что утечка — «ошибка при упаковке релиза, вызванная человеческим фактором, а не брешь в безопасности». Пользовательские данные, ключи API и учётные записи не были скомпрометированы. Компания начала рассылать DMCA-уведомления зеркалам на GitHub, но формальный постмортем не опубликовала.</p><p>Тем временем корейский разработчик Сигрид Джин (Sigrid Jin) создал clean-room переписку архитектуры Claude Code на Python под названием <a href="https://github.com/instructkr/claw-code">claw-code</a>. Репозиторий набрал 50 000 звёзд за два часа — <a href="https://github.com/instructkr/claw-code">по заявлению авторов</a>, это рекорд GitHub. Сейчас проект переписывается на Rust.</p><h2>Что это значит для разработчиков</h2><p>Если вы используете Bun для сборки production-кода, стоит проверить свой конвейер публикации:</p><ol><li>Проверьте, не попадают ли файлы .map в ваши npm-пакеты. Используйте npm pack --dry-run для аудита содержимого перед публикацией.</li><li>Добавьте *.map в .npmignore или явно перечислите разрешённые файлы в поле files в package.json. Второй вариант надёжнее — вместо списка исключений вы задаёте список включений.</li><li>Следите за <a href="https://github.com/oven-sh/bun/issues/28001">issue #28001</a> в репозитории Bun — на момент публикации баг не исправлен.</li><li>Если публикуете пакеты через CI/CD — добавьте шаг валидации, который проверяет отсутствие source maps в артефакте.</li></ol><h2>Итог</h2><p>История с утечкой Claude Code — наглядный пример того, как цепочка мелких упущений приводит к масштабному инциденту. Возможный баг в рантайме, отсутствие фильтрации при публикации, недостаточный аудит CI/CD-пайплайна — по отдельности каждая из этих проблем тривиальна. Вместе они обнажили более 500 000 строк проприетарного кода компании с оценкой выше $100 млрд.</p><p>Для разработчиков урок простой: не доверяйте дефолтам инструментов сборки и всегда проверяйте, что именно попадает в ваши пакеты. Команда npm pack --dry-run занимает секунды и может предотвратить утечку, которая изменит историю вашего продукта.</p>]]></content:encoded>
    </item>
    <item>
      <title>Axios взломан на npm — вредоносные версии устанавливают RAT-троянец на все ОС</title>
      <link>https://tproger.ru/news/axios-vzloman-na-npm---vredonosnye-versii-ustanavlivayut-rat-troya</link>
      <comments>https://tproger.ru/news/axios-vzloman-na-npm---vredonosnye-versii-ustanavlivayut-rat-troya?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Михайлишин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/axios-vzloman-na-npm---vredonosnye-versii-ustanavlivayut-rat-troya</guid>
      <description><![CDATA[<p>Злоумышленники взломали npm-аккаунт мейнтейнера axios и внедрили RAT-троянец в версии 1.14.1 и 0.30.4. Разбираем атаку, IoC и как защититься. Проверьте проект.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/axios-vzloman-na-npm---vredonosnye-versii-ustanavlivayut-rat-troya">Axios взломан на npm — вредоносные версии устанавливают RAT-троянец на все ОС</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Информационная безопасность]]></category>
      <category><![CDATA[Уязвимость]]></category>
      <category><![CDATA[Кибербезопасность]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 31 Mar 2026 04:31:22 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если ваш проект использует <b>axios</b> — проверьте package-lock.json прямо сейчас. 31 марта 2026 года злоумышленники взломали npm-аккаунт основного мейнтейнера библиотеки и опубликовали версии с троянцем удалённого доступа (RAT).</p><p>Скомпрометированы версии <b>axios@1.14.1</b> и <b>axios@0.30.4</b> — обе ветки, актуальная и легаси. Вредоносный код устанавливает кроссплатформенный RAT, который работает на macOS, Windows и Linux. Пакеты были доступны в npm-реестре около <b>3 часов</b>, прежде чем npm их удалил.</p><p>— Скомпрометированы axios@1.14.1 и axios@0.30.4 через взлом npm-аккаунта мейнтейнера</p><p>— Вредоносная зависимость plain-crypto-js устанавливает RAT-троянец на все ОС</p><p>— axios — самый популярный HTTP-клиент в JS-экосистеме: более 100 млн загрузок в неделю</p><p>— Безопасные версии: 1.14.0 (для 1.x) и 0.30.3 (для 0.x)</p><p>— Все секреты на затронутых системах нужно ротировать немедленно</p><p><a href="https://github.com/axios/axios">Axios</a> — HTTP-клиент для Node.js и браузеров с более чем 100 миллионами загрузок в неделю. Атаки на цепочки поставок (supply chain attacks) — это компрометация инфраструктуры распространения ПО: вместо атаки на конечную цель злоумышленник внедряет вредоносный код в доверенный пакет, который жертва сама устанавливает.</p><h2>Как произошла атака</h2><p>Злоумышленники получили доступ к npm-аккаунту <b>jasonsaayman</b> — основного мейнтейнера axios. Email аккаунта был изменён с легитимного адреса на ifstap@proton.me. Предположительно, был украден долгоживущий классический npm access token.</p><p>Легитимные релизы axios публикуются через GitHub Actions с криптографической привязкой <a href="https://docs.npmjs.com/generating-provenance-statements">npm OIDC Trusted Publisher</a> и содержат <b>SLSA-провенанс</b> (Supply chain Levels for Software Artifacts — стандарт подтверждения целостности сборки). Вредоносные версии были опубликованы вручную через npm CLI — без привязки к GitHub, без SLSA-провенанса, без соответствующего тега в репозитории.</p><p>Атака была подготовлена заранее: за 18 часов до компрометации axios в npm был опубликован пакет-приманка plain-crypto-js — клон легитимного crypto-js с тем же описанием и именем автора. Сначала чистая версия 4.2.0, затем вредоносная 4.2.1 с постустановочным скриптом.</p><h2>Хронология атаки</h2><p>Все события — по UTC, 30–31 марта 2026 года:</p><ul><li><b>30 марта, 05:57</b> — атакующий публикует чистый plain-crypto-js@4.2.0 для формирования истории публикаций</li><li><b>30 марта, 23:59</b> — выходит вредоносный plain-crypto-js@4.2.1 с постустановочным скриптом</li><li><b>31 марта, 00:05</b> — <a href="https://socket.dev/blog/axios-npm-package-compromised">Socket</a> детектирует вредоносный пакет — через 6 минут после публикации</li><li><b>31 марта, 00:21</b> — публикуется axios@1.14.1 со скомпрометированного аккаунта</li><li><b>31 марта, 01:00</b> — публикуется axios@0.30.4 — обе ветки поражены за 39 минут</li><li><b>31 марта, ~03:15</b> — npm удаляет обе вредоносные версии, dist-tag latest откатывается к 1.14.0</li><li><b>31 марта, 04:26</b> — npm публикует заглушку безопасности для plain-crypto-js — пакет заблокирован</li></ul><p>Итого: <b>axios@1.14.1</b> был доступен около 2 часов 53 минут, <b>axios@0.30.4</b> — около 2 часов 15 минут.</p><h2>Что делает вредоносный код</h2><p>В исходном коде axios изменений нет — добавлена только зависимость plain-crypto-js@^4.2.1. Сам пакет никогда не импортируется в коде; он существует исключительно для запуска postinstall-хука при npm install.</p><h3>Дроппер setup.js</h3><p>Файл setup.js весит 4209 байт и использует двухслойную обфускацию: сначала reversed Base64-декодирование (с подстановкой символов), затем XOR-шифр с ключом OrDeR_7077. Дроппер определяет ОС и загружает платформенный payload с C2-сервера.</p><h3>Payload по платформам</h3><p><b>macOS:</b> AppleScript-дроппер скачивает бинарный RAT с C2-сервера и сохраняет его как /Library/Caches/com.apple.act.mond — замаскирован под системный демон Apple. Запускается через /bin/zsh.</p><p><b>Windows:</b> PowerShell копируется в %PROGRAMDATA%\wt.exe (маскировка под Windows Terminal). VBScript-дроппер запускает скрытый PowerShell-скрипт с обходом Execution Policy.</p><p><b>Linux:</b> Python-скрипт RAT скачивается в /tmp/ld.py и запускается через nohup в фоновом режиме.</p><h3>Возможности RAT</h3><p>При первом запуске RAT отправляет на C2-сервер «отпечаток» системы: hostname, имя пользователя, версию ОС, архитектуру CPU, часовой пояс, время установки ОС и загрузки, список процессов, а также содержимое директорий /Applications, ~/Library и ~/Application Support. Затем RAT опрашивает C2 каждые <b>60 секунд</b>. Поддерживаемые команды:</p><ul><li><b>runscript</b> — выполнение произвольных shell-команд и Python-кода</li><li><b>peinject</b> — загрузка и запуск дополнительных бинарных payload</li><li><b>rundir</b> — перечисление содержимого директорий</li><li><b>kill</b> — самоуничтожение процесса</li></ul><h3>Антифорензика</h3><p>После выполнения дроппер удаляет себя (setup.js) и подменяет package.json чистой версией без postinstall-секции. При инспекции node_modules после заражения следов вредоносного скрипта не остаётся.</p><p><b>Это означает, что проверка node_modules постфактум бесполезна.</b> Единственный надёжный способ определить компрометацию — проверить package-lock.json на наличие вредоносных версий и IoC-пути на файловой системе (см. ниже).</p><h2>Кто обнаружил</h2><p>Атаку независимо обнаружили несколько компаний: <a href="https://socket.dev/blog/axios-npm-package-compromised">Socket</a> задетектировал вредоносный plain-crypto-js через 6 минут после публикации. <a href="https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan">StepSecurity</a> подтвердил компрометацию через AI Package Analyst и инструментирование GitHub Actions-раннера. Детальный технический разбор также <a href="https://safedep.io/axios-npm-supply-chain-compromise">опубликовала SafeDep</a>.</p><h2>Индикаторы компрометации (IoC)</h2><p><b>Файловая система:</b></p><p><b>Сеть:</b></p><p><b>SHA256 хеши:</b></p><h2>Что делать прямо сейчас</h2><p>Быстрая проверка — есть ли вредоносные версии в вашем lockfile:</p><p>Если команда ничего не вернула — ваш проект не затронут. Если нашлись совпадения:</p><ol><li>Откатить axios: npm install axios@1.14.0 (для 1.x) или npm install axios@0.30.3 (для 0.x) — это автоматически удалит plain-crypto-js</li><li>Проверить IoC-пути на файловой системе для вашей ОС (см. выше)</li><li>Заблокировать C2 на сетевом уровне: sfrclak[.]com / 142.11.206.73</li><li><b>Ротировать все секреты</b> на затронутых системах: npm-токены, SSH-ключи, API-ключи, переменные из .env</li><li>Проверить CI/CD-логи за 30–31 марта — ротировать все secrets из затронутых пайплайнов</li><li>Перейти на npm ci --ignore-scripts в CI/CD как постоянную политику</li></ol><p>Если обнаружены артефакты RAT — <b>считайте систему полностью скомпрометированной</b> и пересоберите из заведомо чистого состояния.</p><h2>Защита от транзитивных зависимостей</h2><p>Если axios используется как транзитивная зависимость (его тянет другой пакет), прямой npm install axios@1.14.0 не поможет — нужно зафиксировать версию через overrides в package.json:</p><p>Поле overrides работает в npm 8+, resolutions — в Yarn.</p><h2>Выводы</h2><p>Инцидент с axios — один из самых масштабных supply chain атак в npm по потенциальному охвату: при 100 миллионах загрузок в неделю даже 3-часовое окно доступности вредоносных версий критично. Для сравнения: компрометация <b>event-stream</b> в 2018 году затронула пакет с 2 миллионами загрузок в неделю.</p><p>Скорость реакции экосистемы впечатляет: Socket обнаружил вредоносный plain-crypto-js через 6 минут, npm отозвал пакеты менее чем за 3 часа. Но сам факт, что атакующему хватило одного украденного токена для публикации от имени доверенного мейнтейнера, ставит вопрос о необходимости обязательного использования OIDC Trusted Publishing для критической инфраструктуры npm.</p><p>Проверьте свои зависимости, ротируйте секреты и убедитесь, что ваш CI/CD-пайплайн защищён от автоматической установки непроверенных версий.</p>]]></content:encoded>
    </item>
    <item>
      <title>Спецификация ECMAScript заставляет V8 раскрывать, запущен ли DevTools — и это нельзя пропатчить</title>
      <link>https://tproger.ru/news/specifikaciya-ecmascript-zastavlyaet-v8-raskryvat--zapushhen-li-dev</link>
      <comments>https://tproger.ru/news/specifikaciya-ecmascript-zastavlyaet-v8-raskryvat--zapushhen-li-dev?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Михайлишин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/specifikaciya-ecmascript-zastavlyaet-v8-raskryvat--zapushhen-li-dev</guid>
      <description><![CDATA[<p>Исследователь нашёл два способа детектировать Puppeteer/Playwright через CDP. Второй вектор — Proxy в прототипе — нельзя закрыть без изменений в ECMAScript. Разбираем все 4 слоя C++.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/specifikaciya-ecmascript-zastavlyaet-v8-raskryvat--zapushhen-li-dev">Спецификация ECMAScript заставляет V8 раскрывать, запущен ли DevTools — и это нельзя пропатчить</a>»</p>]]></description>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Информационная безопасность]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 30 Mar 2026 15:04:10 GMT</pubDate>
      <content:encoded><![CDATA[<p>Вы запустили Puppeteer или Playwright, чтобы автоматизировать браузер. Ваш скрипт аккуратно маскируется: правильный User-Agent, реальные размеры окна, никаких следов headless-режима. Но есть один вектор, который вы не можете закрыть — и именно он сдаёт вас с потрохами.</p><p>Исследователь под ником Sveba <a href="https://svebaa.github.io/personal/blog/cdp-fingerprinting/">опубликовал</a> разбор двух способов детектировать headless-браузеры через Chrome DevTools Protocol (CDP). Оба метода работают в одну строку JavaScript, не требуют никаких разрешений и срабатывают синхронно — без замеров тайминга. Второй способ по состоянию на март 2026 года не закрыт и, по заявлению автора, не может быть закрыт без изменения поведения ECMAScript.</p><p><b>Ключевые выводы:</b><br />— Оба сигнала срабатывают, когда активен CDP Runtime domain — то есть одновременно при открытых DevTools и при работе Puppeteer/Playwright<br />— Сигнал 1 (май 2025): кастомный getter на <b>stack</b> у объекта Error — частично закрыт патчем V8, но обходится через прототипы<br />— Сигнал 2 (март 2026): Proxy в прототипе объекта + console.groupEnd() — не закрыт, корень проблемы в спецификации ECMAScript<br />— Детектирование синхронное, без тайминга, без permissions — один вызов console.*<br />— Runtime.enable одинаково активируется и DevTools UI, и автоматизацией</p><h2>Что такое CDP и почему он нас выдаёт</h2><p>Chrome DevTools Protocol (CDP) — это API, через который DevTools общается с браузером. Когда вы открываете инструменты разработчика, браузер активирует домен Runtime внутри V8. То же самое происходит, когда Puppeteer или Playwright вызывают Runtime.enable — с точки зрения V8 это неотличимо.</p><p>В режиме Runtime.enable инспектор подписывается на все вызовы console.* и начинает сериализовывать аргументы для отображения в панели DevTools. Именно эта сериализация и создаёт side effects, которые становятся детектируемыми.</p><h2>Сигнал 1: кастомный getter на stack (май 2025, частично закрыт)</h2><p>Первый вектор, который широко использовался среди вендоров bot-detection, выглядит так:</p><p>В нормальном браузере console.debug(e) просто логирует объект. Никто не читает .stack, getter не вызывается, detected остаётся false.</p><p>При активном Runtime.enable инспектор перехватывает вызов и прогоняет аргумент через функцию descriptionForError() в v8/src/inspector/value-mirror.cc. Эта функция обращается к свойствам name, stack и message через object-&gt;Get() — а C++ API V8 при обращении к свойству честно вызывает getter, если тот определён. Getter срабатывает. detected становится true.</p><h3>Патч V8 и его неполнота</h3><p>В мае 2025 года в V8 <a href="https://chromium.googlesource.com/v8/v8/">приземлились два коммита</a> (7 и 9 мая), которые ввели обёртку getErrorProperty(). Перед чтением свойства она проверяет ScriptId getter'а: если у него есть реальный ScriptId (то есть getter написан на JavaScript, а не является нативным C++-аксессором) — читать отказывается.</p><p>Проблема в том, что патч срабатывает только если GetOwnPropertyDescriptor на объекте возвращает дескриптор. Если нет — функция идёт по альтернативной ветке и вызывает object-&gt;Get() напрямую, до всякой проверки ScriptId. Getter, определённый не как собственное свойство экземпляра, а через прототип — обходит патч полностью.</p><h2>Сигнал 2: Proxy в прототипе + console.groupEnd (март 2026, не закрыт)</h2><p>Второй вектор аккуратнее и глубже:</p><p>obj — это не Proxy. Это обычный объект, у которого прототипом является Proxy. typeof obj === "object". Именно эта деталь позволяет обойти защиту.</p><h3>Четыре слоя C++, которые приводят к срабатыванию trap</h3><p>Автор прошёлся по всей цепочке выполнения в debug-сборке V8 и задокументировал каждый переход:</p><ul><li><b>Слой A — инспектор генерирует preview безусловно.</b> Функция V8ConsoleMessage::wrapArguments вызывается для каждого аргумента любого console.*-метода с флагом generatePreview = true. console.groupEnd по спецификации не принимает аргументов — но JavaScript позволяет передавать аргументы любой функции, и V8 их исправно обрабатывает.</li><li><b>Слой B — проверка Proxy работает только на поверхности.</b> В buildObjectPreviewInternal есть цикл, который снимает обёртку Proxy через while(value-&gt;IsProxy()). Поскольку obj сам не является Proxy, цикл не выполняется ни разу. Инспектор считает объект «пассивными данными» и передаёт его дальше.</li><li><b>Слой C — DebugPropertyIterator обходит цепочку прототипов заранее.</b> При создании итератора через DebugPropertyIterator::Create() он сразу накапливает ключи — не лениво по запросу, а при конструировании. Итератор проходит по всей цепочке прототипов: объект без свойств → переход к прототипу (нашему Proxy) → запрос ключей.</li><li><b>Слой D — спецификация ECMAScript требует вызова trap.</b> Когда KeyAccumulator::GetKeys наконец добирается до Proxy, выбора нет: спецификация (§10.5.11 [[OwnPropertyKeys]]) прямо требует вызвать ownKeys trap, если он определён. V8 вызывает его через Execution::Call — граница C++/JavaScript пересекается. detected = true.</li></ul><h2>Корень проблемы: три разумных решения создают дыру</h2><p>Автор подчёркивает: ни одна отдельная строка кода не является ошибкой. Уязвимость — это пересечение трёх архитектурных решений:</p><ol><li><b>Безусловная генерация preview.</b> Инспектор сериализует аргументы всех console.*-методов, даже тех, которые по спецификации аргументов не принимают.</li><li><b>Неполное раскрытие Proxy.</b> Проверка IsProxy() в двух местах смотрит только на сам объект, но не на его прототипы. Полная защита потребовала бы обхода всей цепочки прототипов при каждом вызове — дорогая операция для того, что сейчас является однострочной проверкой.</li><li><b>Жадный сбор ключей при создании итератора.</b> DebugPropertyIterator собирает все ключи заранее, включая из прототипов. Ленивый подход позволил бы вообще не трогать прототипы, если их ключи в итоге не нужны.</li></ol><blockquote>Паттерн — достичь управляемого пользователем trap через код инспектора, который защищает только непосредственный аргумент — вряд ли уникален для этих двух поверхностей.</blockquote><h2>Что это значит для автоматизации браузеров</h2><p>Оба сигнала срабатывают в любой среде, где активен Runtime.enable: при открытых DevTools, при использовании <a href="https://pptr.dev/">Puppeteer</a>, <a href="https://playwright.dev/">Playwright</a> или любого другого инструмента, работающего через CDP. Среды без Runtime.enable — не детектируются.</p><p>Детектирование синхронное — никаких таймингов, никаких разрешений, никаких browser extensions. Один вызов console.groupEnd() с правильно подготовленным объектом даёт однозначный ответ.</p><p>Закрыть второй сигнал на стороне V8 сложно: любое решение либо потребует дорогого обхода цепочки прототипов, либо изменит поведение инспектора при работе с Proxy. Спецификация ECMAScript не оставляет пространства для манёвра — trap должен вызываться.</p><h2>Частые вопросы</h2><h3>Что такое CDP Runtime domain и зачем его активируют?</h3><p>Chrome DevTools Protocol (CDP) — это API для взаимодействия с внутренностями браузера. Домен Runtime отвечает за исполнение JavaScript и отображение объектов. DevTools активирует его, чтобы показывать переменные в консоли. Puppeteer и Playwright активируют его автоматически для управления страницей и выполнения скриптов.</p><h3>Можно ли заблокировать детектирование на стороне Puppeteer/Playwright?</h3><p>Для первого сигнала есть частичные обходы (не определять getter как собственное свойство Error, использовать прототипы). Для второго сигнала — нет. Вызов ownKeys trap при обращении к ключам Proxy является требованием спецификации ECMAScript и не может быть пропущен движком без нарушения совместимости.</p><h3>Это касается только headless Chrome?</h3><p>Нет. Сигналы срабатывают всякий раз, когда активен Runtime.enable — то есть в том числе при открытых DevTools в обычном браузере. Разница в том, что bot-detection интересует именно автоматизация, а не пользователи с открытой консолью.</p><h3>Был ли сигнал 2 раскрыт вендорам bot-detection?</h3><p>Статья опубликована в личном блоге исследователя. Отдельного responsible disclosure в Chromium не упоминается. Автор ссылается на пост <a href="https://castle.io/blog/why-a-classic-cdp-bot-detection-signal-suddenly-stopped-working/">castle.io</a>, который задокументировал первый сигнал и майский патч 2025 года — именно он послужил вдохновением для этого исследования.</p><h2>Выводы</h2><p>Исследование наглядно показывает, как три независимых, разумно выглядящих инженерных решения образуют детектируемый side effect. V8 закрыл первый вектор патчем в мае 2025 года — но неполно. Второй вектор остаётся открытым и, по мнению автора, не может быть устранён без изменений либо в архитектуре инспектора, либо в поведении ECMAScript.</p><p>Если вы занимаетесь разработкой инструментов автоматизации или browser fingerprinting — <a href="https://svebaa.github.io/personal/blog/cdp-fingerprinting/">полный разбор с исходниками V8</a> стоит прочитать целиком. Обсуждение развернулось в <a href="https://www.reddit.com/r/ReverseEngineering/">r/ReverseEngineering</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Разработчик построил DOOM на чистом CSS — можно поиграть прямо в браузере</title>
      <link>https://tproger.ru/news/razrabotchik-postroil-doom-na-chistom-css---rendering-bez-edinoj-s</link>
      <comments>https://tproger.ru/news/razrabotchik-postroil-doom-na-chistom-css---rendering-bez-edinoj-s?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Михайлишин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/razrabotchik-postroil-doom-na-chistom-css---rendering-bez-edinoj-s</guid>
      <description><![CDATA[<p>Разработчик построил DOOM целиком на CSS transforms: стены через hypot(), углы через atan2(), анимации на transitions. Логика на JS, графика — чистый CSS.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/razrabotchik-postroil-doom-na-chistom-css---rendering-bez-edinoj-s">Разработчик построил DOOM на чистом CSS — можно поиграть прямо в браузере</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Разработка игр]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 29 Mar 2026 05:33:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>Веб-разработчик Нильс Ленхер (Niels Leenheer) построил полноценный DOOM на чистом CSS. Каждая стена, пол, бочка и имп — это &lt;div&gt;, позиционированный в 3D-пространстве через CSS transforms. Игровая логика работает на JavaScript, но <b>весь рендеринг — исключительно CSS</b>.</p><p><b>Поиграть прямо в браузере:</b> <a href="https://cssdoom.wtf/">cssdoom.wtf</a> — полноценный первый уровень DOOM, целиком на CSS. Chrome или Safari, WASD + мышь.</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-03-29/89e75765-8bbd-4917-a64f-221772d95ffb.webp" alt="CSS DOOM — DOOM работающий на чистом CSS в браузере" /><figcaption>DOOM на CSS — все стены, полы и спрайты отрисованы через CSS transforms</figcaption></figure><p>Ленхер — автор нескольких экспериментальных проектов, включая DOOM на осциллографе 1980-х годов. Новый проект использует координаты из оригинального WAD-файла (формат хранения данных карт DOOM) 1993 года и современные CSS-функции: @property, shape() и clip-path (свойство CSS для обрезки элемента по произвольному контуру) с правилом evenodd (правило заливки SVG-путей: закрашиваются области, ограниченные нечётным числом линий). <a href="https://nielsleenheer.com/articles/2026/css-is-doomed/">Исходная статья</a> набрала 306 очков и 67 комментариев на <a href="https://news.ycombinator.com/item?id=47557960">Hacker News</a>, а код <a href="https://github.com/NielsLeenheer/cssDOOM">опубликован на GitHub</a>.</p><p>— CSS умеет считать тригонометрию: ширина стены через hypot(), угол через atan2()</p><p>— Камеры в CSS нет — двигается весь мир вокруг игрока через translate3d с инвертированными координатами</p><p>— Двери, лифты и снаряды анимируются через CSS transitions и CSS animations без JS animation loop</p><p>— Освещение секторов работает через filter: brightness(), наследуемый по каскаду</p><p>— @property регистрирует custom properties как числа, что позволяет анимировать их плавно</p><h2>Как это работает — CSS как 3D-движок</h2><p>Сцена строится из нескольких тысяч &lt;div&gt;-элементов. Каждый получает сырые координаты из оригинального WAD-файла DOOM в виде CSS custom properties: две пары x/y-координат, высоту пола и потолка. CSS сам вычисляет всё остальное.</p><h3>Тригонометрия в CSS</h3><p>Ширина стены — это расстояние между двумя точками, которое вычисляется по теореме Пифагора через функцию hypot(). Угол поворота — это арктангенс, вычисляемый через atan2(). Обе функции добавлены в CSS специально для подобных расчётов.</p><p>JavaScript передаёт сырые данные DOOM. CSS считает тригонометрию. Это разделение — ключевой архитектурный принцип проекта.</p><h3>Движение мира вместо камеры</h3><p>В CSS нет понятия камеры. Вместо этого используется классический трюк: перемещается весь мир в направлении, противоположном движению игрока. JavaScript задаёт всего четыре custom property — --player-x, --player-y, --player-z и --player-angle. CSS делает остальное:</p><p>Обратите внимание на инвертированные знаки: если игрок шагает вперёд — мир сдвигается назад. Если игрок поднимается по лестнице — лестница опускается вниз.</p><h2>Полы, текстуры и клипы</h2><p>DOM-элементы по умолчанию вертикальны — существуют в плоскости x/y. Чтобы превратить &lt;div&gt; в пол, достаточно rotateX(90deg) — элемент «ложится» горизонтально.</p><h3>Сложные формы через clip-path</h3><p>Секторы DOOM — это произвольные многоугольники: L-образные комнаты, неправильные формы, закруглённые коридоры. Для них используется clip-path с polygon(). Для секторов с отверстиями (колонны, платформы, окна) — clip-path с path() и правилом заливки evenodd.</p><p>Однако polygon() работает с процентами, а path() требует координат в пространстве CSS — что нарушает чистоту разделения. Решение нашлось в новой функции shape(), которая поддерживает проценты и evenodd одновременно.</p><h3>Выравнивание текстур</h3><p>Два соседних сектора с одинаковой текстурой пола должны бесшовно стыковаться. Поскольку background-image повторяется бесконечно, достаточно выровнять начало паттерна по мировым координатам:</p><p>Каждый сектор ссылается на одну и ту же текстурную сетку — независимо от того, где расположен его &lt;div&gt;. В результате переход между секторами выглядит бесшовным.</p><h2>Анимации — двери, снаряды, спрайты</h2><p>Прежде чем перейти к деталям — важный термин: <b>billboarding</b>. Это техника, при которой 2D-спрайт всегда повёрнут лицом к камере, независимо от положения игрока. В DOOM все враги, бочки и снаряды — billboarded-элементы.</p><h3>Двери и лифты на CSS transitions</h3><p>Открытие двери в DOOM — это поднятие потолка сектора. В CSS все элементы двери группируются в контейнер, а анимация запускается переключением атрибута data-state:</p><p>Никакого JS animation loop — достаточно установить атрибут состояния на элементе. CSS transitions берут анимацию на себя.</p><p>С лифтами всё сложнее: игрок едет вместе с платформой, поэтому --player-z должен обновляться синхронно с CSS transition. Но --player-z управляется из JavaScript. Поэтому для лифтов JS вынужден использовать функцию cubic ease-in-out (t² * (3 - 2t)), чтобы оставаться в синхронизации с CSS-анимацией — это признанное ограничение текущей архитектуры.</p><h3>Снаряды на CSS animations</h3><p>Ракеты и файерболы импов — это billboarded &lt;div&gt;. При создании снаряда JavaScript вычисляет конечную точку и длительность полёта, а CSS анимирует перемещение от точки A к точке B:</p><p>Благодаря разделению translate и rotate как отдельных CSS-свойств анимация управляет только позицией, а rotate реагирует на --player-angle — файербол продолжает смотреть на камеру при движении игрока.</p><p>Параллельно с CSS-анимацией игровой цикл на JavaScript рассчитывает позицию снаряда тем же линейным методом — для обнаружения столкновений (collision detection). Когда снаряд попадает в стену, пол, игрока или врага, JS удаляет элемент посреди полёта и порождает взрыв.</p><h3>Спрайты с billboarding и mirroring</h3><p>Враги в DOOM — 2D-спрайты, которые всегда повёрнуты к камере (billboarding). Оригинальная игра хранит 5 уникальных наборов кадров из 8 ракурсов — ракурсы 6-8 это зеркальные отражения 2-4. CSS воспроизводит это через scaleX(-1):</p><p>Анимация ходьбы — это spritesheet (набор кадров анимации в одном изображении) со сдвигом background-position-x через steps(). При атаке или смерти JavaScript меняет data-state, и CSS переключается на другой фрагмент spritesheet.</p><p>Одна из проблем: изначально все враги маршировали идеально в ногу — левая нога каждого зомби касалась земли в один и тот же момент. Решение — случайный animation-delay, задаваемый из JavaScript. Когда в браузерах появится CSS-функция random(), этот параметр можно будет перенести целиком в CSS.</p><h2>Освещение и @property</h2><p>DOOM хранит уровень освещённости для каждого сектора. В CSS это реализовано через custom property --light на контейнере сектора:</p><p>Каскад CSS идеально подходит для этого: все стены, полы и спрайты в тёмном секторе автоматически затемняются — без необходимости устанавливать яркость на каждом элементе отдельно. Мерцающие лампы — это @keyframes-анимации переменной --light.</p><p>Но анимировать custom properties можно только если они зарегистрированы через @property. Без этого браузер воспринимает их как строки:</p><p>Эта регистрация позволяет плавно анимировать --player-z — например, при падении игрока с уступа CSS сам создаёт плавный переход.</p><h2>Что делает JavaScript, а что CSS</h2><p>Автор чётко разделил ответственности:</p><ul><li><b>JavaScript</b> — игровой цикл, состояние игры, коллизии, порождение и удаление DOM-элементов, установка custom properties и data-атрибутов</li><li><b>CSS</b> — все 3D-трансформации, вычисление геометрии (тригонометрия), анимации дверей и снарядов, освещение, CSS рендеринг спрайтов</li></ul><p>По словам Ленхера, game loop на JavaScript — наименее интересная часть проекта. Исходный код DOOM на C доступен публично уже много лет, поэтому для портирования он <a href="https://nielsleenheer.com/articles/2026/css-is-doomed/">использовал Claude</a>, чтобы сосредоточиться на CSS-рендеринге.</p><h2>Выводы</h2><blockquote>Я хотел найти границы того, на что способен браузер. Увидеть, насколько мощным стал современный CSS. И потому что это DOOM. На CSS. Вам правда нужна ещё какая-то причина?</blockquote><p>Проект демонстрирует, как далеко продвинулся CSS за 30 лет. Функции вроде hypot(), atan2() и @property превращают каскадные таблицы стилей в полноценный инструмент для 3D-вычислений. Разделение на JS game loop и CSS-рендеринг оказалось не только возможным, но и элегантным.</p><p>Исходный код: <a href="https://github.com/NielsLeenheer/cssDOOM">GitHub</a>. Подробный разбор: <a href="https://nielsleenheer.com/articles/2026/css-is-doomed/">CSS is DOOMed</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Ваш debounce вас обманывает — и вот почему</title>
      <link>https://tproger.ru/translations/vaw-debounce-vas-obmanyvaet---i-vot-pochemu</link>
      <comments>https://tproger.ru/translations/vaw-debounce-vas-obmanyvaet---i-vot-pochemu?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Михайлишин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/vaw-debounce-vas-obmanyvaet---i-vot-pochemu</guid>
      <description><![CDATA[<p>Debounce снижает частоту вызовов, но не контролирует сетевые запросы. Разбираем race conditions и ошибки, исправляем через AbortController и retry.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/vaw-debounce-vas-obmanyvaet---i-vot-pochemu">Ваш debounce вас обманывает — и вот почему</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 28 Mar 2026 14:02:07 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://www.geeksforgeeks.org/javascript/debouncing-in-javascript/">Debounce</a> — один из тех паттернов, которые фронтенд-разработчик узнаёт в самом начале карьеры и использует всю оставшуюся жизнь.</p><p>По сути debounce делает одну простую вещь: собирает серию вызовов и превращает их в один вызов после паузы. Отлично подходит для «шумных» UI-событий.</p><p>Самый типичный пример — автодополнение в поиске. Но тот же паттерн работает для обработки resize, scroll, live-валидации, фильтров и хуков аналитики.</p><p>Классическая реализация выглядит так:</p><p>Выглядит дисциплинированно. Ощущается эффективно. Быстро доезжает до прода.</p><p>И вот тут начинается обман.</p><p>Проблема не в самом debounce. Проблема в связке «debounce + fetch», когда в уравнение входит реальная сеть.</p><p>Debounce создаёт ощущение, что запросы «под контролем». Но он не контролирует жизненный цикл запроса: порядок ответов, отмену устаревших запросов, поведение при ошибках.</p><p>Именно поэтому в продакшене debounce «врёт»: UI выглядит плавно, а сетевой слой по-прежнему хрупкий.</p><p>В этой статье мы оставим debounce для того, в чём он хорош (сглаживание UI), и укрепим сетевой слой отменой запросов, повторными попытками и корректной обработкой ошибок.</p><blockquote><b>Ключевые выводы:</b><br />— Debounce — это паттерн UI, а не паттерн работы с сетью<br />— Он гарантирует только одно: «я не буду вызывать функцию слишком часто»<br />— Порядок ответов, отмена устаревших запросов и обработка ошибок — это то, что вам придётся решать отдельно<br />— AbortController отменяет устаревшие запросы на уровне сети<br />— Повторные попытки с экспоненциальной задержкой спасают от транзиентных ошибок сервера</blockquote><h2>Проблема 1 — гонка запросов (race conditions)</h2><p>На локальной машине всё летает. Но в продакшене сеть непредсказуема: запросы могут приходить с разной задержкой, и нет никакой гарантии, что ответы придут в том же порядке, в каком были отправлены.</p><p>Представьте: пользователь печатает 12345678. Debounce пропускает запросы для 1234567 и 12345678. Ответ на 1234567 задерживается на сервере и приходит <i>после</i> ответа на 12345678. UI обновляется последним пришедшим ответом — и показывает устаревшие данные.</p><p>Это классическая гонка запросов, и debounce сам по себе её не предотвращает.</p><h3>Решение — AbortController</h3><p>Нам нужно гарантировать, что обрабатывается только ответ на последний запрос, а все предыдущие — отменяются. <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController">AbortController</a> — это браузерный API, который позволяет отменять fetch-запросы. Создаём контроллер, передаём его signal в fetch, и вызываем abort(), когда нужно отменить запрос.</p><p>Что изменилось:</p><ul><li>Перед каждым запросом мы отменяем предыдущий через abort() и создаём новый контроллер</li><li>В блоке catch проверяем, является ли ошибка AbortError — это ожидаемое поведение, а не реальный сбой</li></ul><p>Результат: в обычном потоке только последний запрос из серии нажатий доходит до конца. Предыдущие отменяются на уровне сети, а не просто игнорируются после получения ответа.</p><h2>Проблема 2 — сетевые ошибки</h2><p>Сеть непредсказуема не только по задержкам, но и по надёжности. Иногда запрос, который мог бы пройти при повторной попытке, просто падает. Причины: кратковременная перегрузка сервера, пики нагрузки, таймауты базы данных.</p><h3>fetch не бросает исключение при HTTP-ошибках</h3><p>Это одна из главных ловушек нативного fetch: он отклоняет промис только при сетевых сбоях (нет соединения). Коды 4xx и 5xx — это «успешные» ответы с точки зрения fetch. Если сервер вернёт 500, ваш код радостно вызовет response.json() и получит undefined вместо данных.</p><p>Исправляем проверкой response.ok:</p><p>Теперь 500-я ошибка выбрасывает исключение до того, как мы пытаемся разобрать тело ответа. Блок catch обработает её корректно.</p><h3>Повторные попытки с экспоненциальной задержкой</h3><p>Но простая проверка — это только начало. В реальном приложении стоит добавить автоматические повторные попытки для транзиентных ошибок. Если запрос упал из-за временной проблемы, лучше попробовать ещё раз с нарастающей задержкой, чем сразу показывать ошибку пользователю.</p><p>Писать логику повторов вручную — это циклы, счётчики попыток, тайминги, и всё это должно корректно работать с отменой. Нетривиально и неинтересно. Воспользуемся библиотекой <a href="https://github.com/nickersoft/fetchkit">@fetchkit/ffetch</a> — это тонкая обёртка над fetch, которая решает именно эту задачу.</p><p>Есть и альтернативы: <a href="https://github.com/sindresorhus/ky">ky</a>, <a href="https://github.com/axios/axios">axios</a> или собственная обёртка. ffetch выбран за совместимый с fetch API и корректную работу с AbortController при повторных попытках.</p><p>Что нам это даёт:</p><ul><li>retries: 3 — при 500-й ошибке библиотека повторяет запрос до 3 раз</li><li>shouldRetry — повторяем только при 5xx; всё остальное (сетевая ошибка, отмена) пробрасывается сразу</li><li>throwOnHttpError: true — автоматически бросает исключение на HTTP-ошибки, не нужна ручная проверка response.ok</li><li>Задержка между повторами учитывает AbortController — если abort() вызван во время ожидания, повтор немедленно прекращается</li></ul><p>Последний пункт особенно важен. Без этого отмена запроса в середине серии повторов убила бы текущий fetch, но оставила бы таймер — и следующая попытка сразу бы упала с AbortError.</p><h2>Полное решение</h2><p>Собираем всё вместе: debounce для UI-сглаживания, AbortController для отмены устаревших запросов, ffetch для повторных попыток и автоматической обработки HTTP-ошибок.</p><p>Каждый слой отвечает за своё: debounce снижает частоту вызовов, AbortController гарантирует, что обрабатывается только актуальный запрос, а ffetch добавляет устойчивость к транзиентным сбоям.</p><h2>Частые вопросы</h2><h3>Зачем AbortController, если debounce и так снижает количество запросов?</h3><p>Debounce снижает <i>частоту</i> вызовов, но не контролирует, что происходит с уже отправленными запросами. Если два запроса ушли один за другим, более ранний может вернуться позже — и перезаписать актуальные данные. AbortController отменяет устаревший запрос на уровне сети, а не просто игнорирует ответ.</p><h3>Можно ли обойтись без сторонней библиотеки для повторов?</h3><p>Да, можно написать retry-логику вручную. Но это циклы, счётчики, экспоненциальная задержка и корректная обработка отмены. В продакшен-коде проще использовать готовое решение — ffetch, ky или axios — чтобы не изобретать велосипед и не допускать ошибок в edge-кейсах.</p><h3>Работает ли этот подход с React / Vue / Angular?</h3><p>Да. AbortController и retry-логика — это чистый JavaScript, независимый от фреймворка. В React, например, AbortController часто используется в useEffect для отмены запросов при размонтировании компонента. Принцип тот же: debounce для UI, отмена и повторы для сетевого слоя.</p><h2>Выводы</h2><p>Debounce — не проблема. Проблема — считать его полным решением для управления сетевыми запросами, когда он контролирует только одно измерение: частоту вызовов.</p><p>Debounce — это паттерн UI, а не паттерн работы с сетью. Чтобы построить надёжное приложение, нужно дополнить его управлением жизненным циклом запросов:</p><ul><li>Отмена устаревших запросов через AbortController</li><li>Повторные попытки с экспоненциальной задержкой для транзиентных сбоев</li><li>Корректная обработка HTTP-ошибок (проверка response.ok или автоматический проброс через библиотеку)</li></ul><p>Тогда UI будет не только отзывчивым, но и точным — даже при непредсказуемых сетевых условиях.</p><p><i>Адаптированный перевод статьи <a href="https://blog.gaborkoos.com/posts/2026-03-28-Your-Debounce-Is-Lying-to-You/">Your Debounce Is Lying to You</a> Габора Кооша (Gabor Koos).</i></p>]]></content:encoded>
    </item>
    <item>
      <title>Headless WordPress: архитектура с Next.js, GraphQL и Cloudflare</title>
      <link>https://tproger.ru/articles/headless-wordpress--arhitektura-s-next-js--graphql-i-cloudflare</link>
      <comments>https://tproger.ru/articles/headless-wordpress--arhitektura-s-next-js--graphql-i-cloudflare?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Пехота]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/headless-wordpress--arhitektura-s-next-js--graphql-i-cloudflare</guid>
      <description><![CDATA[<p>Разбираем headless WordPress на практике: Next.js, Cloudflare Workers, GraphQL и архитектура быстрых и масштабируемых сайтов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/headless-wordpress--arhitektura-s-next-js--graphql-i-cloudflare">Headless WordPress: архитектура с Next.js, GraphQL и Cloudflare</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[PHP]]></category>
      <category><![CDATA[WordPress]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Cloudflare]]></category>
      <category><![CDATA[CMS]]></category>
      <category><![CDATA[CDN]]></category>
      <category><![CDATA[CI/CD]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 26 Mar 2026 11:29:45 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Почему WordPress?</h2><p>WordPress часто не любят backend-разработчики, и у каждого на это есть свои причины. Кому-то не нравится функциональный стиль разработки, кто-то критикует form-builder и экосистему плагинов. У других WordPress как CMS и PHP как язык программирования до сих пор ассоциируются со стереотипами 10–15-летней давности - будто они устарели и уступают современным технологиям.</p><p>При этом реальность такова, что и PHP, и WordPress - отличные и современные инструменты, которые очень хорошо выполняют свои задачи. Опустим PHP - статья не об этом. Что же можно сказать про WordPress как про продукт и как CMS?</p><p>WordPress по‑прежнему остаётся самой популярной системой управления контентом. Согласно данным команды WordPress, платформа обслуживает более 43% всех веб-сайтов и занимает долю в 61% на рынке CMS. Также статистика показывает, что WordPress используется примерно на 59% сайтов, где известна CMS (это около 42% всего веба).</p><p>Данные были взяты из официального блога WordPress и сайта w3techs.com:</p><ul><li><a href="https://wordpress.com/blog/2025/04/17/wordpress-market-share/" rel="nofollow">https://wordpress.com/blog/2025/04/17/wordpress-market-share/ </a></li><li><a href="https://w3techs.com/technologies/overview/content_management" rel="nofollow">https://w3techs.com/technologies/overview/content_management</a></li><li><a href="https://w3techs.com/technologies/details/cm-wordpress" rel="nofollow">https://w3techs.com/technologies/details/cm-wordpress</a></li></ul><p>При этом, традиционный WordPress объединяет CMS, шаблоны на PHP и монолитные темы. Такая связка усложняет разработку с использованием современных JS фреймворков, а также затрудняет независимое масштабирование фронтенда и бэкенда, и оптимизацию производительности и безопасности. Жёсткая связка страниц, устаревшие PHP‑функции и не самый удобный девелоперский опыт часто заставляют команды искать альтернативы.</p><p>Headless WordPress решает эти проблемы: CMS становится админ-панелью для управления контентом, а отдельный фронтенд отвечает за UI. Такое разделение обязанностей даёт несколько преимуществ: четкое разделение ответственности, независимое масштабирование интерфейса и CMS, упрощенную локальную разработку и CI/CD. CMS превращается в API‑ориентированное хранилище, а современные фреймворки вроде Next.js берут на себя маршрутизацию и рендеринг.</p><h2>Headless WordPress с использованием WPGraphQL</h2><p>Чтобы использовать WordPress как headless‑CMS, нужен API. Также есть интересный пост про headless wordpress в их <a href="https://wordpress.com/blog/2025/03/20/headless-wordpress/" rel="nofollow">официальном блоге</a>.</p><p>В WordPress из коробки есть REST API, но для frontend и mobile приложений часто удобнее использовать GraphQL. <a href="https://wordpress.org/plugins/wp-graphql/" rel="nofollow">WPGraphQL</a> - это open source плагин, который добавляет GraphQL API в WordPress. Используя WPGraphQL, мы получаем:</p><ul><li>Гибкие запросы к таким сущностям, как посты, страницы, произвольным типам постов, таксономиям и пользователям.</li><li>Систему расширений которая позволяет расширять функционал GraphQL бекенда и таким образом поддерживать популярные плагины, тем самым позволяя возвращать дополнительные поля которые не относятся к стандартным полям Wordpress.</li><li>GraphQL API, который даёт очень удобный формат для интеграции фронтенд фреймворков таких как Next.js, Astro и SvelteKit.</li><li>Оптимизацию производительности, поскольку клиент запрашивает только нужные поля и данные делая один запрос вместо группы REST запросов + отдельный фронтенд забирает на себя часть запросов.</li></ul><p>В дополнение к доступному функционалу WPGraphQL можно добавлять дополнительные плагины-расширения, такие как <a href="https://wordpress.org/plugins/add-wpgraphql-seo/" rel="nofollow">WPGraphQL Yoast SEO</a> и <a href="https://woographql.com/" rel="nofollow">WooGraphQL</a> (WPGraphQL для WooCommerce). Таким образом добавив несколько плагинов в базовую инсталляцию CMS можно из коробки получить полностью функциональный GraphQL бекенд, который может покрыть запросы для блога, сео функционал, онлайн-магазин и тд.</p><p>Важно отметить, что изначальная идея использовать WPGraphQL пришла из статьи в блоге <a href="https://vercel.com/kb/guide/wordpress-with-vercel" rel="nofollow">Vercel</a>.</p><figure><img src="https://media.tproger.ru/user-uploads/136948/2026-03-25/838bc085-bfe5-4888-a326-6dcc00b0aaf5.webp" alt="Сравнение традиционного WordPress и headless WordPress: монолитная CMS с PHP темами против архитектуры с WPGraphQL API и Next.js фронтендом" /><figcaption>Традиционный WordPress vs Headless WordPress: разделение CMS и frontend через API (WPGraphQL + Next.js)</figcaption></figure><h2>Фронтенд на Next.js</h2><p><a href="https://nextjs.org/" rel="nofollow"> Next.js</a> - production-ready React-фреймворк, который разрабатывается компанией Vercel. Многие используют его по умолчанию для разных headless‑проектов. Наш пример headless-wordpress не исключение. Фреймворк предлагает удобную <a href="https://nextjs.org/docs/pages/building-your-application/routing" rel="nofollow">маршрутизацию</a> на базе файловой системы, где любой файл в папке pages автоматически становится маршрутом и поддерживает несколько стратегий рендеринга:</p><ul><li>Server‑side rendering (SSR) позволяет генерировать HTML при каждом запросе.</li><li>Статическая генерация (включая [Incremental Static Regeneration])</li><li>React Server Components, стратегия которая дает гибкость в балансировании производительности и кэширования.</li></ul><p>Это делает Next.js хорошей платформой для работы с GraphQL API и рендеринга страниц React‑компонентами. Если у вас нет опыта с <a href="http://nex.js">Next.js</a> и React, то это не повод не попробовать набросать POC в свободное время. Современные <a href="http://next.js">Next.js</a> и React templates + хороший AI agent помогут адаптировать UI под GraphQL для вас.</p><h2>Почему Cloudflare?</h2><p>Vercel очень часто является платформой по умолчанию для Next.js‑приложений. Более того Next.js адаптирован для запуска из коробки на серверах Vercel. При этом нужно добавить, что идея этой статьи не в том чтобы как-то компрометировать Vercel. Что же нужно знать про Cloudflare чтобы обратить внимание на этот сервис с точки зрения альтернативы для хостинга Next.js?</p><p>Согласно <a href="https://w3techs.com/technologies/details/cn-cloudflare" rel="nofollow">статистике</a>, реверс-прокси сервисы Cloudflare используются примерно на 21,9% всех сайтов в интернете, а это более 82% сайтов, где используется прокси‑сервисы в принципе. Такая распространённость говорит о масштабе, надежности и глобальном охвате сервиса. Но Cloudflare - это не только reverse-proxy. Компания разрабатывает целую группу облачных сервисов, включая такие сервисы, как Cloudflare Pages - альтернатива Github Pages, Workers - Serverless functions (по аналогии с AWS Lambda), Контейнеры, Очереди, AI сервисы, R2 Object Storage, и другие. В дополнение ко всему, компания предоставляет такие сервисы, как защита сайта (site-protection), VPN и капча (human-detection captcha). Такое разнообразие сервисов делает сервис очень распространенным.</p><h2>Лимиты бесплатного тарифа Cloudflare</h2><p>Одним из самых интересных аргументов в пользу Cloudflare можно считать их бесплатный тариф. Защита от DDoS, Universal SSL и глобальную CDN доступны бесплатно. Также бесплатный тариф включает большинство из вышеперечисленных облачных сервисов. Например, Cloudflare Workers, который можно использовать для хостинга Next.js-проектов, бесплатно даёт 100,000 запросов в день. Или R2 object storage - альтернатива S3 по умолчанию дает 10GB пространства, которое можно использовать для хранения статики или других данных. Этого более чем достаточно чтобы поэкспериментировать на выходных с новым стеком и вполне достаточно для того, чтобы бесплатно хостить ваш проект до тех пор пока у вас не пойдет серьезный трафик. Ниже приведена таблица с некоторыми из Cloudflare сервисов и что включено в бесплатный тариф.</p><figure><img src="https://media.tproger.ru/user-uploads/136948/2026-03-25/826d92c6-cecd-4d70-a2ee-78733a324a70.webp" alt="Таблица сервисов Cloudflare (Workers, KV, D1, R2 и др.) с лимитами бесплатного тарифа и их назначением" /><figcaption>Cloudflare free tier: сервисы и лимиты, достаточные для запуска headless WordPress + Next.js проекта. Взято с https://dev.to/ioniacob/which-cloudflare-services-are-free-2025-free-tier-guide-53jl.</figcaption></figure><h2>Запуск Serverless функций на edge-серверах</h2><p>Cloudflare Workers позволяют запускать серверлесс‑код по всей сети Cloudflare. Ниже приведено изображение показывающее как работает Edge CDN, когда например статика продублирована на все доступные сервера и таким образом пользователь получает ресурсы с самого близлежащего сервера.</p><figure><img src="https://media.tproger.ru/user-uploads/136948/2026-03-25/6c12d101-f282-4d1a-8d7a-b4119fabdfda.webp" alt="Схема работы CDN: пользователи обращаются к ближайшим edge-серверам, которые кешируют контент и уменьшают нагрузку на origin-сервер" /><figcaption>Как работает CDN: пользователь получает контент с ближайшего edge-сервера, снижая задержку и нагрузку на origin. Источник: https://www.cloudflare.com/learning/cdn/what-is-a-cdn/</figcaption></figure><p>Эта картинка хороша тем, что аналогично CDN статике на этих же серверах можно запускать и Workers (Lambda) функции, тем самым ускоряя вашу инфраструктуру еще больше.</p><p>Одной из интересных особенностей Workers-функций является отсутствие cold-starts. Любой cloud provider обычно подымает docker container или виртуализированное окружение в момент первого запуска программы, а это всегда задержка. Минусом Workers-функций является тот факт что их Runtime API требует чтобы код мог использовать их Web platform APIs. А это в свою очередь ограничивает выбор языка программирования: Javascript, Typescript и WebAssembly. Но благодаря такому подходу Workers используют изолированную модель запуска  и могут быть прогреты еще до момента запуска кода этого воркера. Прогрев начинается еще на этапе TLS-соединения между клиентом и серверами Cloudflare. Полный текст статьи можно почитать в их <a href="https://blog.cloudflare.com/eliminating-cold-starts-with-cloudflare-workers/" rel="nofollow">блоге</a>.</p><figure><img src="https://media.tproger.ru/user-uploads/136948/2026-03-25/4bcd2a0a-5161-46f5-984c-87f4097411dc.webp" alt="Схема работы Cloudflare Workers: прогрев (warmup) и загрузка воркера происходят во время TLS handshake до выполнения HTTP-запроса" /><figcaption>Как Cloudflare Workers устраняют cold-start: прогрев воркера происходит ещё на этапе TLS-соединения.</figcaption></figure><p>Воркеры выполняются на edge-серверах, которые находятся ближе всего к пользователям, тем самым уменьшая задержку и разгружая origin‑сервер (в нашем случае WordPress backend). По аналогии с другими cloud-провайдерами, код внутри Workers Runtime может использовать другие сервисы Cloudflare, такие как:</p><ul><li><a href="https://developers.cloudflare.com/workers/runtime-apis/cache/" rel="nofollow">Cache API</a> - позволяет читать и записывать данные в глобальный edge‑кэш через caches.default, что удобно для кэширования GraphQL‑ответов или страниц Next.js.</li><li><a href="https://developers.cloudflare.com/kv/" rel="nofollow">Workers KV</a> - распределенное key‑value‑хранилище для конфигурации и небольших наборов данных; можно хранить и получать данные глобально с низкой задержкой.</li><li><a href="https://developers.cloudflare.com/workers/configuration/cron-triggers/" rel="nofollow">Cron Triggers</a> - можно сопоставить cron‑выражение с обработчиком scheduled(), чтобы запускать периодические задачи, например, уборку кэша или обновление данных. Триггеры выполняются на малоиспользуемых машинах, максимизируя эффективность.</li></ul><p>Наличие доступа к дополнительным сервисам, таким как базы данных (D1), объектное хранилище (R2), очереди и AI даёт свободу строить более сложные и гибкие архитектуры, что очень полезно в дальнейшем на больших масштабах.</p><h2>OpenNext: мост между Next.js и Cloudflare</h2><p>Самостоятельный деплой Next.js на разные платформы непрост, поскольку среда исполнения Vercel отличается от других. Можно поднять Next.js на Node‑сервере, но его работа отличается от edge‑режима Vercel. OpenNext - это проект с открытым исходным кодом, который адаптирует Next.js для разных серверлесс‑платформ. Важно сказать что у Next.js нет нативного способа само разворачивания на других платформах, кроме Vercel; существующие отдельные адаптеры разрознены и сложны в поддержке. <a href="https://opennext.js.org/" rel="nofollow">OpenNext</a> объединяет усилия в одном адаптере, переводя выход сборки Next.js в формат, совместимый с основными облачными платформами. Проект поддерживают сообщество SST (AWS), команда Cloudflare и Netlify. Соответственно, с помощью OpenNext можно развернуть Next.js на Cloudflare Workers, сохраняя SSR, статическую генерацию и API‑маршруты.</p><p>Cloudflare‑адаптер устанавливается через @opennextjs/cloudflare. Далее следует установить<a href="https://developers.cloudflare.com/workers/wrangler/"> Wrangler</a>, настроить wrangler.toml с вашим Account ID и создать open-next.config.ts для управления кэшем и ассетами. Адаптер собирает приложение Next.js под среду Cloudflare, создает edge‑воркер и конфигурирует кэш для статики и ISR‑страниц (например, используя R2). После публикации Git‑интеграция Cloudflare автоматически разворачивает приложение при каждом пуше в GitHub или GitLab, а для pull‑request создает превью.</p><figure><img src="https://media.tproger.ru/user-uploads/136948/2026-03-25/ffd3d8f8-9e94-4ee2-b1b8-7b7d075a9bbd.webp" alt="Логотипы OpenNext, Cloudflare, AWS Amplify и Netlify, показывающие поддержку деплоя Next.js на разные cloud-платформы" /><figcaption>OpenNext как единый адаптер для деплоя Next.js приложений на разные платформы: Cloudflare, AWS и Netlify</figcaption></figure><h2>Кэширование и уровни производительности</h2><p>Архитектура headless WordPress + Next.js + Cloudflare обычно включает несколько уровней кэша:</p><ol><li>Кэш браузера - стандартный HTTP‑кэш на стороне клиента.</li><li>Кэш edge‑рантайма - Cache API Cloudflare Workers сохраняет HTML‑страницы или GraphQL‑ответы рядом с пользователем; при попадании в кэш контент отдаётся мгновенно, а промахи идут к воркеру или origin.</li><li>Кэш ISR Next.js - технология Incremental Static Regeneration сохраняет отрендеренные страницы на сервере и обновляет их по запросу, снижая нагрузку на WordPress API.</li><li>Кэш GraphQL - API WPGraphQL может реализовывать кэширование по времени или тегам (например, через WPGraphQL Smart Cache), чтобы управлять сроком жизни ответов.</li></ol><p>Эта многоуровневая иерархия кэша обеспечивает, что большинство запросов вообще не доходят до вашего WordPress‑сервера, повышая производительность и снижая нагрузку.</p><p>Пример конечной архитектуры показан на изображении ниже.</p><figure><img src="https://media.tproger.ru/user-uploads/136948/2026-03-25/37141150-e160-423b-a8cd-3784117e6499.webp" alt="" /><figcaption>Архитектура headless WordPress + Next.js на Cloudflare: edge-рендеринг, многоуровневый кэш и взаимодействие с WPGraphQL</figcaption></figure><h2>Автоматизация периодических задач</h2><p>Headless‑сайтам часто требуются периодические действия - например, обновление кэша ISR или синхронизация данных. Cron Triggers Cloudflare позволяют планировать запуск воркера по cron‑выражению. Обработчик scheduled() срабатывает по расписанию и подходит для обслуживания и получения сторонних данных. Триггеры выполняются на малоиспользуемых машинах по всему миру и легко управляются через Wrangler или панель Cloudflare.</p><h2>Модернизация PHP‑стека с Roots toolkit</h2><p>Хотя headless WordPress переносит рендеринг на JavaScript, CMS всё ещё нужно поддерживать. В качестве бонуса хочется порекомендовать экосистему <a href="https://roots.io/" rel="nofollow">Roots</a>, которая предлагает современный инструментарий для разработки на WordPress:</p><figure><img src="https://media.tproger.ru/user-uploads/136948/2026-03-25/27e070e5-39c8-425b-8142-b0b2ce0af69d.webp" alt="Скриншот сайта Roots с описанием инструментов для разработки WordPress: Bedrock, Sage, Trellis и Acorn" /><figcaption>Roots — современный инструментарй для разработки WordPress с использованием Composer, Blade и автоматизированного деплоя. Источник: Roots - https://roots.io/</figcaption></figure><ul><li><a href="https://roots.io/bedrock/" rel="nofollow">Bedrock</a> - шаблон WordPress, которая устанавливает ядро, плагины и темы через Composer. Таким образом Bedrock дает современную для PHP проектов структуру проекта, улучшает структуру папок, использует концепты Двенадцать факторов для конфигурации приложения с помощью .env‑файлы и тд. Более того, управление зависимостями через Composer повышает надежность и позволяет делать деплой приложения на разные сервера без страха что-то забыть или упустить.</li><li><a href="https://roots.io/sage/" rel="nofollow">Sage</a> - стартовая тема WordPress, использующая Blade от Laravel для шаблонов и интегрирующая Tailwind CSS. Sage автоматически генерирует theme.json из конфигурации Tailwind, поддерживает live preview блокового редактора с Vite и позволяет создавать компоненты на Blade. Это помогает фронтенд‑разработчикам отойти от устаревших подходов для разработки тем Wordpress с нуля.</li><li><a href="https://roots.io/trellis/" rel="nofollow">Trellis</a> - DevOps‑инструмент на базе Ansible, который поднимает серверы и автоматизирует деплой. Trellis предоставляет LEMP‑стек (Ubuntu 24.04, Nginx, PHP 8.3, MariaDB), выполняет деплой без downtimes и из коробки поддерживает SSL‑сертификаты. CLI помогает создавать и настраивать серверы, а также разворачивать проекты с атомарными релизами и откатами.</li><li><a href="https://roots.io/acorn/" rel="nofollow">Acorn</a> - интеграция, позволяющая использовать функционал Laravel в WordPress. С Acorn становятся доступны такие инструменты как Blade‑шаблоны, миграции, роутинг, кэширование и Artisan‑подобный CLI внутри WordPress. Это позволяет разработчикам строить плагины и фичи WordPress с использованием современных PHP‑подходов и современного фреймворка .</li></ul><p>Эти инструменты показывают, что экосистема WordPress продолжает развиваться и хорошо сочетается с современными подходами. Иными словами, WordPress - отличное решение, если знать, как его правильно готовить.</p><h2>Собираем всё вместе</h2><p>Архитектура приложения headless WordPress + Next.js + Cloudflare выглядит приблизительно так:</p><ol><li>WordPress (headless) - работает на традиционном сервере или в контейнере. Редакторы управляют контентом в админке. WPGraphQL и его расширения предоставляют GraphQL‑endpoint с данными, SEO и другой информацией, например данными о магазине.</li><li>Next.js фронтенд - React‑приложение, которое получает данные через GraphQL, рендерит страницы на сервере (SSR) или статически (ISR/SSG) и обрабатывает маршрутизацию и взаимодействие с клиентом. Код хранится в Git и автоматически разворачивается благодаря Git‑интеграции Cloudflare.</li><li>Cloudflare Workers - размещают приложение Next.js на edge через OpenNext. Workers исключают cold-starts и работают по аналогии с CDN как можно ближе к пользователю. Они также выполняют кэширование, обрабатывают API‑маршруты и запускают cron‑задачи.</li><li>Кэш на edge и в браузере - несколько уровней кэша гарантируют быструю отдачу статики и отрендеренных страниц. KV, R2 или D1 могут хранить дополнительные данные вроде сессий или объектов.</li></ol><h2>Заключение</h2><p>Headless‑архитектура объединяет универсальность WordPress и гибкость современных JavaScript‑фреймворков. Экспонируя контент через WPGraphQL и потребляя его в Next.js, можно получить больше контроля над рендерингом и тем самым улучшить производительность. Размещение фронтенда на Cloudflare Workers через OpenNext позволяет приблизить фронтенд код к пользователям, устраняет cold-starts, позволяет использовать free-tier и продвинутые уровни кэширования. Инструменты вроде Bedrock, Sage, Trellis и Acorn модернизируют PHP/Wordpress‑сторону и делают CMS такой же удобной в работе, как и современный Next.js/React-фронтенд. Вместе эти технологии создают мощный стек для создания быстрых, масштабируемых и безопасных сайтов, будь то хакатон, pet‑проект или серьёзный продакшн.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как работают браузеры: понятное техническое объяснение</title>
      <link>https://tproger.ru/articles/kak-rabotayut-brauzery--samoe-ponyatnoe-obyasnenie</link>
      <comments>https://tproger.ru/articles/kak-rabotayut-brauzery--samoe-ponyatnoe-obyasnenie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Валерия Турчак]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-rabotayut-brauzery--samoe-ponyatnoe-obyasnenie</guid>
      <description><![CDATA[<p>Это простая техническая статьи, из которой вы поймете детали и сформируете интуитивное понимание работы браузеров.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-rabotayut-brauzery--samoe-ponyatnoe-obyasnenie">Как работают браузеры: понятное техническое объяснение</a>»</p>]]></description>
      <category><![CDATA[Браузеры]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 09 Feb 2026 09:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p><i>Это адаптированный перевод материала <a href="https://howbrowserswork.com">How Browsers Work</a>, подготовленный специально для читателей Tproger.</i></p><h2>Зачем это нужно?</h2><p>Эту статью написали для разработчиков и всех, кто пользуется браузерами каждый день, но никогда не разбирался в том, как они устроены изнутри.</p><p>Автор посчитал, что большинство существующих гайдов либо слишком технические и детализированные, либо, наоборот, поверхностные. Поэтому он решил выбрать другой подход.</p><p>Руководство построено на небольших примерах, чтобы понять технические детали и сформировать интуитивное понимание работы браузеров.</p><p>Чтобы материал оставался кратким и по существу, автор намеренно опустил многие критические детали: разные версии HTTP-протокола, SSL, TLS, нюансы работы DNS и многое другое.</p><h2>Браузеры работают с URL</h2><p>Вы можете ввести в адресную строку буквально что угодно. Но под капотом браузеры работают с URL:</p><ul><li>Случайный текст вроде «pizza» будет преобразован в поисковый URL типа https://google.com/search?q=pizza</li><li>Доменное имя вроде example.com будет нормализовано в полный URL: https://example.com</li></ul><p>Чтобы увидеть, как это работает на практике, попробуйте ввести «pizza» или «example.com» в адресную строку браузера.</p><h2>Превращение URL в HTTP-запрос</h2><p>Когда браузер знает точный URL, который нужно посетить, он может отправить запрос на сервер, чтобы получить ресурс и отобразить его. Браузеры общаются с серверами по протоколу HTTP.</p><p>Чтобы понять, как URL переводится в формат HTTP-запроса, рассмотрим пример с полным URL вроде <b>example.com.</b></p><p>HTTP-запросы содержат заголовки в таком формате:</p><p>Один из заголовков — это заголовок Host. Он используется для идентификации сервера, на который отправляется запрос: <b>example.com</b>.</p><h2>Определение адреса сервера</h2><p>Браузеры не могут отправлять запросы на имена вроде<b> example.com</b>.</p><p>Компьютеры общаются с IP-адресами, поэтому браузер сначала обращается к системе DNS, чтобы преобразовать доменное имя в IP-адрес, прежде чем подключиться к серверу и отправить HTTP-запрос.</p><p>Например, если вы введёте в терминале команду для разрешения домена, DNS вернёт соответствующий IP-адрес:</p><h2>Установка TCP-соединения</h2><p>После того как DNS предоставил браузеру IP-адрес, всё ещё требуется надёжное соединение с сервером. TCP — это протокол, который устанавливает такое соединение до того, как будут отправлены любые HTTP-данные.</p><p>TCP устанавливает соединение с помощью трёхэтапного согласия, которое подтверждает, что обе стороны готовы отправлять и получать данные:</p><ol><li>SYN: клиент отправляет свой порядковый номер (seq=1000), чтобы открыть соединение</li><li>SYN-ACK: сервер подтверждает пакет, добавляя свой порядковый номер (seq=5000) и подтверждая номер клиента, увеличивая его на 1 (ack=1001)</li><li>ACK: клиент подтверждает номер сервера, увеличивая его на 1 (ack=5001), и соединение готово</li></ol><p>Эти номера показывают, как клиент и сервер отслеживают диалог. Они считают байты, поэтому обе стороны согласны с тем, где начинается поток данных и что должно произойти дальше. Если какие-то данные не приходят, отправитель видит разрыв и повторно передаёт недостающие байты. Именно так TCP поддерживает порядок данных и надёжность после установки соединения.</p><p>Когда вы начинаете отправлять пакеты и пытаетесь нарушить работу сети, вы можете увидеть, как TCP справляется с потерями данных и восстанавливает передачу.</p><h2>HTTP-запросы и ответы</h2><p>После установки TCP-соединения браузер может отправить HTTP-запрос на сервер.</p><p>Представьте, что вы наблюдаете за тем, как HTTP-запрос путешествует к серверу, а HTTP-ответ возвращается в браузер:</p><p>БРАУЗЕР (КЛИЕНТ)</p><p>СЕРВЕР</p><p>Когда приходит HTTP-ответ, браузер читает сырой HTTP-ответ и начинает рендеринг HTML-контента.</p><h2>Парсинг HTML для построения DOM-дерева</h2><p>После того как приходит HTTP-ответ, браузер отделяет заголовки от тела и направляет HTML-байты в парсер. Парсер превращает теги вроде<b> &lt;h1&gt;</b> в токены и строит DOM-дерево.</p><p>Посмотрите, как HTML-поток парсится в DOM-дерево:</p><p>DOM-дерево:</p><p>Парсинг происходит потоково и устойчив к ошибкам: браузер начинает строить узлы ещё до того, как документ полностью загружен, и вставляет недостающие теги, чтобы дерево оставалось валидным. Когда появляется тег <b>&lt;script&gt;</b>, парсинг может приостановиться, чтобы выполнить скрипт.</p><p>DOM-дерево затем объединяется с CSS, чтобы создать render tree (дерево рендеринга), которое используется для расчёта layout и отрисовки пикселей.</p><h2>О важности DOM</h2><p>DOM — это внутренняя модель документа в памяти браузера. Это общий контракт между HTML-парсером, движком CSS-селекторов и средой выполнения JavaScript. Изменения в DOM немедленно влияют на layout, стили и то, с чем пользователи могут взаимодействовать.</p><p>DOM обеспечивает всё: от выборки элементов до динамической стилизации и обработки событий. Попробуйте отредактировать JavaScript-код и посмотрите, как меняется DOM:</p><p>Редактируемый JavaScript:</p><p>Живой DOM:</p><h2>Layout, Paint и Composite</h2><p>После того как DOM и CSS готовы, браузер запускает конвейер рендеринга: Layout (reflow) для расчёта размеров и позиций, Paint для заполнения пикселей, затем Composite для сшивки слоёв на GPU.</p><p>Не каждое изменение запускает все этапы заново. Изменение цвета обычно требует только перерисовки (repaint), тогда как изменение размеров заставляет пересчитывать layout и paint.</p><p>Этапы рендеринга:</p><ol><li>Layout — пересчёт размеров и позиций</li><li>Paint — заполнение пикселей в слои</li><li>Composite — сшивка слоёв на GPU</li></ol><p>Пример DOM:</p><p>Composite всегда смешивает слои в финальный кадр.</p><p>Именно поэтому страницы с большим количеством layout-операций ощущаются медленнее: больше работы нужно выполнить, прежде чем можно будет показать следующий кадр.</p><p>Вот и всё! Если вы разобрались со всеми примерами, у вас должна сформироваться чёткая ментальная модель работы браузеров.</p><p>Теперь вы понимаете путь от ввода URL в адресную строку до отображения пикселей на экране: разрешение DNS, установку TCP-соединения, отправку HTTP-запросов, парсинг HTML, построение DOM и конвейер рендеринга с этапами Layout, Paint и Composite.</p>]]></content:encoded>
    </item>
    <item>
      <title>В Android-троянах начали использовать ИИ для обхода защиты и «умного» кликфрода</title>
      <link>https://tproger.ru/news/v-android-troyanah-nachali-ispolzovat-ii-dlya-obhoda-zashhity-i--um</link>
      <comments>https://tproger.ru/news/v-android-troyanah-nachali-ispolzovat-ii-dlya-obhoda-zashhity-i--um?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/v-android-troyanah-nachali-ispolzovat-ii-dlya-obhoda-zashhity-i--um</guid>
      <description><![CDATA[<p>В Android-троянах начали использовать ИИ для кликфрода и обхода защиты: вредоносные приложения анализируют интерфейсы почти как человек</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/v-android-troyanah-nachali-ispolzovat-ii-dlya-obhoda-zashhity-i--um">В Android-троянах начали использовать ИИ для обхода защиты и «умного» кликфрода</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Android]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Машинное обучение]]></category>
      <category><![CDATA[Google]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[Хакеры]]></category>
      <category><![CDATA[Spotify]]></category>
      <category><![CDATA[Xiaomi]]></category>
      <category><![CDATA[WebRTC]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 23 Jan 2026 02:36:56 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мобильные трояны сделали очередной шаг вперед: вместо примитивных скриптов они начали использовать <b>машинное обучение</b> для анализа интерфейсов и взаимодействия с рекламой почти как человек.</p><p>Новую волну вредоносных Android-приложений <a href="https://forum.drweb.com/index.php?showtopic=339666#entry923156">обнаружили</a> специалисты <b>Dr.Web</b>.</p><h2>Как работают «умные» трояны</h2><p>В основе новой схемы — <b>TensorFlow.js</b>. Это open-source библиотека от <b>Google</b>, которая позволяет запускать модели машинного обучения прямо в JavaScript. Вместо заранее заданных правил, троян анализирует изображение экрана и сам определяет, куда «нажать».</p><p>Сценарий выглядит так:</p><ul><li>троян загружает обученную ML-модель с удаленного сервера;</li><li>открывает сайт с рекламой во встроенном скрытом WebView;</li><li>делает скриншоты виртуального экрана;</li><li>модель распознает нужные элементы (кнопки, видео, баннеры);</li><li>вредоносное ПО имитирует реальные действия пользователя.</li></ul><p>За счет визуального анализа, троян лучше адаптируется к современным рекламным форматам: динамической верстке, iframe, видео и постоянно меняющимся шаблонам.</p><h2>Два режима работы: автономный и ручной</h2><p>По данным Dr.Web, трояны используют сразу два режима.</p><p><b>Phantom-режим.</b> Это полностью автоматический вариант, когда вредоносный код работает в фоне. Пользователь ничего не видит, а клики по рекламе выглядят максимально естественно.</p><p><b>Signalling-режим.</b> Это уже более опасный вариант, при котором через WebRTC троян транслирует экран виртуального браузера операторам атаки. Злоумышленники могут в реальном времени нажимать, скроллить и вводить текст, управляя устройством удаленно.</p><h2>Где распространяется вредоносное ПО</h2><p>Часть зараженных приложений попала даже в <b>Xiaomi GetApps</b> — официальный магазин приложений компании. Схема классическая: сначала на площадку загружается «чистая» игра, а вредоносный функционал появляется позже, через обновление.</p><p>Среди выявленных приложений — мобильные игры с десятками тысяч установок. Параллельно трояны активно распространяются через сторонние APK-сайты и «моды» популярных сервисов вроде Spotify и YouTube, а также через Telegram-каналы и Discord-серверы.</p><figure><img src="https://media.tproger.ru/user-uploads/111041/2026-01-23/36cb2110-ac67-4ad8-9f54-10b6fe4c4362.webp" alt="" /></figure><h2>Почему это опаснее обычного кликфрода</h2><p>Формально такие трояны не крадут пароли и не вытаскивают личные данные. Но их скрытность делает их особенно неприятными:</p><ul><li>пользователь не видит подозрительных действий;</li><li>ускоряется разряд батареи и износ устройства;</li><li>растет расход мобильного трафика;</li><li>рекламные системы получают фейковую «человеческую» активность, которую сложнее отфильтровать.</li></ul><p>Главное же — это сигнал: ИИ все чаще используют не только для защиты, но и для атак. Вредоносное ПО постепенно перенимает те же инструменты, что и легальные разработчики.</p><h2>Что делать пользователям</h2><p>Рекомендации остаются простыми и банальными, но от этого не менее важными:</p><ul><li>не устанавливать APK из сторонних источников;</li><li>избегать модов и бесплатных премиум-версий приложений;</li><li>внимательно относиться даже к альтернативным официальным магазинам;</li><li>регулярно обновлять систему и защитное ПО.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>На чём писать в 2026: какой язык программирования выбрать, если хочется обновить стек</title>
      <link>https://tproger.ru/articles/na-chyom-pisat-v-2026--kakoj-yazyk-programmirovaniya-vybrat--esli-hochetsya-obnovit-stek</link>
      <comments>https://tproger.ru/articles/na-chyom-pisat-v-2026--kakoj-yazyk-programmirovaniya-vybrat--esli-hochetsya-obnovit-stek?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Татьяна Жукова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/na-chyom-pisat-v-2026--kakoj-yazyk-programmirovaniya-vybrat--esli-hochetsya-obnovit-stek</guid>
      <description><![CDATA[<p>Выбор языка программирования в 2026: объективный анализ Python, C, C++, Java, C#, JavaScript и Visual Basic. В статье вы найдете стратегии перехода между языками, оценку рисков и возможностей, а также дорожную карту для обновления стека.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/na-chyom-pisat-v-2026--kakoj-yazyk-programmirovaniya-vybrat--esli-hochetsya-obnovit-stek">На чём писать в 2026: какой язык программирования выбрать, если хочется обновить стек</a>»</p>]]></description>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 19 Jan 2026 12:30:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рынок разработки в 2026 году не обнуляется, но заметно перетасовывается. Это среда, где ИИ уже встроен в рабочие процессы, системное программирование снова становится массово востребованным, высокие нагрузки перестают быть экзотикой, требования к безопасности растут, а кроссплатформенность набирает обороты. На этом фоне вопрос обновления стека — важен в контексте сохранения рыночной ценности в ближайшие годы.</p><p>Подборка в этом материале опирается на <a href="https://www.tiobe.com/tiobe-index/">TIOBE Index </a>— международный рейтинг популярности языков программирования, который ежемесячно формируется на основе данных поисковых систем, количества обучающих материалов и активности профессионального сообщества. Некий индикатор того, где сейчас сосредоточен реальный спрос.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-12-22/9a5abb10-371c-4aae-8048-a544c74fa5df.png" alt="" /></figure><p>Разберём на примере семи самых рейтинговых языков, какие из них логично рассматривать в 2026 году (и логично ли) действующим разработчикам, и из каких стеков в них входить быстрее всего.</p><h2>Python в 2026: универсальный усилитель для бэкенда, данных и ИИ</h2><p>В 2026 году Python остаётся языком, который проще всего встроить в уже существующий стек. Он <a href="https://mimo.org/blog/top-ai-programming-languages">живёт </a>в областях, где важны скорость разработки и плотность экспериментов: машинное обучение и прикладные AI-задачи, data engineering и ETL-процессы, автоматизация рутинных операций, разработка девтулов и утилит; а также в бэкенде, где критичнее гибкость, чем низкоуровневая оптимизация.</p><p>Переходить на Python проще всего тем, кто привык мыслить объектно и работать с динамикой. Самый оптимальный вариант — из JS, PHP и Java:</p><ul><li>Разработчики на JavaScript <a href="https://evrone.ru/blog/articles/from-java-to-python">уходят</a> в питон почти бесшовно: есть близкие модели данных, схожая философия быстрого прототипирования и огромная база библиотек.</li><li>PHP-разработчики <a href="https://habr.com/ru/companies/exWargaming/articles/221035/">адаптируются</a> к языку благодаря знакомому подходу к бэкенд-логике и работе с фреймворками, но есть принципиальная разница к подходам с типизацией.</li><li>Преимущества для Java-специалистов в<a href="https://evrone.ru/blog/articles/from-java-to-python"> понятном</a> синтаксисе и лаконичности Python, поэтому переход также возможен.</li><li>Аналитики и тестировщики переходят на Python через <a href="https://habr.com/ru/articles/843520/">практику</a>: скрипты автоматизации быстро превращаются в продуктивные инструменты.</li></ul><p>Порог входа <a href="https://blog.jetbrains.com/pycharm/2025/08/the-state-of-python-2025/">низкий</a>: синтаксис прямолинейный, в экосистеме есть готовые решения почти на каждый случай, а первые практические результаты появляются через считанные недели. Но это же создаёт риски. Так как вход в язык относительно лёгкий (это подтверждает первое место в рейтинге) — конкуренция высокая, особенно в прикладном ML. Кроме того, в <a href="https://www.opennet.ru/openforum/vsluhforumID3/138045.html">высоконагруженных системах</a>, где важна каждая микросекунда, асинхронное программирование и управление памятью требуют глубоких знаний, выходящих за рамки базового синтаксиса.</p><p>Python — не лучший выбор для тех, кто работает в системном программировании, embedded или инфраструктуре на C/C++. В этих задачах важна предсказуемость управления памятью, а не удобство разработчика.</p><h2>C в 2026: опорный язык для embedded, драйверов и систем реального времени</h2><p>В 2026 году С остаётся <a href="https://habr.com/ru/companies/swd_es/articles/508240/">фундаментом</a> там, где железо важнее абстракций. Он работает на микроконтроллерах, в промышленной автоматике, драйверах, прошивках и в RTOS-средах, где критична предсказуемость времени выполнения и полный контроль над ресурсами. Это язык, вокруг которого ещё долго будут строиться системы, способные жить десятилетиями.</p><p>Проще всего переход даётся тем, кто уже держал в руках низкоуровневый стек — в C можно войти из C++ и эмбеда:</p><ul><li>Переход с C++ на C <a href="https://stackoverflow.com/questions/4058496/moving-from-c-to-c">естественен</a> из-за общей модели памяти и необходимости понимать стоимость операций.</li><li>Специалисты по встраиваемым системам (embedded) часто <a href="https://hexly.ru/professiya-info/embedded-programmist">начинают</a> с C/C++ и глубоко понимают аппаратную часть, что делает переход к чистому C логичным.</li></ul><p>Вход кажется простым только внешне. Настоящая работа начинается там, где ответственность лежит за всё — от выделения памяти до обработки ошибок. Минимум встроенных защит и отсутствие автоматического управления ресурсами требуют инженерной дисциплины. Ошибки в C <a href="https://habr.com/ru/articles/883992/">редко проявляются </a>спокойно: они выливаются в падения, утечки, нестабильные устройства и долгие ночи с отладчиком.</p><p>Зато выгода ощутимая. По-прежнему ценится умение работать с ограниченными ресурсами и понимать систему изнутри. Это профессии с высокой инженерной значимостью и долгим жизненным циклом: такие проекты живут годами и переживают несколько поколений языков.</p><p>Не стоит идти в C тем, кто работал только в веб-стеке и никогда не сталкивался с ограничениями железа. <a href="https://skyeng.ru/it-industry/programming/chto-vybrat-dlya-razrabotki-c-ili-python/">В отличие</a> от Python, ориентированного на скорость разработки и обладающего автоматическим управлением памятью, C требует полного низкоуровневого контроля над ресурсами, что создаёт огромный разрыв в парадигмах программирования. Путь в продакшен может превратиться в затяжной стресс вместо профессионального роста.</p><h2>C++ в 2026: высокопроизводительные системы, игры и сложная инженерия</h2><p>В 2026 году C++ <a href="https://devblogs.microsoft.com/cppblog/whats-new-for-cpp-developers-in-visual-studio-2026-version-18-0/">остаётся </a>рабочей лошадкой там, где важны скорость, предсказуемость и возможность вручную управлять каждым уровнем системы. Он используется в игровых движках, графических пайплайнах, симуляторах, системах хранения и в любом ПО, где миллисекунды стоят денег или определяют игровой опыт. Это язык, который не потерял актуальность и вряд ли потеряет — слишком много инфраструктуры на нём держится.</p><p>Проще всего переходят те, кто уже понимает низкоуровневую модель — специалисты по C и Rust:</p><ul><li>Разработчики на C чувствуют себя здесь как дома, ведь не теряют контроль и получают больше инструментов.</li><li>Тем, кто <a href="https://www.linkedin.com/posts/lanceharvie_embeddedsystems-rustlang-cprogramming-activity-7227508999947005953-59ws">пришёл </a>из Rust, знакомы принципы безопасности памяти, хотя в C++ они реализованы иначе.</li></ul><p>Главное преимущество C++ остаётся неизменным: он позволяет строить системы, где скорость и контроль указаны в требованиях. Производительность, доступ к железу, возможность оптимизировать всё вручную — это сильные стороны, благодаря которым язык удерживает позиции в самых сложных доменах. Вместе с этим <a href="https://habr.com/ru/articles/862832/">растёт</a> и планка задач: на C++ редко делают что-то простое, и это превращает язык в сильный показатель инженерной зрелости.</p><p>Минусы есть. Экосистема зачастую <a href="https://habr.com/ru/companies/ruvds/articles/871940/">тяжеловесна</a>, а стандарт развивается быстро, что может осложнять поддержку актуальности проектов. Ошибки стоят дорого: неправильное владение памятью, неоптимальный алгоритм или гонки данных могут превратиться в реальные финансовые или продуктовые потери. C++ вознаграждает за дисциплину и карает за невнимательность.</p><p>Переход оправдан тогда, когда нужна работа в performance-доменах: игровой dev, real-time-системы, графика, симуляции, базы данных. Если задачи требуют скорости и полного контроля над процессом — C++ остаётся одним из самых мощных инструментов для этого уровня.</p><h2>Java в 2026: стабильный корпоративный бэкенд и инфраструктура, которая не рушится</h2><p>В 2026 году Java остаётся одним из самых <a href="https://softjourn.com/insights/is-java-still-used">устойчивых</a> языков для корпоративных систем. Банки, страховые компании, платёжные сервисы, крупные ритейлеры и весь классический enterprise-сегмент продолжают работать на JVM-стеке, потому что он проверен, масштабируется и выдерживает десятки лет развития продукта.</p><p>Переход проще всего даётся тем, кто уже жил в объектно-ориентированной модели — C#, Kotlin.</p><ul><li>C#-спецы попадают почти в привычную среду, где меняются лишь инструменты, но не логика разработки.</li><li>Kotlin-разработчики <a href="https://www.infoworld.com/article/2244227/jetbrains-kotlin-jvm-language-appeals-to-the-java-faithful.html">ощущают </a>Java как чуть более формальную, но узнаваемую основу JVM.</li></ul><p>Java остаётся рабочим языком по очень приземлённым причинам. Объём существующего кода огромный, и его нужно поддерживать, развивать, переписывать и интегрировать с современными сервисами. Стабильность и предсказуемость стека — редкая ценность в отрасли, где технологии меняются каждые пару лет. Java хорошо масштабируется, отлично ложится на корпоративные процессы, <a href="https://evincedev.com/blog/top-java-frameworks-for-web-developement/">имеет </a>зрелые фреймворки и помогает разработчикам быть востребованными в крупных компаниях, где команды десятилетиями развивают один продукт.</p><p>Но у языка есть ограничения. Попасть в экосистему без опыта сложнее, потому что компании ожидают зрелости, аккуратности и умения работать с большими системами. Старт карьеры в Java <a href="https://cpb-runo.ru/news/programming/pochemu-java-luchshiy-yazyk-dlya-starta-v-it-universalnost-i-vostrebovannost-na-rynke/">может</a> быть простым, но рост до уровня enterprise-разработчика требует изучения сложных концепций и фреймворков.</p><p>Язык подойдёт тем, кто хочет развивать крупные платформы, работать с надёжной архитектурой и видеть долгосрочный эффект от своих решений. Если цель — предсказуемый рост, корпоративный бэкенд и инфраструктура, Java в 2026 остаётся одной из самых практичных точек входа.</p><h2>C# в 2026: универсальный стек для бэкенда, геймдева и кроссплатформы</h2><p>В 2026 году C# <a href="https://www.educative.io/blog/is-c-sharp-and-net-still-relevant">уверенно</a> держит позицию одного из самых практичных языков для разработчиков, которым нужен сбалансированный стек: промышленный бэкенд, стабильные корпоративные продукты, кроссплатформенная разработка и <a href="https://nt.ua/ru/advantages-trends-and-prospects-for-development-in-c-sharp-and-dot-net">геймдев</a> на Unity. Это язык, который позволяет работать сразу в нескольких направлениях, не выпадая из экосистемы .NET.</p><p>Чаще всего C# живёт в web-бэкенде, корпоративных приложениях и десктопных продуктах — там, где важны производительность, предсказуемость и хорошая интеграция с инфраструктурой. Unity по-прежнему остаётся главным входом в геймдев для начинающих, и именно через него многие приходят в C#, а не наоборот.</p><p>Переходить сюда проще Java-разработчикам, <a href="https://softjourn.com/insights/c-sharp-vs-java">из-за</a> схожего объектно-ориентированного синтаксиса.</p><p>C# <a href="https://nt.ua/ru/advantages-trends-and-prospects-for-development-in-c-sharp-and-dot-net">усиливается </a>благодаря развитию .NET и всё более уверенной кроссплатформенности. Этому стеку уже не нужно доказывать свою жизнеспособность: он стабильно используется в компаниях разного масштаба, даёт хороший гарантированный спрос на рынке и регулярно обновляется без резких ломок экосистемы.</p><p>Особенно выгодно выбирать C#, если хочется совмещать бэкенд и геймдев — редкое сочетание, которое открывает широкий диапазон карьерных возможностей. Это стек, позволяющий развиваться горизонтально, не меняя язык при переходе между разными индустриями.</p><h2>JavaScript в 2026: опора веба и самый быстрый путь в fullstack</h2><p>В 2026 году JavaScript <a href="https://dev.to/blarzhernandez/what-will-shape-the-next-wave-of-frontend-development-in-2026-backed-by-experts-data-52h3">остаётся</a> одним из главных рабочих языков для продуктовых команд: он ведёт фронтенд, закрывает часть бэкенда через Node.js и работает в мобильных приложениях.</p><p>Перейти сюда проще всего тем, кто уже<a href="https://sky.pro/wiki/html/luchshie-yazyki-dlya-sozdaniya-veb-sajtov-chto-vybrat/"> работал</a> с вебом — PHP и Python:</p><ul><li>PHP-разработчики смогут быстро адаптироваться к асинхронности и современным подходам.</li><li>Python-разработчики оценят низкий порог входа и скорость, с которой можно развернуть рабочий прототип.</li></ul><p>JavaScript остаётся одним из ключевых языков из-за ширины экосистемы и того, что браузер задаёт технические ограничения и возможности для всего фронтенда. Он <a href="https://tobiz.net/support/perspektivy-veb-razrabotki-v-2026-frontend-bekend-i-fullstek/">хорошо</a> масштабируется на фуллстек, что делает его удобным языком для продуктовых команд, которым важен быстрый цикл разработки.</p><p>Главный вызов — JS <a href="https://dev.to/blarzhernandez/what-will-shape-the-next-wave-of-frontend-development-in-2026-backed-by-experts-data-52h3">быстро</a> меняется, и это требует регулярных вложений времени в поддержание компетенций.</p><p>JavaScript — лучший выбор, если нужен быстрый переход в другой домен без полной переучки: от фронтенда к бэкенду, от веба к мобильному приложению, от продуктового стека к созданию собственных инструментов.</p><h2>Visual Basic в 2026: язык, который до сих пор жив</h2><p>В 2026 году Visual Basic — это язык, который держится за счёт огромного слоя легаси-систем, внутренних корпоративных решений и автоматизаций вокруг Office. Он живёт там, где продукт работает много лет и стабильно приносит деньги, но уже <a href="https://www.braintools.ru/article/16792">устаревает</a>.</p><p>Visual Basic всё ещё встречается в старых внутренних продуктах, отчётных системах, инструментах на базе Excel и в корпоративных контурах, которые строились до эпохи массовой веб-разработки.</p><p>Почему язык до сих пор всплывает в индексах и рейтингах? Потому что огромный пласт кода всё ещё выполняется в продакшене. Бизнес выбирает стабильность: если система работает, её не переписывают.</p><p>Входить в Visual Basic с нуля в 2026 году почти никогда не рационально. Это не инвестиция в карьеру, а узкая специализация, завязанная на конкретный контур внутри конкретной компании. Смысл появляется только в одной ситуации: вы уже работаете в организации, которая держит ключевую систему на VB, и переход даёт повышение или расширение роли. Но если слишком хочется, то ориентируйтесь на требования конкретной компании.</p><h2>Почему не стоит слепо доверять общим рейтингам языков</h2><p>Важно сделать оговорку: любые общие рейтинги языков программирования — включая TIOBE — дают лишь приближённую картину и не могут служить прямой инструкцией к выбору стека.</p><p>TIOBE Index строится на анализе поисковых запросов, упоминаний в обучающих материалах и общей видимости языка в сети. Это скорее индикатор узнаваемости и обсуждаемости, чем отражение реального распределения задач, вакансий и индустриальных доменов. Язык может быть высоко в рейтинге, потому что о нём много пишут, спорят или учат, но это не всегда означает, что именно на нём сейчас создаётся больше всего коммерческого продукта.</p><p>Именно поэтому у практикующих разработчиков рейтинги часто вызывают скепсис. В реальной работе языки живут внутри конкретных областей. Геймдев не переезжает на Python или TypeScript, мобильная разработка не отказывается от Swift и Kotlin, системное программирование, embedded и highload по-прежнему требуют C, C++ или Rust. Универсального списка «главных языков года» просто не существует — есть разные наборы под разные задачи.</p><p>Отдельный аргумент — рынок вакансий. Он часто даёт более приземлённую картину спроса, чем глобальные индексы. Например, как сообщает <a href="https://t.me/blog7x">Евгений Красников</a>, разработчик ПО, в лидерах оказываются совсем не те языки, которые традиционно доминируют в международных чартах: высокие позиции занимают 1С, Python, Java, JavaScript, PHP, Go. Это не отменяет глобальных трендов, но показывает, насколько сильно результаты зависят от региона и структуры экономики.</p><p>Ещё одна проблема рейтингов — они плохо учитывают влияние ИИ на разработку. Да, сегодня Python и JavaScript действительно выигрывают за счёт того, что на них проще и быстрее генерировать код с помощью AI-инструментов. Но это не означает, что весь новый софт будет писаться только на них. Языки по-прежнему выбирают под требования к производительности, безопасности, модели исполнения и жизненному циклу продукта — и эти требования никуда не исчезли.</p><p>Поэтому подборка языков в этом материале — не попытка назвать «лучшие языки 2026 года», а способ показать набор устойчивых стеков, которые логично рассматривать при обновлении навыков. Рейтинги здесь — отправная точка для разговора, а не истина в последней инстанции. В реальности язык почти всегда выбирается не по позиции в индексе, а по домену, типу задач и карьерной стратегии разработчика.</p><h2>Как выбрать язык для перехода: дорожная карта</h2><p>Обновление стека приносит пользу только тогда, когда связано с реальными задачами и карьерной траекторией. В 2026 году переходы лучше совершать горизонтально: оставаться в своём направлении, но расширять инструменты, чтобы работать быстрее и закрывать больше типов задач.</p><p>Основой выбора остаются три фактора: текущий стек; домен, в котором вы уже сильны; рынок, куда хотите сместиться (корпорации, международные компании или продуктовые команды).</p><p>Запомните техническое правило: не уходить сразу в область, которая сильно отличается по уровню абстракций или модели исполнения. Такой скачок обычно замедляет прогресс и растягивает срок выхода к офферу.</p><p>Если выбирать наиболее безопасную стратегию, то это переход внутри текущего направления. Например:</p><p>– backend → backend на другом языке;</p><p>– data → data с расширением инструментов;</p><p>– frontend → fullstack как способ увеличить гибкость и ценность в команде.</p><p>Такой подход позволяет нарастить стек, не теряя опыт, и быстрее выйти на уровень, где новый инструмент начинает приносить отдачу (и деньги).</p><p>Ну и напоследок даем совет от Игоря Алентьева, продуктового разработчика и создателя агрегатора IT-вакансий <a href="https://hirify.me">hirify.me</a>.</p><blockquote>Нет никакого смысла учить в 2026 что то кроме питона и js/ts: весь новый софт пишется на них (не в последнюю очередь, потому что на них отлично пишет AI), поэтому если нет исключительной любви к легаси или какому-то определенному языку, просто берите их и не ошибетесь. <br />Если есть любовь к энтерпрайзному софту, всяким банкам/телекомам, то подойти могут java/kotlin/c#. Но это нисходящий тренд на горизонте 5-10 лет. <br />В целом, нужно учиться работать с AI, ведь через какое-то количество лет код не будут писать руками, только проектировать.</blockquote><p>Желаем удачи в переходе на новый язык!</p>]]></content:encoded>
    </item>
    <item>
      <title>Программирование по-русски: от советских ЯП до JavaScript на кириллице</title>
      <link>https://tproger.ru/articles/programmirovanie-po-russki--ot-sovetskih-yap-do-javascript-na-kirillice</link>
      <comments>https://tproger.ru/articles/programmirovanie-po-russki--ot-sovetskih-yap-do-javascript-na-kirillice?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Вадим Егорцев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/programmirovanie-po-russki--ot-sovetskih-yap-do-javascript-na-kirillice</guid>
      <description><![CDATA[<p>Русскоязычные языки программирования: история советских ЯП от Рапиры до JavaScript на кириллице. Почему одни исчезли, а другие живы до сих пор</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/programmirovanie-po-russki--ot-sovetskih-yap-do-javascript-na-kirillice">Программирование по-русски: от советских ЯП до JavaScript на кириллице</a>»</p>]]></description>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 14 Jan 2026 12:27:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Пока мир программировал на FORTRAN и COBOL, советские инженеры создавали языки на кириллице. В школах учили Рапире, на оборонных предприятиях писали русский код для суперкомпьютеров, конструкторы «Бурана» рисовали алгоритмы в ДРАКОН.</p><p>Большинство языков ушли в историю — но идея русскоязычного программирования неожиданно получила продолжение.</p><p>В 2025 году преподаватели Пензенского университетаперевелисинтаксис JavaScript на кириллицу и уже обучают студентов писать код без единой латинской буквы.</p><p>В статье рассказываем, как работали русскоязычные ЯП, почему они появились именно в СССР, какие задачи решали и что с ними стало после распада Союза — некоторые идеи актуальны до сих пор.</p><h2>1. Рапира</h2><p>Язык придумали в 1987 году в Институте систем информатики им. А.П. Ершова в Новосибирске. Название расшифровывается как «Русский Алгоритмический Процедурный Императивный Расширяемый Адаптируемый».</p><p>Рапиру создавали для обучения программированию школьников и студентов. Синтаксис использовал русские ключевые слова:</p><ul><li>проц вместо function,</li><li>цел вместо int,</li><li>вывод вместо print.</li></ul><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/592ac780-9ed1-4701-b080-2ab0f115ea55.jpg" alt="" /><figcaption>Пример программы на Рапире</figcaption></figure><p>Язык поддерживал процедуры, функции, массивы, работу с файлами и базовые структуры данных.</p><p>Рапиру преподавали в советских школах и вузах с конца 1980-х до середины 1990-х годов. После распада СССР язык исчез из учебных программ — его вытеснили <b>Pascal</b> и <b>Basic</b>.</p><p>Сегодня язык не поддерживается, компиляторы не обновляются, сообщества разработчиков не существует. Исходный код программ на Рапире сохранился только в архивах и учебниках того времени.</p><h2>2. ПЭВМ Искра-1256</h2><p>Безымянный язык программирования для компьютера «Искра-1256» появился в конце 1980-х годов. Машину выпускал Ленинградский завод имени Светланы для автоматизации офисной работы — учёта, делопроизводства, составления отчётов.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/b71340ef-c0da-4be9-a93d-5e137b052ef7.jpg" alt="" /><figcaption>Советский компьютер Искра-1256, 1980 год выпуска</figcaption></figure><p>Разработчики встроили интерпретатор языка в ПЗУ компьютера, чтобы пользователи писали программы без установки дополнительного софта. Целевая аудитория — бухгалтеры, секретари, экономисты без технического образования.</p><p>Синтаксис копировал структуру русских предложений. Программист набирал команды в диалоговом режиме, интерпретатор выполнял их сразу после ввода.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/86b0e844-3ee8-4728-a338-d98c01005935.jpg" alt="" /><figcaption>Примеры программ на Искра-1256</figcaption></figure><p>Язык поддерживал арифметические операции, работу с текстовыми данными, условные переходы и циклы. Документация описывала его как инструмент для расчёта зарплат, ведения складского учёта и формирования табличных отчётов. Программы сохранялись на встроенный накопитель или дискеты.</p><p>«Искра-1256» выпускалась ограниченной серией и поставлялась в государственные учреждения. После 1991 года производство прекратилось. Компьютеры заменили на машины с DOS и Windows. Язык исчез вместе с платформой — портирование на другие системы не проводилось.</p><p>Сегодня информация о языке сохранилась в технических описаниях «Искры-1256» и воспоминаниях пользователей.</p><h2>3. Эль-76</h2><p>Это разработка конца 1970-х годов родом из Института точной механики и вычислительной техники в Москве. Язык придумали специально для суперкомпьютера «Эльбрус-1».</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/55d17e1d-adbf-404b-93bb-63ecc950cf2d.jpg" alt="" /><figcaption>Суперкомпьютер Эльбрус-1 в СССР</figcaption></figure><p>Эль-76 использовал русские ключевые слова и кириллицу для всех операторов. Синтаксис поддерживал структурное программирование — последовательности, ветвления, циклы. Программы разбивались на подпрограммы. Система типов хранила информацию о переменной вместе с её значением в памяти.</p><p>Язык применяли на суперкомпьютерах в оборонных предприятиях и научных центрах СССР. Машины поддерживали суперскалярность — выполнение нескольких инструкций за такт — и защитное программирование для критичных систем.</p><p>После распада Союза производство суперкомпьютеров прекратилось, Эль-76 перестали использовать. Сегодня процессоры «Эльбрус» выпускаются для государственных нужд, но работают с современными языками — C, C++, Fortran.</p><h2>4. КуМир</h2><p>КуМир (Комплект Учебных МИРов) разработан в НИИСИ РАН в начале 2000-х годов. Его создали для обучения школьников. В отличие от Рапиры и Эль-76, КуМир пережил свою эпоху.</p><p>Синтаксис использует русские ключевые слова:</p><ul><li>алг (алгоритм),</li><li>нач (начало),</li><li>кон (конец),</li><li>цел (целое),</li><li>нц (начало цикла).</li></ul><p>КуМир включает визуальных исполнителей — Робот, Чертёжник, Черепаха. Школьник пишет команды вправо, вперед, закрасить и видит результат на экране. Исполнители помогают понять циклы, условия и процедуры без абстрактных примеров с числами.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/ca5a12df-a7a8-4ac7-ac7a-a4e31c67511f.jpg" alt="" /><figcaption>Программа с визуальным исполнителем «Черепаха»</figcaption></figure><p>Язык входит в школьную программу по информатике. Задания ЕГЭ публикуют на псевдокоде, который копирует синтаксис КуМира. Ученики решают задачи в среде КуМир, готовятся к экзамену.</p><p>Практическое применение ограничено образованием. КуМир не используют в коммерческой разработке, библиотек для веб-разработки или работы с базами данных. После школы ученики переходят на Python, JavaScript или C++.</p><h2>5. АЛМИР-65</h2><p>Название расшифровывается как «Алгоритмический язык для машины инженерных расчётов». Язык работал на компьютерах серии МИР — настольных машинах размером с письменный стол.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/e605882a-9be8-4d60-a09e-2140af0c48ea.jpg" alt="" /><figcaption>ЭВМ МИР-1</figcaption></figure><p>Синтаксис копировал математическую нотацию: формулы записывали почти так же, как в учебниках по математике. Инженер открывал руководство, смотрел пример и через час уже решал свои задачи без помощи программистов.</p><p>Язык обрабатывал числа с плавающей точкой, работал с массивами и матрицами. Компьютер МИР выводил результаты на экран сразу после ввода команды — диалоговый режим работы в 1965 году встречался редко.</p><p>АЛМИР-65 использовали конструкторские бюро, научные лаборатории и учебные заведения СССР. Инженеры рассчитывали прочность конструкций, физики моделировали эксперименты, студенты учились программированию. Идеи языка развили в АЛМИР-Аналитик.</p><h2>6. Аналитик</h2><p>Язык создали в 1968 году в Институте кибернетики АН УССР. Разработка продолжила линию языка АЛМИР-65. Первую версию Аналитика запустили на компьютерах МИР-2, созданных в том же институте.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/21e58a59-501d-4c68-afbe-2447fd815de9.jpg" alt="" /><figcaption>Советский компьютер МИР-2</figcaption></figure><p>Программист описывал абстрактные типы данных и работал с произвольными алгебрами — группами, кольцами, полями. Аналитик выполнял преобразования формул, упрощал выражения, решал уравнения в символьном виде.</p><p>В 1974 году вышла версия Аналитик-74 для компьютеров МИР-3. Язык применяли в научных расчётах — физике, математике, механике. Исследователи писали программы для вывода формул, дифференцирования, интегрирования, работы с матрицами и полиномами.</p><p>После распада СССР разработка не прекратилась, на его базе создали систему компьютерной алгебры Аналитик-2010. Современная версия работает на актуальных операционных системах и сохраняет идеи оригинального языка.</p><p>Практическое применение Аналитика ограничено академической средой.</p><h2>7. Робик</h2><p>Язык используют в российских школах с 2000-х годов. Синтаксис построен на кириллических ключевых словах.</p><p>Программа управляет виртуальным роботом на клетчатом поле. Робот выполняет команды движения, закрашивает клетки и проверяет наличие стен. Типичная задача: пройти лабиринт или закрасить фигуру по заданному алгоритму.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/eccbfd25-25e8-4872-b819-aaaf6b252a77.jpg" alt="" /><figcaption>Пример задачи на ЯП «Робик»</figcaption></figure><p>Робик содержит три управляющие конструкции:</p><ul><li>цикл со счётчиком (нц 10 раз … кц),</li><li>цикл с условием (нц пока свободно снизу … кц),</li><li>условный оператор (если закрашена то … иначе … все).</li></ul><p>Переменных, функций и типов данных нет — только последовательность команд.</p><p>Пример программы, которая закрашивает квадрат 3×3:</p><p>Робик входит в задания по информатике ОГЭ и ЕГЭ. Практическое применение ограничено образованием.</p><h2>8. ДРАКОН</h2><p>ДРАКОН разработали в 1980-х годах для советской космической программы. Расшифровка названия — Дружелюбный Русский Алгоритмический язык, Который Обеспечивает Наглядность. Программист не пишет код, а рисует блок-схемы.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/efaf4341-965c-47d4-882e-b5dab524a1a5.jpg" alt="" /><figcaption>Алгоритм поездки на автобусе в виде ДРАКОН-схемы</figcaption></figure><p>Язык использовали в НПО «Энергия» при разработке «Бурана». Сейчас существуют редакторы ДРАКОН-схем с генерацией кода на Python, C++, Java. Программист рисует схему, нажимает кнопку — получает исходники.</p><p>Практическое применение:</p><ul><li>документирование сложной логики,</li><li>проектирование критичных систем,</li><li>обучение алгоритмам.</li></ul><p>ДРАКОН-схема заменяет десятки страниц технического описания.</p><h2>9. ЯМБ</h2><p>Язык Машин Бухгалтерских разработали в конце 1970-х годов. Название связывают с инициалами Марины Борисовны Ярошевской — руководителя группы разработки из СКБ ВТ «Искра».</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/8bb654a4-eac6-4376-af50-b6dc373aac56.jpg" alt="" /><figcaption>Компьютер «Искра»</figcaption></figure><p>ЯМБ использовал русскоязычный синтаксис — команды записывали кириллицей. Программисты писали на нём приложения для учёта материальных ценностей, начисления зарплаты, формирования отчётности.</p><p>Язык адаптировали под специфику бухгалтерских задач:</p><ul><li>работу с таблицами,</li><li>суммирование колонок,</li><li>форматирование документов.</li></ul><p>Практическое применение ограничивалось советскими организациями. После перехода бухгалтерий на программы типа «1С» про ЯМБ забыли.</p><h2>10. 1С:Предприятие</h2><p>Платформу «1С:Предприятие» выпустили в 1991 году. Фирма «1С» создала её для автоматизации бухгалтерского учёта в российских компаниях.</p><p>Встроенный язык программирования использует русскоязычный синтаксис. Разработчики пишут команды кириллицей:</p><p>Платформа генерирует интерфейсы и таблицы базы данных автоматически. Программист описывает структуру справочников, документов, отчётов в визуальном конфигураторе. Система создаёт формы ввода, списки, печатные формы без ручного написания SQL или HTML.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-12-29/9f600126-8b0a-4b94-b005-457a196eacdf.jpg" alt="" /><figcaption>Программа в «1С:Предприятие»</figcaption></figure><p>Язык запросов встроен в платформу — разработчик получает данные через конструкции типа ВЫБРАТЬ Номенклатура, Количество ИЗ Документ.Товары.</p><p>Рынок 1С существует параллельно классической разработке. Вакансий публикуют больше, чем по Rust или Swift. Специалисты пишут типовые конфигурации, дорабатывают учётные системы под требования заказчиков, интегрируют 1С с сайтами и CRM.</p><h2>Выводы</h2><p>Большинство языков исчезли вместе с платформами, для которых создавались. Рапира, Эль-76, АЛМИР-65, ЯМБ остались в архивах и воспоминаниях — их вытеснили международные стандарты.</p><p>Три проекта выжили:</p><ul><li><b>КуМир </b>стал основой для подготовки к ЕГЭ.</li><li><b>ДРАКОН </b>занял нишу в документировании сложных систем.</li><li><b>1С </b>превратилась в отдельную индустрию с десятками тысяч вакансий.</li></ul><p>Язык программирования выживает благодаря экосистеме, сообществу и решению актуальных задач. Кириллица в синтаксисе работает технически, но практическая ценность определяется совместимостью с мировыми инструментами.</p><p>В 2026 году русскоязычные разработчики пишут на Python и JavaScript. Однако история советских языков напоминает: технологический суверенитет возможен, если есть задачи, ресурсы и готовность десятилетиями поддерживать разработку.</p>]]></content:encoded>
    </item>
    <item>
      <title>Пузырь 2021–2022 годов лопнул: эксперты составили прогноз рынка на 2026 год</title>
      <link>https://tproger.ru/news/puzyr-2021-2022-godov-lopnul--eksperty-sostavili-prognoz-rynka-na-2026-god</link>
      <comments>https://tproger.ru/news/puzyr-2021-2022-godov-lopnul--eksperty-sostavili-prognoz-rynka-na-2026-god?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/puzyr-2021-2022-godov-lopnul--eksperty-sostavili-prognoz-rynka-na-2026-god</guid>
      <description><![CDATA[<p>После пузыря 2021–2022 ИТ-рынок стабилизируется: в 2026 ждут низкий найм, меньше джунов и рост спроса на сильных инженеров</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/puzyr-2021-2022-godov-lopnul--eksperty-sostavili-prognoz-rynka-na-2026-god">Пузырь 2021–2022 годов лопнул: эксперты составили прогноз рынка на 2026 год</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Навыки]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Fullstack]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 30 Dec 2025 11:01:55 GMT</pubDate>
      <content:encoded><![CDATA[<p>IT-рынок пережил болезненную коррекцию после бума и перегрева 2021–2022 годов.</p><p>Тогда компании нанимали инженеров с запасом, под давлением цифровизации, дешевых денег и страха «остаться позади». По данным Indeed и FRED, пик вакансий пришелся на середину 2022 года. После чего последовал резкий спад, так и не вернувшись до прежнего уровня.</p><h2>Почему ИИ — удобный, но не главный виновник</h2><p>ИИ стал публичным оправданием сокращений. Заявление «мы заменяем людей на ИИ» хорошо смотрится в отчетах и перед инвесторами.</p><p>На практике же компании сокращали издержки, выравнивали штат и параллельно активнее нанимали за пределами США и Европы, где инженеры стоят дешевле.</p><p>Исследования показывают, что ИИ пока не делает разработчиков быстрее в реальной работе. Напротив, опытные инженеры иногда теряют в продуктивности из-за необходимости проверять и исправлять сгенерированный код.</p><h2>Как будет выглядеть рынок в 2026 году</h2><p>Эксперты <a href="https://www.finalroundai.com/blog/software-engineering-job-market-2026">сходятся</a> во мнении: 2026 год пройдет в режиме «низкого найма — низких увольнений». Массовых сокращений не ожидается, но и бурного роста вакансий не будет. Так выглядит стабилизация рынка.</p><p>При этом спрос смещается — универсальных «кодеров» нужно меньше. А вот инженеры, которые умеют проектировать системы, работать с производительностью, безопасностью и сложной интеграцией, наоборот будут все чаще находить вакансии под себя.</p><p>По прогнозу Бюро статистики труда США, число вакансий для разработчиков продолжит расти примерно на 15%. Это медленнее, чем ожидалось раньше, но все еще быстрее большинства профессий.</p><figure><img src="https://media.tproger.ru/user-uploads/111041/2025-12-30/3a3ad90f-f354-48b3-83bd-8857158de8e8.jpeg" alt="" /></figure><h2>Почему джунам повезло меньше всего</h2><p>Самое слабое место рынка — начальные позиции. Количество вакансий для junior-разработчиков упало примерно на 40% по сравнению с допандемийным уровнем.</p><p>Компании больше не готовы вкладываться в длительное обучение: им нужны люди, которые могут приносить пользу почти сразу.</p><p>Но это также создает отложенную проблему — без притока новичков рушится кадровый «конвейер».</p><h2>Какие навыки реально востребованы</h2><p>В 2026 году ценятся не языки сами по себе, а связки «язык + область».</p><p>Тот же Python чаще всего нужен при работе с ИИ и данными. JavaScript и TypeScript — для фронтенда и fullstack. Go — для бэкенда и инфраструктуры. Java — для enterprise-систем.</p><p>Параллельно растет спрос на ИИ- и дата-инженеров, а также специалистов по ИИ-инфраструктуре.</p>]]></content:encoded>
    </item>
    <item>
      <title>JavaScript для детей: зачем ребенку «взрослый» язык программирования и с чего начать обучение</title>
      <link>https://tproger.ru/articles/javascript-dlya-detej--zachem-rebenku--vzroslyj--yazyk-programmirovaniya-i-s-chego-nachat-obuchenie</link>
      <comments>https://tproger.ru/articles/javascript-dlya-detej--zachem-rebenku--vzroslyj--yazyk-programmirovaniya-i-s-chego-nachat-obuchenie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Блог Школа программирования Пиксель]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/javascript-dlya-detej--zachem-rebenku--vzroslyj--yazyk-programmirovaniya-i-s-chego-nachat-obuchenie</guid>
      <description><![CDATA[<p>Хотите познакомить ребенка с «настоящим» программированием? Отличный старт для детей — JavaScript! Рассказываем, почему этот язык так хорош для новичков, что на нем можно сделать и с чего начать обучение.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/javascript-dlya-detej--zachem-rebenku--vzroslyj--yazyk-programmirovaniya-i-s-chego-nachat-obuchenie">JavaScript для детей: зачем ребенку «взрослый» язык программирования и с чего начать обучение</a>»</p>]]></description>
      <category><![CDATA[Партнёрский материал]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 29 Dec 2025 15:20:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Современные веб-сайты — это не просто электронные книги с текстом и картинками. Это полноценные интерактивные приложения, где можно играть, смотреть видео в реальном времени, общаться и работать с документами. За эту динамику и интерактивность отвечает специальный язык программирования — JavaScript.</p><p>Технически любая веб-страница строится на трех технологиях:</p><ul><li>HTML (гипертекстовая разметка) задает структуру и содержание (заголовки, абзацы, кнопки).</li><li>CSS (стили) определяет внешний вид (цвета, шрифты, расположение элементов).</li><li>JavaScript отвечает за интерактивность и поведение всех элементов. Именно он заставляет страницу реагировать на действия пользователя.</li></ul><p>Выпадающее меню, всплывающее окно подтверждения, обновляющаяся без перезагрузки лента новостей или браузерная игра — во всех этих процессах работает JavaScript. Это делает его основным инструментом для создания цифровых интерфейсов, с которыми ежедневно взаимодействуют миллиарды людей.</p><h2>Почему JavaScript — оптимальный первый «настоящий» язык программирования для детей</h2><p>Выбор первого текстового языка программирования — важное решение. Python, Java, C# — у каждого есть свои достоинства. Однако для детей, которые делают первые шаги в программировании, у JavaScript есть свой, уникальный набор преимуществ.</p><h3>Низкий порог входа и мгновенная обратная связь</h3><p>Главный враг интереса к программированию на старте — долгая и сложная настройка среды разработки. Этой проблемы не существует с JavaScript. Детям нужен лишь любой современный браузер (Chrome, Firefox, Edge) и простейший текстовый редактор, даже встроенный «Блокнот».</p><p>Любой код можно проверить мгновенно: сохранить файл с расширением .html или .js и открыть его в браузере. Более того, прямо в браузере есть встроенная Консоль разработчика (Developer Tools). Это интерактивная среда, где можно писать и выполнять команды на JavaScript построчно и сразу видеть результат или ошибки. Такая немедленная визуальная отдача превращает обучение в серию быстрых экспериментов: «А что будет, если изменить эту цифру? А если добавить эту команду?».</p><h3>Визуальность и креативность</h3><p>В отличие от языков, где первые программы часто выводят в консоль лишь текст (например, Hello, World!) или результат вычисления, JavaScript изначально ориентирован на работу с визуальными элементами.</p><p>Уже через несколько уроков ребенок сможет написать программу, которая:</p><ul><li>изменит цвет или текст любой кнопки на сайте;</li><li>создаст анимацию движения объекта по экрану;</li><li>сделает интерактивную фотогалерею или слайдер.</li></ul><p>Для более сложных творческих задач существует элемент HTML5 Canvas — «цифровой холст», на котором с помощью JavaScript можно рисовать графику, создавать 2D- и даже простые 3D-игры, визуализировать данные. С JavaScript дети получают зрелищный, осязаемый результат, что принципиально важно для удержания внимания и развития творческого мышления.</p><h3>Универсальность и понимание полного цикла разработки приложений</h3><p>JavaScript — это редкий пример языка, который доминирует на одной стороне («клиентской» или фронтенде; это то, что видят пользователи в окне браузера) и при этом полноценно работает на другой («серверной» или бэкенде; то, что «незаметно» происходит на сервере, где обрабатываются данные).</p><p>На фронтенде — это единственный язык, который исполняют все веб-браузеры. Его изучение дает полное понимание того, как устроено любое веб-приложение, с которым взаимодействует пользователь.</p><p>На бэкенде JavaScript тоже используют — благодаря платформе Node.js на нем можно писать серверную логику: работать с базами данных, формировать ответы на запросы, организовывать обмен данными в реальном времени.</p><p>Такая универсальность JavaScript позволяет ребенку, начав с простых скриптов для страницы, в перспективе разобраться с полным циклом создания веб-приложений — от интерфейса до сервера. А это открывает путь к самым востребованным профессиям в IT.</p><h2>Какие концепции программирования осваивает ребенок с JavaScript и почему они важны</h2><p>Программирование на JavaScript для детей — это освоение фундаментальных понятий, которые лежат в основе большинства современных программ.</p><h3>Базовые понятия программирования</h3><p>Первым делом ребенок знакомится с элементарными строительными блоками любого языка программирования:</p><ul><li>Переменные (в JavaScript они обозначаются как let или const) — это коробки, или контейнеры, для хранения данных: чисел, текста, списков. Они работают по принципу математической переменной. Например, let score = 0 создает «ящик» с надписью score, где хранится число 0. Это основа для любых изменяющихся данных, например, счетчика очков в игре или имени пользователя.</li><li>Типы данных — компьютер различает числа, текст (строки), логические значения true/false (правда/ложь). Понимание типов помогает избежать ошибок: нельзя сложить число и слово, но можно соединить два слова.</li><li>Операторы — это знаки действий. Они бывают арифметические (+, -, *, /) — здесь прямая связь с математикой и работают они так же, как в математике. Есть операторы сравнения (&gt;, ===, &lt;=) и логические (&amp;&amp; — «и», || — «или») — они отвечают за логику и необходимы для принятия решений в программе.</li></ul><p>Эта база программирования, которая учит детей внимательности и дисциплине. Они понимают, что программа — это последовательность однозначных команд для машины. А ошибка (опечатка, пропущенная скобка) приведет к сбою.</p><h2>Интерактивность и программирование событий</h2><p>Сначала дети осваивают статические программы на JavaScript, которые просто выводят результат, затем переходят к интерактивным. На этом этапе нужно познакомиться с событиями и функциями:</p><ul><li>События (events) — это любое действие пользователя: клик мышью (onclick), нажатие клавиши (onkeypress), движение курсора.</li><li>Ребенок учится создавать функции — блоки кода, которые выполняются по требованию, и «привязывать» их к событиям.</li></ul><p>Например:</p><p>Этот код формирует прямую причинно-следственную связь: если произошло событие (клик), то выполнить действие (показать окно). Это основа логики любого приложения — от банковского и до сложной игры.</p><h3>Работа с DOM (Document Object Model) и управление страницей</h3><p>DOM — это программное представление структуры HTML-страницы в виде дерева объектов. Детям это можно объяснить так: браузер превращает всю страницу (заголовки, абзацы, кнопки, картинки) в особую «карту», которой может управлять JavaScript:</p><ul><li>Код может найти любой элемент на этой карте, например, заголовок — он обозначается как &lt;h1&gt;.</li><li>Код может изменить этот элемент: поменять текст, цвет, размер, скрыть или переместить его.</li></ul><h2>Первая игровая логика: Canvas и анимация</h2><p>Для создания графики и игр используется элемент HTML5 Canvas («холст»). Это область на странице, где каждым пикселем можно управлять с помощью JavaScript.</p><ul><li>Дети учатся рисовать геометрические фигуры, выводить изображения.</li><li>Осваивают принципы анимации: чтобы объект на экране двигался, нужно циклически (30-60 раз в секунду) стирать его со старой позиции и рисовать на новой. Так изучаются циклы и таймеры.</li><li>Обработка столкновений — следующая ступень. Это уже прикладная геометрия: программа постоянно проверяет, пересеклись ли координаты одного объекта с координатами другого, и реагирует (например, при столкновении мяча с платформой он меняет направление).</li></ul><p>Этот этап объединяет все предыдущие знания: переменные хранят координаты, события управляют героем, функции передают правила отрисовки, а работа с Canvas — это управление особым элементом DOM.</p><h2>Полезные ресурсы и курсы по JavaScript для детей</h2><p>Здесь мы собрали все, что поможет детям освоить программирование на JavaScript: уроки, курсы, полезные сайты с тренажерами и даже книги!</p><h3>Видеоуроки и онлайн-курсы по JavaScript для детей</h3><p>Мы рекомендуем начать с видеоуроков на наших каналах на <a href="https://youtube.com/playlist?list=PLdzeMLV8u_l406gFs_XAR36XmX8N2SouY&amp;si=VXY07vZmmaSArPF9">YouTube</a> и <a href="https://rutube.ru/plst/186177/">RuTube</a> или <a href="https://stepik.org/course/108924/promo">образовательной платформе Stepik</a>.</p><p>Также приглашаем на онлайн-курс <a href="https://pixel.study/htmlcss?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=javascript-dlya-detey-zachem-rebenku-vzroslyy-yazyk-programmirovaniya-i-s-chego-nachat-obuchenie">«Создание сайтов на языках HTML, CSS и JavaScript для детей»</a> у нас, в Школе программирования «Пиксель». Здесь дети работают над собственными интерактивными сайтами, которые станут частью их взрослого портфолио.</p><p>Первый урок курса можно пройти бесплатно. Это полноценный урок с преподавателем, на котором ребенок:</p><ul><li>познакомится с основами программирования на JavaScript,</li><li>напишет свой первый код, увидит его результат,</li><li>сможет задать любые вопросы преподавателю,</li><li>на практике проверит, насколько ему интересен этот язык.</li></ul><p><a href="https://pixel.study/htmlcss?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=javascript-dlya-detey-zachem-rebenku-vzroslyy-yazyk-programmirovaniya-i-s-chego-nachat-obuchenie">Узнать подробнее о курсе можно здесь</a>.</p><p>А <a href="https://pixel.study/demo?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=javascript-dlya-detey-zachem-rebenku-vzroslyy-yazyk-programmirovaniya-i-s-chego-nachat-obuchenie">записаться на бесплатный первый урок здесь</a>.</p><p>Еще один полезный ресурс — бесплатный курс <a href="https://code-basics.com/ru/languages/javascript">«JavaScript для начинающих»</a>.</p><h3>Интерактивные тренажеры по JavaScript для детей</h3><p>Самые известные — интерактивные платформы <a href="https://www.codecademy.com/learn/introduction-to-javascript">Codecademy </a>и <a href="https://www.freecodecamp.org/">freeCodeCamp</a>. Они на английском, но английский — это язык программистов всего мира, так что попотеть и разобраться в нем стоит.</p><p>Как они работают? Ребенок видит теоретическое объяснение, затем окно редактора с частично написанным кодом. Его задача — дописать недостающую строку или исправить ошибку. А система мгновенно проверит его решение.</p><h3>Браузерные игры для детей по программированию и JavaScript</h3><p><a href="https://www.codingame.com/ide/puzzle/onboarding">CodinGame</a>. Ваш ребенок будет решать алгоритмические задачи, набирая код на JavaScript, и сразу увидит, как его команды оживляют игровых персонажей на экране.</p><p><a href="https://codecombat.com/play/level/dungeons-of-kithgard">CodeCombat</a>. Дети отправятся в фэнтезийное приключение, где для того, чтобы двигать героя, сражаться и решать головоломки, нужно писать реальные строчки кода.</p><p><a href="https://www.codewars.com/">CodeWars</a>. Ребенок будет тренировать навыки JavaScript, решая задачи на логику разной сложности и сможет сравнить свои решения с вариантами других участников.</p><p><a href="https://jsdares.com/">JSDares</a>. Здесь дети получают от сообщества небольшие практические задания, например, создать мини-игру или эффект на JavaScript, и учатся применять знания на конкретных примерах.</p><p><a href="https://warriorjs.com/">WarriorJS</a>. В этой игре-головоломке ребенок напишет искусственный интеллект для своего героя, чтобы тот сражался с врагами и преодолевал препятствия, поднимаясь по уровням башни. Каждый новый этаж требует применения более сложных приемов программирования.</p><p><a href="https://screeps.com/">Screeps</a>. Это масштабная онлайн-стратегия, где для управления колонией и юнитами дети пишут настоящий код на JavaScript, который работает постоянно, даже когда они вышли из игры. Это учит автоматизации, планированию и проектированию сложных систем.</p><p><a href="https://untrustedgame.com/">Untrusted</a>. Ребенок выступит в роли хакера, используя консоль браузера и JavaScript, чтобы изменять саму игру и таким образом проходить сложные лабиринты.</p><p><a href="https://www.crunchzilla.com/">Crunchzilla</a>. На этой платформе дети начинают с основ: меняют числа и команды в готовом коде, чтобы сразу увидеть, как меняется анимация или игра.</p><p><a href="https://lab.reaal.me/jsrobot/">JSRobot</a>. Чтобы пройти уровень, ребенку нужно написать код, который будет управлять роботом: заставлять его двигаться, собирать предметы или избегать опасностей.</p><p><a href="https://play.elevatorsaga.com/">Elevator Saga</a>. Задача ребенка — запрограммировать логику работы лифтов в здании так, чтобы они успевали обслуживать всех пассажиров. Сложность постепенно увеличивается.</p><h3>Учебники по JavaScript для детей</h3><p><b>«JavaScript для детей» Ника Моргана</b>. Популярное пособие на русском языке, которое обучает языку через создание простых игр (змейка, виселица). Подходит для детей от 10 лет.</p><p><b><a href="http://Learn.javascript.ru">Learn.javascript.ru</a> </b>— главный русскоязычный онлайн-учебник. Он может быть сложен для совсем маленьких, но идеален для подростков 14+ как справочный ресурс.</p><p>JavaScript открывает детям двери в мир «взрослого» программирования. Чтобы этот старт был уверенным и осознанным, следуйте простому алгоритму:</p><ol><li>Посмотрите с ребенком бесплатные уроки и предложите повторить их или проверить знания в браузерных играх.</li><li>Пройдите бесплатный пробный урок. Это поможет вам объективно оценить интерес ребенка к программированию и JavaScript, его готовность заниматься, познакомиться с форматом обучения.</li><li>Если эксперименты показывают устойчивый интерес — значит нужен более системный подход. Структурированный курс с четкой программой, поддержкой преподавателя и работой над собственными проектами — то, что нужно ребенку на этом этапе.</li></ol><p><i>Реклама. Рекламодатель ООО «Пиксель.Стади», ИНН 5074078988, erid: 2W5zFJq93Sp</i></p>]]></content:encoded>
    </item>
    <item>
      <title>Как мы разрабатываем систему голосового управления презентациями на базе Whisper и GigaChat</title>
      <link>https://tproger.ru/articles/ai-kliker--kak-my-razrabatyvaem-sistemu-golosovogo-upravleniya-prezentaciyami-na-baze-whisper-i-gigachat</link>
      <comments>https://tproger.ru/articles/ai-kliker--kak-my-razrabatyvaem-sistemu-golosovogo-upravleniya-prezentaciyami-na-baze-whisper-i-gigachat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сергей Киприн]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/ai-kliker--kak-my-razrabatyvaem-sistemu-golosovogo-upravleniya-prezentaciyami-na-baze-whisper-i-gigachat</guid>
      <description><![CDATA[<p>Как создать инструмент, который позволяет переключать слайды с помощью голосовых команд и контекстного анализа речи. В статье разбирается микросервисная архитектура на React и Python (FastAPI), использование модели OpenAI Whisper для транскрибации в реальном времени и интеграция LLM GigaChat для интеллектуального ведения презентации. Также описываются проблемы нестабильности нейросетей в живых выступлениях и реализованные решения: режим байпаса и навигация по ключевым словам.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/ai-kliker--kak-my-razrabatyvaem-sistemu-golosovogo-upravleniya-prezentaciyami-na-baze-whisper-i-gigachat">Как мы разрабатываем систему голосового управления презентациями на базе Whisper и GigaChat</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Практика]]></category>
      <category><![CDATA[Распознавание]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 27 Dec 2025 11:40:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Наша команда разрабатывает инструмент для интеллектуального управления презентациями.</p><p>Пользователь может транслировать презентацию через "AI Кликер", а сервис будет записывать и транскрибировать речь спикера и аудитории. Спикер может переключать слайды простыми голосовыми командами или ключевыми словами, привязанными к конкретным слайдам.</p><p>Интеграция LLM позволяет реализовать интеллектуальные функции - переключение слайдов по описанию, контексту, генерация нового контента в реальном времени. Целевая аудитория — учителя, преподаватели и спикеры, на постоянной основе работащие с презентациями.</p><h2>Архитектура решения</h2><p>Наша команда использует JavaScript с React на фронтенде и Python c FastAPI на бэкенде. Мы используем PosgtreSQL как базу данных и S3 как объектное хранилище для хранения презентаций. Мы строим микросервисную архитектуру в нашем приложении.</p><p>Первый сервис - REST API для авторизации, управления личным кабинетом и контентом пользователя. Развернут на отдельном дешевом сервере за прокси NGINX, запросы шифруются HTTP</p><figure><img src="https://media.tproger.ru/user-uploads/135058/2025-12-16/91ac9c1a-c7e9-46e6-903d-8224c80bf981.png" alt="" /><figcaption>Архитектура REST API</figcaption></figure><p>Второй сервис - API трансляции презентации. Двустороннее взаимодействие браузера и этого сервиса (передача звука и отдача команд по переключению слайдов) идет по протоколу WebSocket Secure. Отдельный сервис для трансляции позволяет нам динамически управлять дорогими серверами с GPU, уничтожать их, поднимать, экспериментировать с конфигурациями, не переживая о сохранности основного приложения. Модель OpenAI Whisper используется для транскрибации речи и GigaChat от Сбера как LLM для анализа контекста выступления и генерации команд и контента.</p><figure><img src="https://media.tproger.ru/user-uploads/135058/2025-12-16/4ef40e63-5b61-4e65-997d-ff80ac16061b.png" alt="" /><figcaption>Архитектура сервиса трансляции</figcaption></figure><h2>Работа Whisper</h2><p>В AI-кликере распознавание речи - фундамент всей системы. Пока человек говорит, система должна получить текст, проверить команды, сопоставить с ключевыми словами, передать в LLM. Для этого обычного Whisper недостаточно — он отлично работает с файлами, но плохо подходит для живого потока.</p><p>Поэтому в проекте используется связка Whisper + <a href="https://github.com/QuentinFuxa/WhisperLiveKit">WhisperLiveKit</a> — библиотека, которая позволяет кормить модель аудио чанками в реальном времени, а не загружать готовые записи.</p><h2>Байпас: ручное управление голосом</h2><p>Одна из ключевых фич — байпас-режим. Это механизм, который позволяет управлять презентацией жёсткими голосовыми командами, в обход всей нейросетевой логики.</p><p>Когда Whisper отдаёт текст, он сначала проверяется не через LLM, а через механизм сопоставления фраз:</p><p>Дальше всё работает просто:</p><ul><li>если пользователь сказал фразу, похожую на “кликер вперёд” → слайд листается вперёд;</li><li>если “кликер назад” → слайд листается назад.</li></ul><p>Данная реализация позволяет пользователю в любой момент перебить автоматику и принудительно перелистнуть слайд. Это критично для надёжности: если LLM вдруг “поплыла”, у спикера всегда есть жёсткий голосовой руль.</p><h2>Ключевые слова: автоматический переход на нужный слайд</h2><p>Вторая важная часть — режим ключевых слов. Это уже не просто “вперёд/назад”, а сопоставление речи с конкретными слайдами.</p><p>Логика такая:</p><ol><li>Для презентации загружается набор ключевых фраз, который группируется по номеру слайда.</li><li>Когда приходит новый фрагмент речи — он сравнивается с ключевыми словами и в случае успеха отправляет команду на переключение:</li></ol><p>То есть фактически система работает как голосовой навигатор по презентации:</p><ul><li>сказал фразу, связанную со слайдом 5 → система предлагает перейти на 5;</li><li>сказал фразу из блока 10 → прыжок сразу туда</li></ul><p>При этом переход отправляется в очередь и дополнительно проверяется по порогу уверенности:</p><p>Слайд переключается только если вероятность достаточная. Это защищает от случайных совпадений и шумов.</p><h2>Работа с LLM (Гигачат) - промпты, контексты, очереди</h2><p>Если Whisper в AI-кликере отвечает за то, чтобы услышать, то GigaChat отвечает за то, чтобы понять, о чём сейчас говорит спикер и какой слайд этому соответствует. Вся работа с LLM построена асинхронно: распознанная речь не блокирует систему, а складывается в очередь и обрабатывается в фоне. Каждый фрагмент речи:</p><ul><li>попадает в очередь,</li><li>добавляется в историю пользователя,</li><li>отправляется в GigaChat,</li><li>а результат в виде вероятностей по слайдам уходит дальше в очередь переключения.</li></ul><p>Такой подход позволяет не тормозить распознавание речи, не зависеть от времени ответа нейросети, обрабатывать несколько фрагментов параллельно.</p><h2>В чём возникла основная проблема?</h2><p>Текущая концепция работы LLM строится на предугадывании номера слайда по смыслу речи. И именно здесь в реальных условиях всплыла главная боль проекта. На живых выступлениях выяснилось, что GigaChat может путаться между похожими слайдами и менять своё решение от фразы к фразе. В итоге презентация иногда начинала хаотично перелистываться, даже если спикер говорил последовательно и спокойно. Для пользователя это выглядит как будто “нейросеть сошла с ума и живёт своей жизнью”.</p><p>Пока проблема не решена полностью, в системе есть страховочный механизм — временное отключение GigaChat при ручном управлении. Если пользователь даёт жёсткую голосовую команду (байпас), то нейросеть временно игнорируется, а управление полностью возвращается человеку. Через несколько секунд автоматика включается обратно, что позволяет не ломать выступление даже в моменты, когда LLM начинает вести себя нестабильно</p><p>Практика показала, что угадывать слайд по смыслу — слишком нестабильная стратегия для живого продукта. Поэтому сейчас происходит переход к новой модели:</p><ul><li>было - нейросеть угадывает номер следующего слайда.</li><li>станет - нейросеть находит слайд по его описанию. После определенной команды, система слушает пользователя, пока он не закончит предложение (как умная колонка) и тогда один раз выбирает и переключает слайд. Это снижает вероятность случайных переключений и делает поведение системы более предсказуемым.</li></ul><h2>Итоги</h2><p>Мы продолжаем реализацию проекта и планируем скорый выход на рынок. Реализация местами еще сырая, но мы стараемся тестировать решение с фокус-группой профессионалов, работающих с презентациями, чтобы выработать лучшие решения, удобные для них. У нас получилось выиграть небольшой студенческий грант на реализацию, это поможет нам оплатить сервера и работу разработчиков.</p>]]></content:encoded>
    </item>
    <item>
      <title>Топ-5 популярных языков программирования для детей и не только</title>
      <link>https://tproger.ru/articles/yazyki-programmirovaniya-dlya-detej--kakoj-vybrat-i-gde-uchit</link>
      <comments>https://tproger.ru/articles/yazyki-programmirovaniya-dlya-detej--kakoj-vybrat-i-gde-uchit?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Блог IT для детей]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/yazyki-programmirovaniya-dlya-detej--kakoj-vybrat-i-gde-uchit</guid>
      <description><![CDATA[<p>Какой язык программирования выбрать ребенку, чтобы было не только интересно, но и пригодилось в будущем? Рассказываем о пятерке лидеров — Scratch, Python, JavaScript, Lua и C#.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/yazyki-programmirovaniya-dlya-detej--kakoj-vybrat-i-gde-uchit">Топ-5 популярных языков программирования для детей и не только</a>»</p>]]></description>
      <category><![CDATA[Партнёрский материал]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 18 Dec 2025 09:00:04 GMT</pubDate>
      <content:encoded><![CDATA[<p>Выбор первого языка программирования для детей — это целая стратегия. Дать слишком сложный — и ребенок решит: «это не для меня». Выбрать слишком простой — и ему быстро станет скучно. Но правильно выбранный первый язык становится волшебным ключом. Он не просто открывает дверь в IT — он удерживает интерес, потому что позволяет быстро создавать то, что радует: первую игру, ожившую анимацию, полезного чат-бота. Параллельно он незаметно формирует правильное, алгоритмическое мышление — а это будет полезно не только программистам.</p><p>В этой статье мы рассмотрим 5 самых востребованных языков программирования, подходящих для детей, — от визуальных, понятных даже младшим школьникам, до мощных «взрослых», на которых построены современные технологии. Вы узнаете не только что это за языки, но и почему они стали популярными, какие задачи решают и, самое главное, как понять, какой из них идеально подойдет для увлечений и склада ума именно вашего ребенка.</p><h2>Визуальный язык программирования Scratch</h2><figure><img src="https://media.tproger.ru/user-uploads/134768/2025-12-16/ab8d74e8-0e87-4ab8-bfa7-1e2a27a95bcd.jpg" alt="Логотип Scratch — языка программирования для детей" /></figure><p>Представьте, что перед ребенком — не пустой экран редактора кода, а красочная палитра команд: «двигаться», «повернуть», «повторить», «если-то». Это и есть Scratch — визуальная среда, созданная в 2003 году командой под руководством Митчела Резника в легендарном Массачусетском технологическом институте. Они задались вопросом: как сделать программирование таким же интуитивным и увлекательным, как сборка конструктора Lego? Ответом и стал Scratch, где код «собирается» из цветных блоков.</p><p>Почему Scratch идеален для младших школьников:</p><ul><li>С ним нет страха перед ошибками. Главный барьер для новичка — синтаксис. Одна пропущенная скобка или точка с запятой ломает всю программу. В Scratch такой проблемы просто не существует. Блоки подходят друг к другу, только если это логически верно, и дети с первых минут думают не о правилах написания, а о логике действий.</li><li>Мгновенная обратная связь и визуальный результат. Нажал «запустить» — и спрайт (персонаж) побежал по экрану, заиграла музыка, сменился фон. Эта наглядность — хороший мотиватор. Детям сложно воспринимать абстракции, а здесь они создают осязаемую интерактивную историю, игру или мультфильм.</li><li>Развитие вычислительного мышления. Scratch учит сути программирования: разбивать сложную задачу на простые шаги, видеть закономерности, использовать условия и циклы. Это и есть основа алгоритмического мышления, причем освоенная в игровой форме.</li></ul><p>Scratch ошибочно считают несерьезным. На самом деле, это идеальный тренажер для детского мозга. Осваивая основные концепции программирования (переменные, события, циклы) в визуальной форме, ребенок совершает огромный интеллектуальный труд. Переход к текстовым языкам потом происходит в разы легче — остается только выучить синтаксис для уже знакомых идей.</p><p>Где заниматься Scratch онлайн:</p><ul><li>Курсы в школе программирования «Пиксель». Их преимущество — есть целых 3 курса по Scratch, рассчитанных на разный возраст учеников:<br /><a href="https://pixel.study/programmirovanie-dlya-mladshikh-shkolnikov?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=top-5-populyarnykh-yazykov-programmirovaniya-dlya-detey-i-ne-tolko">Программирование в Scratch Junior и Kodu Game Lab</a> для детей 5-7 лет,<br /><a href="https://pixel.study/scratch-detskoe-programmirovanie?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=top-5-populyarnykh-yazykov-programmirovaniya-dlya-detey-i-ne-tolko">Программирование в Scratch</a> для детей 6-9 лет,<br /><a href="https://pixel.study/scratch?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=top-5-populyarnykh-yazykov-programmirovaniya-dlya-detey-i-ne-tolko">Создание игр и анимации. Визуальная среда Scratch</a> для детей 9-12 лет.</li></ul><ul><li><a href="https://programmirovanie.skysmart.ru/scratch">Scratch для детей и подростков</a> в онлайн-школе Skysmart. Это универсальный курс для школьников всех возрастов.</li></ul><ul><li><a href="https://xn----8sbahd5acjvaoefhnccei2b0m3d.xn--p1ai/kursy-programmirovaniya/sozdaniye-igr-scratch">Scratch для детей — визуальное программирование и создание игр с нуля</a> в UnionCode.</li></ul><h2>Лучший первый текстовый язык программирования для детей — Python</h2><figure><img src="https://media.tproger.ru/user-uploads/134768/2025-12-16/f331ed6a-3103-4a03-aa54-dccab3e3ba7a.png" alt="Логотип Python — лучшего первого текстового языка программирования для детей" /></figure><p>Если Scratch похож на конструктор, то Python — это первый настоящий текстовый язык, который рекомендуют детям. Он удивительно эргономичен, точен, и при этом с ним можно создать буквально что угодно. А его философия зашифрована в принципе «Дзен Пайтона» — так называют сборник из 20 правил, которыми руководствуются разработчики при написании кода: простое лучше, чем сложное; читаемое лучше, чем непонятное; явное лучше, чем скрытое и т.д.</p><p>Этот язык программирования был создан в конце 1989 года голландским программистом Гвидо ван Россумом. Он начал работать над ним в качестве хобби, в рождественские каникулы, мечтая устранить недостатки другого языка — ABC. Название он позаимствовал не у змеи, а от британского комедийного шоу «Летающий цирк Монти Пайтона». Гвидо хотел, чтобы язык ассоциировался с чем-то забавным и несерьезным. Но жизнь тоже решила пошутить, и сегодня Python — один из самых серьезных и востребованных языков в мире.</p><p>Почему Python — лучший первый текстовый язык для детей:</p><ul><li>Код на нем часто читается как короткие инструкции на английском. Например, для вывода текста нужна простая команда <b>print("Привет, мир!")</b>.</li><li>Мощный, простой, универсальный, подходящий для всего — от игры до нейросети. Его сила в его гибкости и огромной коллекции библиотек — готовых модулей с кодом. С ними ребенок даже на старте обучения сможет написать текстовый квест или графическую игру, создать полезного телеграм-бота, нарисовать сложные фрактальные узоры или обработать изображения, проанализировать реальные данные — например, статистику своих игровых результатов.</li><li>От простой идеи до рабочего прототипа путь очень короткий, что постоянно подпитывает интерес.</li><li>Python прививает культуру «чистого кода» с самого начала. Из-за того, что для обозначения блоков кода используются отступы (пробелы), а не скобки, дети интуитивно учатся писать аккуратный, структурированный и легко читаемый код. А это хорошая профессиональная привычка.</li></ul><p>Python — это редкий случай, когда детское хобби может стать прямым билетом в самые передовые и высокооплачиваемые IT-сферы:</p><ul><li>Искусственный интеллект и Data Science — библиотеки TensorFlow, PyTorch и scikit-learn сделали Python главным языком машинного обучения.</li><li>Веб-разработка — фреймворки Django и Flask лежат в основе миллионов сайтов, среди которых Pinterest, Spotify, YouTube, Netflix.</li><li>Автоматизация и аналитика — его используют системные администраторы, аналитики и ученые для обработки данных и автоматизации рутины.</li></ul><p>Лучшие онлайн-курсы для изучения Python:</p><ul><li>Курсы в школе программирования «Пиксель» для детей разного возраста:<br /><a href="https://pixel.study/minecraft?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=top-5-populyarnykh-yazykov-programmirovaniya-dlya-detey-i-ne-tolko">Игровая вселенная Minecraft. Программирование на языке Python</a> для детей 9-13 лет,<br /><a href="https://pixel.study/python?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=top-5-populyarnykh-yazykov-programmirovaniya-dlya-detey-i-ne-tolko">Основы программирования на Python</a> для детей 10-14 лет,<br /><a href="https://pixel.study/django-for-children?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=top-5-populyarnykh-yazykov-programmirovaniya-dlya-detey-i-ne-tolko">Веб-разработка Python Django</a> для старших школьников 14-17 лет.</li></ul><ul><li>Курс <a href="https://www.progkids.com/courses/python">Программирование на Пайтон</a> для детей от 10 лет в школе ProgKids.</li></ul><ul><li><a href="https://gb.ru/courses/geek-school/python-pro">Программирование на Python</a> для детей 11-14 лет в онлайн-школе GeekSchool.</li></ul><h2>JavaScript — язык для веба, оживляющий интернет-страницы</h2><figure><img src="https://media.tproger.ru/user-uploads/134768/2025-12-16/7e5c5370-a0e5-4358-8400-614d151823dd.jpeg" alt="Логотип JavaScript — перспективного языка программирования для детей" /></figure><p>Представьте себе веб-страницу как театральную сцену. HTML с текстом, картинками, встроенными видео — это декорации и актеры, CSS — их костюмы и грим, а JavaScript — это режиссер и сценарист, который говорит актерам, когда им выходить, что делать и как реагировать на действия зрителей. Без него Интернет был бы похож на красивую, но статичную картинную галерею.</p><p>Интересный факт: JavaScript был создан за 10 дней. Это было в 1995 году, его создатель Брендан Эйх работал над браузером Netscape Navigator. Компании нужен был «язык скриптов для дизайнеров», который можно было бы встраивать прямо в HTML-страницы. Изначально язык назывался Mocha, затем LiveScript, а свое окончательное имя — JavaScript — получил как маркетинговый ход, чтобы «прокатиться» на волне популярности языка Java. Но несмотря на созвучность, это два абсолютно разных языка.</p><p>JavaScript подходит школьникам и подросткам, потому что с ним результат виден сразу. Это самый наглядный язык программирования из всех текстовых. Написал несколько строк кода — обновил вкладку в браузере — и сразу видишь, как кнопка изменила цвет, изображение «уехало» в сторону или появилось всплывающее окно. Это возможно, потому что в любом браузере есть встроенный интерпретатор JavaScript — то есть он может обрабатывать код и выводить визуальный результат.</p><p>JavaScript — это ключ к созданию динамического контента, и не только в вебе, но и для любой анимации и игр. С его помощью можно:</p><ul><li>сделать сайт интерактивным (слайдеры, всплывающие формы, анимации);</li><li>написать полноценную браузерную игру на HTML5 Canvas;</li><li>создать веб-приложение, работающее как настольное (например, простой фоторедактор или клиент для заметок).</li></ul><p>Начать можно с простых скриптов прямо в консоли браузера. По мере роста появляется доступ к огромной экосистеме: фреймворки (React, Vue), серверная платформа (Node.js), мобильная разработка. Это показывает ребенку, как с одним языком можно масштабироваться от написания небольших косвенных деталей до глобального проекта.</p><p>JavaScript — это безальтернативный стандарт для фронтенд-разработки (того, что видит пользователь). Но его применяют гораздо шире. С появлением Node.js JavaScript вышел за пределы браузера. Теперь на нем можно писать и серверную часть приложений — то, что происходит «за кадром». Это делает возможной профессию фуллстек-разработчика, владеющего одним языком для всех задач.</p><p>JS применяют в мобильной и десктопной разработке — такие фреймворки, как React Native, позволяют создавать мобильные приложения для iOS и Android.</p><p>Если ребенок фанатеет от красивых сайтов, хочет создать свою браузерную игру или мечтает о приложении, которое увидят друзья, то JavaScript — его язык.</p><p>Лучшие онлайн-курсы в этом направлении:</p><ul><li><a href="https://gb.ru/courses/geek-school/website-development#projects">Веб-разработка и создание сайтов для детей</a> в онлайн-школе GeekSchool.</li></ul><ul><li><a href="https://www.progkids.com/courses/web">Создание сайтов для детей (HTML+CSS+JS)</a> в ProgKids.</li></ul><ul><li><a href="https://pixel.study/traektoria-razrabotchik-sajtov-i-prilozhenij?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=top-5-populyarnykh-yazykov-programmirovaniya-dlya-detey-i-ne-tolko">Образовательная траектория Fullstack-разработчик</a> для детей от 14 до 17 лет в школе «Пиксель».</li></ul><h2>Lua и C# — языки для геймдева</h2><p>Эти два языка идеально подходят для подростков, чье увлечение играми переросло в желание понять их внутреннюю кухню и взять управление в свои руки.</p><h2>Lua — маленький гигант игровых миров Roblox</h2><figure><img src="https://media.tproger.ru/user-uploads/134768/2025-12-16/46fc4019-a4a3-412d-a7b6-468d1ad2f441.png" alt="Логотип Lua — языка программирования для детей, на кором написана игра Roblox" /></figure><p>Lua (в переводе с португальского — «луна») был создан в 1993 году в Католическом университете Рио-де-Жанейро группой инженеров во главе с Роберто Иерусалимши, Луисом Энрике де Фигейредо и Валдемаром Селесом. Их цель была скромной: создать легкий, встраиваемый язык для настройки ПО в нефтяных компаниях. Ирония судьбы в том, что слава пришла к Lua из абсолютно другой сферы — из индустрии видеоигр.</p><p>Почему Lua подходит для старта в геймдеве:</p><ul><li>Популярность Lua среди детей на пике благодаря Roblox. Зная основы Lua, можно зайти в Roblox Studio и буквально за час запрограммировать дверь, которая открывается, или создать простого NPC, который будет повторять нужные действия. Так мир, в котором ребенок еще недавно только играл, становится полем для экспериментов и местом обученния.</li><li>Синтаксис Lua намеренно сделан простым и последовательным. В нем мало непонятных правил, он учит основам программирования (переменные, функции, циклы) в чистом, не сбивающем с толку виде.</li><li>Принцип «встраиваемости». Изучая Lua для Roblox, дети на практике понимают важнейшую концепцию: часто программист не пишет программу с нуля, а пишет небольшие скрипты — инструкции для поведения объектов внутри готовой, глобальной системы (движка).</li></ul><p>Lua — это стандарт в игровой индустрии для написания игровой логики и скриптов. Его используют в World of Warcraft (для создания аддонов), Civilization, Angry Birds и во многих других играх и приложениях (например, в Adobe Lightroom).</p><p>Где его изучать:</p><ul><li>Онлайн-курс <a href="https://easycode.tech/lua">Lua для детей</a> в школе EasyCode.</li></ul><ul><li><a href="https://keencentre.com/razrabotka-igr-v-roblox/">Программирование на Lua для детей</a> в Keencentre Online.</li></ul><ul><li><a href="https://geekbrains.by/geek-school/roblox/">Программирование и дизайн игр в Roblox</a> для детей 10-12 лет в GeekSchool.</li></ul><h2>C# — для профессиональных игр и Unity</h2><figure><img src="https://media.tproger.ru/user-uploads/134768/2025-12-16/aa151541-1898-4f37-9abd-ef0ff8135d66.jpg" alt="Логотип языка программирования С#, который доступен не только взрослым, но и детям" /></figure><p>C# — язык, созданный Microsoft для конкуренции с Java. C# (произносится как «си шарп») был представлен корпорацией Microsoft в 2000 году под руководством Андерса Хейлсберга, создателя невероятно популярных в конце 20-го века Turbo Pascal и Delphi. Его цель была амбициозной — современный, объектно-ориентированный язык для платформы .NET, который сочетал бы мощь C++ с простотой Java.</p><p>Почему C# подойдет подросткам:</p><ul><li>Это ключ к профессиональному движку Unity. Unity — один из самых популярных в мире движков для создания 2D- и 3D-игр, на котором сделаны тысячи коммерческих проектов (от Cuphead до Ori and the Blind Forest). C# — основной язык сценариев Unity. Изучая C#, подросток фактически вливается в индустрию геймдева.</li><li>Системность и строгость. В отличие от Lua, C# — это строгий, типизированный язык. Его изучение — это уже погружение во «взрослое» программирование с классами, объектами, наследованием. Это дисциплинирует мышление и дает понимание, как строятся большие и сложные программы.</li><li>В Unity на C# можно создать не просто демо-версию, а полноценную игру со сложной графикой, физикой, звуком и AI, которую потом можно запустить на ПК, консолях или мобильных устройствах.</li></ul><p>Владение C# и Unity открывает двери в профессию геймдев-программиста. Но сфера применения C# шире: это также основной язык для разработки корпоративных приложений, сервисов и программ под платформу Microsoft.</p><p>Лучшие онлайн-курсы по C#:</p><ul><li><a href="https://pixel.study/unity?utm_source=tproger.ru&amp;utm_medium=article&amp;utm_campaign=top-5-populyarnykh-yazykov-programmirovaniya-dlya-detey-i-ne-tolko">Создание игр в Unity и программирование на языке C#</a> для детей 10-14 лет в «Пикселе».</li></ul><ul><li><a href="https://itgen.io/csharp">Программирование на C# с 10 лет</a> в «Айтигенио».</li></ul><ul><li><a href="https://kiber-one.com/moduli/programmirovanie-na-c-udivitelnyy-mir-2d-igr/">Программирование на C#. Удивительный мир 2D-игр для детей</a> в «КиберШколе».</li></ul><p>Lua и C# — две ступеньки на пути в игровую разработку. Lua — быстрый и мотивирующий старт, где результат виден мгновенно, а порог входа минимален. C# — это осознанный выбор для более глубокого изучения, переход на профессиональные рельсы и создание сложных проектов.</p><h2>Чек-лист для родителей: как выбрать первый язык программирования</h2><p>Чтобы не гадать, а принять осознанное решение, задайте себе простые вопросы:</p><p>Какой возраст ребенка и готов ли он к абстракции?</p><ul><li>6-10 лет → В приоритете наглядность, игра, мгновенный результат и полное отсутствие страха перед ошибками. Ответ здесь, скорее всего, один — Scratch или аналогичные визуальные среды.</li><li>10-12 лет → Появляется готовность работать с текстом, если он понятный. Желание создавать что-то «настоящее». Идеальный кандидат — Python.</li><li>12+ лет → Дети в этом возрасте уже имеют четкие увлечения (гейминг, дизайн, технологии), и их мотивирует возможностью влиять на любимую цифровую среду. Выбор расширяется до JavaScript, Lua, C#.</li></ul><p>Подумайте о складе ума ребенка и его интересах:</p><ul><li>Обожает игры → Смотрите в сторону Lua (Roblox) или C# (Unity).</li><li>Нравится рисовать, делать видео, творить → Python (для генерации графики, работы с медиа) и JavaScript (для создания интерактивного искусства, веб-дизайна).</li><li>Любит головоломки, логику, интересно, как именно все устроено → Python (для решения задач) и JavaScript (чтобы «разобрать» и собрать поведение сайтов).</li><li>Хочет создать свой сайт/приложение? → JavaScript (фронтенд) и Python (бэкенд).</li></ul><p>Есть ли уже какой-то опыт?</p><ul><li>Нет, это первый шаг → Начинайте с максимально доброжелательных и наглядных Scratch и Python.</li><li>Да, было знакомство со Scratch или блочным программированием → Самый логичный переход — к Python. Дети с опытом в визуальных средах программирования уже понимают логику, а теперь научатся выражать ее в текстовом виде.</li><li>Да, немного пробовал писать код→ Можно смело смотреть в сторону специализации по интересам: JavaScript для веба, C# для игр.</li></ul><p>Итак, мы разобрали, какие языки программирования подходят детям — от визуального Scratch до «взрослого» C# — и как выбрать подходящий. Но главный вывод прост: не существует единственно правильного «детского» языка. Есть правильные первые шаги для каждого конкретного ребенка. Scratch, Python, JavaScript, Lua, C# — каждый из них открывает свою уникальную дверь в огромный мир IT: через творчество, через логику, через любимую игру или через создание полезных приложений.</p><p>И еще: не выбирайте язык за ребенка — позвольте попробовать свои силы в разных языках и выбрать самому. Многие школы программирования предлагают бесплатные пробные уроки — используйте эту возможность.</p>]]></content:encoded>
    </item>
    <item>
      <title>Инженер реализовал завирусившийся XKCD-комикс про зависимости ПО</title>
      <link>https://tproger.ru/news/inzhener-realizoval-zavirusivwijsya-xkcd-komiks-pro-zavisimosti-po</link>
      <comments>https://tproger.ru/news/inzhener-realizoval-zavirusivwijsya-xkcd-komiks-pro-zavisimosti-po?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/inzhener-realizoval-zavirusivwijsya-xkcd-komiks-pro-zavisimosti-po</guid>
      <description><![CDATA[<p>Инженер создал Stacktower — интерактивную версию культового XKCD-комикса, показывающую, как одна зависимость может «обрушить» все приложение</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/inzhener-realizoval-zavirusivwijsya-xkcd-komiks-pro-zavisimosti-po">Инженер реализовал завирусившийся XKCD-комикс про зависимости ПО</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[IDE]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 05 Dec 2025 09:04:53 GMT</pubDate>
      <content:encoded><![CDATA[<p>Веб-инженер <i>Маттиас Хюэль</i> <a href="https://stacktower.io/">создал</a> <b>интерактивную версию культового комикса XKCD №2347</b>, в котором автор высмеивает хрупкость современного ПО.</p><p>Это тот самый рисунок, который в последние месяцы превратился в популярный мем: огромные цифровые системы стоят на одном крошечном модуле, поддерживаемом энтузиастом из Небраски.</p><p>Но теперь эта «шутка» стала наглядной — <b>проект Stacktower превращает рисунок в настоящую визуализацию зависимостей</b>.</p><p>На заглавной схеме, полностью повторяющей комикс, можно нажать на любой блок — и увидеть реальные пакеты, библиотеки и цепочки зависимостей.</p><p>Проект динамически строит дерево зависимостей, позволяя проследить, как небольшие модули опираются на еще меньшее количество фундаментальных компонентов.</p><figure><img src="https://media.tproger.ru/user-uploads/111041/2025-12-05/1b29d25e-fccc-45ef-ade8-a7c35ad092ba.jpeg" alt="" /></figure><h2>Как работает Stacktower</h2><p>Сайт предлагает две модели: <i>«стабильную башню»</i> и <i>«неустойчивую башню»</i>, где уровни подстраиваются под выбранные библиотеки.</p><p>Пользователь может загрузить зависимости своих приложений — например, на <b>Python</b> или <b>JavaScript</b> — и увидеть, что будет, если убрать даже один элемент.</p><p>На странице приводится пример для <b>Node.js</b>:</p><p>приложение зависит от Express → Express зависит от body-parser → body-parser зависит от qs → и так далее</p><p>Stacktower иллюстрирует, что даже простое приложение тянет десятки модулей, часто неподконтрольных разработчику.</p><p>Есть и <b>Python-вариант</b>:</p><p>импорт Flask тянет Werkzeug, Jinja2, MarkupSafe, Click и еще несколько пакетов. А те, в свою очередь, имеют собственные зависимости.</p><h2>Зачем это нужно</h2><p>Автор подчеркивает: <b>цель проекта — не критиковать экосистемы, а показать их реальное устройство</b>.</p><p>Стек современных приложений неизбежно сложен и даже крошечный модуль может стать критически важным. Именно так в 2022 году случайное удаление библиотеки left-pad временно «сломало» огромный кусок JavaScript-мира.</p><p>Stacktower превращает эту абстрактную проблему в понятный визуальный эффект: убираешь одну зависимость — рушится вся конструкция.</p><h2>Комьюнити уже подхватило идею</h2><p>Проект быстро разошелся по Reddit, Hacker News и X: разработчики делятся скриншотами своих «башен», спорят о хрупкости экосистем и обсуждают, стоит ли пересматривать подход к зависимости.</p><p>Кто-то предлагает добавить поддержку Rust и Go, другие мечтают о корпоративной версии инструмента для визуального аудита безопасности.</p><p>Изучить проект подробнее можно на его <a href="https://github.com/matzehuels/stacktower">странице</a> на GitHub.</p>]]></content:encoded>
    </item>
    <item>
      <title>Самое нужное для фронтендера в 2025: честный взгляд изнутри индустрии</title>
      <link>https://tproger.ru/articles/samoe-nuzhnoe-dlya-frontendera-v-2025--chestnyj-vzglyad-iznutri-industrii</link>
      <comments>https://tproger.ru/articles/samoe-nuzhnoe-dlya-frontendera-v-2025--chestnyj-vzglyad-iznutri-industrii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[MCN Telecom]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/samoe-nuzhnoe-dlya-frontendera-v-2025--chestnyj-vzglyad-iznutri-industrii</guid>
      <description><![CDATA[<p>За последние пару лет роль фронтенд-разработчика заметно изменилась. То, что раньше считалось “плюсом”, теперь стало обязательной базой, а сами интерфейсы окончательно превратились в сложные приложения, которые порой работают быстрее десктопных программ.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/samoe-nuzhnoe-dlya-frontendera-v-2025--chestnyj-vzglyad-iznutri-industrii">Самое нужное для фронтендера в 2025: честный взгляд изнутри индустрии</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 03 Dec 2025 10:20:04 GMT</pubDate>
      <content:encoded><![CDATA[<p>За последние пару лет роль фронтенд-разработчика заметно изменилась. То, что раньше считалось “плюсом”, теперь стало обязательной базой, а сами интерфейсы окончательно превратились в сложные приложения, которые порой работают быстрее десктопных программ.</p><p>2025 год особенно интересен: требования растут, стек стремительно обновляется, а вокруг — десятки новых инструментов, которые одновременно и вдохновляют, и заставляют чувствовать себя не в своей тарелке. Так что давайте спокойно и по-человечески разберёмся, что сегодня действительно важно, а что можно смело откладывать.</p><h2>Как изменилась профессия и что теперь требуют от фронтендера</h2><p>Если лет пять назад от вас ждали уверенную верстку, JavaScript и один фреймворк, то сегодня к этому прибавились архитектура, серверные рендеринги, работа с AI-инструментами и понимание, как всё это уживается на проде. Современный фронтенд стал более инженерным: теперь вы не просто “собираете интерфейс”, а участвуете в создании части системы.</p><p>Эта эволюция кажется пугающей только на первый взгляд. На самом деле она отражает главный тренд — интерфейсы стали критически важными для пользователей и бизнеса. А значит, и навыки разработчиков должны подтягиваться до нового уровня.</p><h2>Стек, который в 2025 уже обязателен</h2><p>Начнём с основы — технологий, без которых трудно представить работу фронтендера.</p><p>TypeScript окончательно стал стандартом. Это уже не “приятное дополнение”, а часть культуры. Большие команды без типизации работать не могут — слишком дорого обходятся ошибки и непредсказуемые зависимости.</p><p>JavaScript тоже не стоит на месте: ежегодно появляются полезные нововведения, которые упрощают работу с асинхронностью, структурами данных и синтаксисом. Те, кто следят за ECMAScript, всегда ощущают себя увереннее остальных: код становится чище, а решения — элегантнее.</p><p>Во фронтенде всё больше внимания уделяется браузерным API. WebGPU, новые возможности Storage, нативные анимации — всё это сильно снижает потребность в тяжёлых библиотеках и позволяет делать вещи, которые раньше были попросту невозможны. Чем лучше вы понимаете браузер, тем реже сталкиваетесь с ограничениями.</p><h2>Фреймворки: что происходит, и куда всё движется</h2><p>React по-прежнему доминирует, но теперь уже невозможно говорить о React в отрыве от Next.js или серверных компонентов. Это целая новая парадигма, где часть логики уезжает на сервер, а приложение начинает работать быстрее и экономнее. Для многих разработчиков переход на RSC становится точкой, где React ощущается как совершенно другой инструмент.</p><p>Параллельно растет популярность SvelteKit. Он проще, легче и зачастую приятнее в работе, особенно в небольших командах и стартапах, где важна скорость разработки. Vue 3 остается стабильным и очень комфортным выбором — его любят команды, которые ценят предсказуемость и мягкий порог входа.</p><p>Angular в 2025 году остаётся выбором крупных компаний и проектов, где важны строгая структура и предсказуемость. Благодаря встроенному DI, мощному CLI и единому стилю разработки он помогает быстрее масштабировать команды и поддерживать большие приложения.</p><h2>Архитектура: что фронтендер обязан понимать в 2025</h2><p>Современная разработка уже не обходится без SSR, SSG или ISR. Рендеринг стал частью оптимизации, а значит нужно понимать, почему одни страницы стоит отдавать с сервера, а другие — генерировать заранее.</p><p>Edge-функции — еще одно направление, которое стремительно набирает обороты. Когда код выполняется ближе к пользователю, интерфейс работает быстрее, а логика становится гибче. Если раньше подобные вещи интересовали только backend-разработчиков, то теперь это важный элемент фронтенд-экосистемы.</p><p>Микрофронтенды остаются актуальными для крупных команд. Они не всегда нужны, но если продукт растет, а структура усложняется, этот подход заметно снижает хаос.</p><h2>Как AI меняет работу разработчика</h2><p>AI перестал быть экспериментом — он стал полноценным участником рабочего процесса. Хорошие специалисты уже воспринимают его не как угрозу, а как инструмент.</p><p>Он помогает быстрее проектировать, находить ошибки, генерировать тесты и даже анализировать архитектуру. Но чтобы это работало эффективно, приходится учиться формулировать точные запросы, проверять ответы и комбинировать идеи модели со своим опытом.</p><p>Компании в 2025 году всё чаще спрашивают не “умеете ли вы пользоваться AI”, а “как именно он встроен в ваш рабочий процесс”.</p><h2>Ключевые навыки, без которых сейчас не обойтись</h2><p>Первое — производительность. Пользователи стали очень нетерпеливыми: если интерфейс притормаживает, они просто уходят. Поэтому важно понимать, как распределяется нагрузка, почему лишний ререндер может быть дорогим и что делать, чтобы сайт оставался быстрым даже на слабых устройствах.</p><p>Второе — доступность. A11y перестала быть “бонусом”. Это требование. Интерфейс должен быть удобен всем пользователям, независимо от их ограничений, и компании всё чаще контролируют этот аспект.</p><p>Третье — автоматизация. CI/CD, базовая работа с пайплайнами, понимание, как собирается и проверяется код — всё это экономит время и снижает число ошибок. Чем сильнее разработчик в автоматизации, тем надёжнее продукт.</p><h2>Софт-скиллы, о которых всё чаще говорят</h2><p>Поскольку команды становятся более распределенными, важным навыком стала коммуникация — умение четко формулировать свои мысли, обсуждать решения и объяснять сложные вещи так, чтобы вас понимали.</p><p>Еще одна ключевая компетенция — умение учиться. Обновления приходят настолько быстро, что способность адаптироваться становится не менее важной, чем знание фреймворков.</p><p>И, конечно, продуктовое мышление. Хороший фронтендер давно перестал цениться только за чистый код. Намного важнее, когда вы понимаете, какие решения действительно улучшают продукт, а какие — лишь выглядят технологично.</p><h2>Заключение</h2><p>2025 год стал переломным для фронтенда. Порог входа вырос, но вместе с этим вырос и потенциал — теперь разработчик может влиять на продукт намного сильнее. “Самое нужное для фронтендера в 2025” — это не столько конкретный стек, сколько способность мыслить шире: понимать архитектуру, разбираться в инструментах, не бояться AI и постоянно развиваться.</p><p>Если смотреть на профессию не как на бесконечный список требований, а как на путь, то изменения перестают пугать. Они открывают новые возможности — и именно это делает работу фронтенд-разработчика такой захватывающей.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как убрать ботов с помощью JavaScript, чтобы A/B-тесты были точнее</title>
      <link>https://tproger.ru/articles/kak-otseivat-botov-s-pomoshhyu-javascript--chtoby-a-b-testy-byli-tochnee</link>
      <comments>https://tproger.ru/articles/kak-otseivat-botov-s-pomoshhyu-javascript--chtoby-a-b-testy-byli-tochnee?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Валерия Турчак]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-otseivat-botov-s-pomoshhyu-javascript--chtoby-a-b-testy-byli-tochnee</guid>
      <description><![CDATA[<p>Как отличить людей от ботов в A/B-тестах с помощью JavaScript и сделать результаты статистически честными.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-otseivat-botov-s-pomoshhyu-javascript--chtoby-a-b-testy-byli-tochnee">Как убрать ботов с помощью JavaScript, чтобы A/B-тесты были точнее</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[SEO]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 06 Nov 2025 09:50:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Адаптированный перевод <a href="https://www.kalzumeus.com/2010/06/07/detecting-bots-in-javascrip/">статьи</a> от редакции Tproger. Текст ведётся от первого лица. Автор оригинала Patrick McKenzie — разработчик библиотеки A/Bingo (open-source-инструмент A/B-тестирования на Rails).</p><h2>Зачем вообще убирать ботов</h2><p>Я из тех, кто не любит тратить время на фичи, пока не понятно, что они реально нужны пользователям.<b> С open-source-проектами та же логика: не стоит всё усложнять, пока клиенты прямо не скажут, что им действительно нужно что-то посложнее </b>(бывает, что такие клиенты способны помочь себе сами… по крайней мере, в теории).</p><p>Несколько месяцев назад один из пользователей моего проекта A/Bingo — библиотеки для A/B-тестов на Rails — попросил добавить возможность исключать ботов из подсчёта. Тогда все мои эксперименты были за формой регистрации, так что боты туда почти не попадали.</p><blockquote>Я подумал: раз уж они не настолько умные, чтобы как-то смещать результат, то распределятся равномерно между вариантами. А раз мы измеряем не абсолютную конверсию, а разницу между вариантами, то влияние ботов должно нивелироваться само собой.</blockquote><p>Эту мысль я и озвучил. Реакция была прохладной, и я ответил по классике: код под лицензией MIT, хочешь — форкни и добавь нужную фичу сам. Если некогда — я открыт для консультаций.</p><p>Тема всплывала ещё пару раз, но никто не был настолько мотивирован, чтобы оплатить мою работу. Пока недавно я не стал проводить серию сквозных A/B-тестов, где конверсией считалась покупка — и вот тут боты <b>действительно оказались убийцами статистики</b>.</p><p>Представим, что в среднем у меня около 2000 визитов в день и 5 покупок — для лета это нормальные цифры. Чтобы различить этот уровень конверсии и вариант, на 25 % лучше, потребуется примерно 56 000 визитов, то есть около месяца данных, чтобы достичь 95 %-го доверительного интервала. Отлично. Проблема в том, что A/Bingo фиксирует не 2000 визитов, а ближе к 8000 — потому что сайт постоянно штурмуют боты. В итоге измеренная конверсия падает с 0,25 % до 0,0625 %.</p><p><i>(Если кажется, что цифры низкие — не забывайте: это несезон, плюс я ранжируюсь по множеству длиннохвостых поисковых запросов, так что часть трафика в любом случае нецелевая.)</i></p><h2>Влияет ли это вообще на статистику?</h2><p>Теоретически я всё ещё думал, что раз боты не конвертируются по-разному между вариантами, то математически всё остаётся честным. Вот формула Z-статистики, которой я пользуюсь при проверке гипотез:</p><figure><img src="https://media.tproger.ru/user-uploads/99854/2025-11-06/c62244f0-c5b8-432b-ada4-991492012f82.png" alt="" /></figure><p><i>CR означает коэффициент конверсии (Conversion Rate), а n — размер выборки для двух альтернатив. </i></p><p>Если мы увеличим размеры выборок на некоторый постоянный коэффициент X, уравнение должно превратиться в:</p><figure><img src="https://media.tproger.ru/user-uploads/99854/2025-11-06/88f4b61f-e8fb-44b1-9d65-b2056f1387d5.png" alt="" /></figure><p>Из числителя можно вынести 1/X и перенести его в знаменатель — простая арифметика из начальной школы.</p><figure><img src="https://media.tproger.ru/user-uploads/99854/2025-11-06/4aeb23af-0806-44f6-b0f5-add37855b6f1.png" alt="" /></figure><p>Теперь, благодаря магии алгебры старших классов:</p><figure><img src="https://media.tproger.ru/user-uploads/99854/2025-11-06/9bddcc1d-bf87-4e20-a3f3-fde354268fae.png" alt="" /></figure><p>Если я здесь ошибся с математикой, команда математиков меня точно исключит:</p><figure><img src="https://media.tproger.ru/user-uploads/99854/2025-11-06/020a01e5-1050-430a-9bff-599bbd3abd9f.png" alt="" /></figure><p>Если внимательно посмотреть на это, то это не то же уравнение, с которого мы начали. Как оно изменилось? Обратная величина коэффициента конверсии (1 – cr) стала ближе к 1, чем была раньше. (Это можно проверить, взяв предел при X, стремящемся к бесконечности.) Приближение к 1 означает, что числители знаменателя становятся больше, что означает, что знаменатель в целом становится больше, что означает, что Z-оценка становится меньше, что потенциально может навредить расчёту, который мы делаем.</p><blockquote>Итак, предполагая, что я правильно выполнил алгебру, интуитивный ответ, который я давал людям месяцами, оказался неверным: боты действительно портят тестирование статистической значимости, искусственно занижая z-оценки и тем самым превращая статистически значимые результаты в нулевые результаты на грани.</blockquote><p>Так что же мы можем с этим сделать?</p><h2>Наивный подход: проверять User-Agent</h2><p>Первое, что приходит в голову: просто отфильтровать ботов по User-Agent. Я тоже так думал.
На практике это оказалось катастрофически неэффективно, по крайней мере, для тех ботов, которые атакуют мой сайт.</p><p><i>
(Так как по ключевым словам мой сайт иногда попадает в серую тематику — вроде азартных игр, — я получаю массу лишнего внимания от скрейперов и парсеров.)</i></p><p>Проверка User-Agent убирала максимум половину трафика от ботов. Остальные продолжали участвовать в A/B-тестах, портя конверсии и статистику.</p><h2>Более надёжный способ: заставить бота подумать</h2><p>Можно было бы использовать CAPTCHA — но заставлять всех пользователей доказывать, что они люди, только ради чистоты A/B-тестов, мягко говоря, глупо.</p><p>Нужен полностью автоматический способ отличить человека от скрипта — без участия пользователя.</p><p><b>Решение, к счастью, уже есть: выполнение произвольного JavaScript-кода.</b></p><p>Пока Googlebot и ещё пара продвинутых ботов умеют исполнять JS, для большинства остальных это слишком дорого — и по ресурсам, и по сложности реализации. Написать скрипт на <b>wget</b> или через любую HTTP-библиотеку куда проще, чем имитировать полноценный браузер с движком JS.</p><p><i>(Да, Googlebot действительно умеет исполнять JavaScript. SEO-специалисты технического толка это хорошо знают: бот может частично выполнять код страницы, а иногда и полностью — как человек. Проверить это легко: поставьте в коде экспериментальный запрос, и Googlebot вскоре появится в логах по этому URL.)</i></p><p>Чтобы точно отличать людей от машин (и при этом не мешать Googlebot, который честно указывает свой User-Agent), нужно заставить браузер выполнить три простых задачи:</p><ol><li><b>Сложить два случайных числа. </b>(Для браузера с JS это тривиально.)</li><li><b>Сделать AJAX-запрос через Prototype или jQuery.</b> (Подгрузить и выполнить эти библиотеки без реального движка JS почти невозможно.)</li><li><b>Выполнить POST-запрос. </b>(Googlebot на POST обычно не ходит. Он делает много GET-запросов и даже пытается угадывать параметры, чтобы обойти больше страниц — но POST для него запретная зона.)</li></ol><h3>Пример на Prototype</h3><h3>И в jQuery</h3><p>На сервере мы принимаем параметры<b> a, b </b>и<b> c</b> и проверяем, что они образуют корректную тройку. <b>Если всё сходится — считаем, что это человек. Если нет — продолжаем считать клиента ботом.</b></p><p>Можно было бы усложнить задачу, например, попросить вычислить MD5 от случайного значения, сохранённого в сессии, чтобы отсеивать ботов, которые пытаются просто повторить старый ответ. Но мне это не нужно: цель не в защите от злонамеренных атак, а в фильтрации большинства «безобидных» ботов, которые лишь засоряют статистику.</p><blockquote>Никто не получает выгоды, ломая мои A/B-тесты, так что я не жду целенаправленных атак. Это не защита — просто средство наведения порядка.</blockquote><p>Да, я исхожу из того, что пользователи умеют выполнять JavaScript. (Мой сайт без JS всё равно не работает.)</p><blockquote>Так что если кто-то вроде Ричарда Столлмана с включённым NoScript не сможет участвовать в моих тестах — что ж, я переживу.</blockquote><h2>Как всё связать вместе</h2><p>Теперь мы умеем отличать тех, кто действительно исполняет JavaScript, от тех, кто нет.</p><p>Осталась одна мелочь: браузер сообщает о своей человечности <b>уже после того, как пользователь начал участвовать в A/B-тесте.</b></p><p>Это абсолютно нормальная ситуация. Например, человек заходит на первую страницу, где где-то внутри встроен тест. Он выполняет JS-код, который посылает на сервер доказательство, что он человек. Но делает это уже после того, как A/Bingo зарегистрировал его участие в тесте.</p><p>Решение оказалось простым.</p><p>A/Bingo и так отслеживает, в каких экспериментах пользователь уже участвовал, чтобы не считать его повторно. В «антибот-режиме» это поведение чуть меняется:</p><ul><li>система продолжает записывать участие и конверсии,</li><li><b>но не добавляет их в общую статистику, пока пользователь не подтвердил, что он человек.</b></li></ul><p>Когда подтверждение приходит (браузер выполнил JS и прошёл проверку), система просматривает все предыдущие тесты, где пользователь уже участвовал, и <b>задним числом добавляет его участие в подсчёты.</b></p><p>Если кому-то интересно посмотреть, как именно это реализовано, код доступен для изучения — всё лежит на <a rel="noopener" href="http://www.bingocardcreator.com/abingo">официальной странице проекта</a>.</p><p>А если не хочется разбираться в деталях, а просто нужно быстро сделать свои A/B-тесты ботоустойчивыми, — посмотрите последнюю запись в <a rel="noopener" href="http://www.bingocardcreator.com/abingo/faq">FAQ</a>. Там объясняется, как включить этот режим в один клик.</p><h2>Другие применения</h2><p>Этот приём с проверкой через JavaScript можно использовать не только в A/B-тестах.</p><p>У него есть и другие, вполне практичные применения.</p><h3>1. Невидимая CAPTCHA для комментариев</h3><p>С небольшой доработкой это превращается в CAPTCHA без взаимодействия с пользователем — отличное решение для блогов, форумов и любых форм обратной связи.</p><p>Принцип такой:</p><ul><li>все пользователи (и люди, и боты) сразу видят свой комментарий после отправки,</li><li>но сайт публикует его только после того, как получит подтверждение JavaScript-выполнения.</li></ul><p>Никаких галочек <b>Я НЕ РОБОТ</b> — просто задержка публикации, пока браузер не выполнит нужный код.</p><p>Можно сделать проверку чуть сложнее и добавить состояние на сервере, чтобы она была уникальной для каждого запроса.</p><blockquote>Минус очевиден: ваш сайт, скорее всего, навсегда останется без комментариев от Ричарда Столлмана и других любителей NoScript.</blockquote><p>Но, согласитесь, это не самая высокая цена за чистоту данных.</p><h3>2. Автоматическое дискриминирование пользователей под нагрузкой</h3><p>Ещё одна идея — всегда различать, кто человек, а кто нет. Когда сервер оказывается на грани по ресурсам, можно временно отключать тяжёлые функции для тех, кто ещё не доказал свою человечность.</p><p>Так вы избавитесь от резких просадок производительности из-за всплесков ботов и сможете спокойно выдерживать пики нагрузки.</p><p>А если ситуация критическая — можно вообще заблокировать ботов на время.
Обычные пользователи этого даже не заметят.</p><h3>3. Защита от разрушительных действий</h3><p>И наконец, проверку можно использовать, чтобы предотвратить опасные или разрушительные операции.</p><p>Хотя, по-хорошему, такие действия и так должны быть защищены — спрятаны за POST-запросом и аутентификацией. Но дополнительная проверка на человечность нужна, особенно если ошибка может что-то удалить или повредить данные.</p><h3>От редакции Tproger</h3><p>Если вам понравился этот перевод — обсудите материал в комментариях.</p><p>Мы стараемся находить и адаптировать для вас лучшие тексты о разработке, данных и инженерном мышлении. Поделитесь своими мыслями: сталкивались ли вы с ботами, которые ломают аналитику, и как вы с этим справлялись?</p>]]></content:encoded>
    </item>
    <item>
      <title>Безопасные методы работы с массивами в JavaScript</title>
      <link>https://tproger.ru/articles/bezopasnye-metody-raboty-s-massivami-v-javascript</link>
      <comments>https://tproger.ru/articles/bezopasnye-metody-raboty-s-massivami-v-javascript?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Даровская Маша]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/bezopasnye-metody-raboty-s-massivami-v-javascript</guid>
      <description><![CDATA[<p>Безопасные методы работы с массивами в JavaScript: toSorted(), toReversed() и toSpliced() вместо мутирующих sort(), reverse() и splice(). Примеры использования в React, сравнение методов и поддержка браузерами. Как писать чистый код без побочных эффектов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/bezopasnye-metody-raboty-s-massivami-v-javascript">Безопасные методы работы с массивами в JavaScript</a>»</p>]]></description>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 24 Oct 2025 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Это <a href="https://allthingssmitty.com/2025/09/08/finally-safe-array-methods-in-javascript/">перевод статьи</a> Мэта Смитта (<a href="https://allthingssmitty.com/">Matt Smith</a>) — веб-разработчика, фронтенд-инженера и UX-дизайнера.</p><p>Есть веская причина, по которой многие разработчики задумываются, прежде чем использовать в JavaScript методы .sort(), .reverse() или .splice(): эти методы изменяют исходный массив. Такой побочный эффект может привести к скрытым, трудноуловимым ошибкам — особенно в приложениях с общим или реактивным состоянием.</p><p>Хорошая новость в том, что за последние пару лет в JavaScript появились новые методы работы с массивами, которые делают этот процесс более безопасным и чистым, полностью избегая мутаций:</p><ul><li>toSorted()</li><li>toReversed()</li><li>toSpliced()</li></ul><p>Эти методы возвращают копии массивов, вместо того чтобы изменять оригинал. Это небольшое, но важное обновление синтаксиса — особенно для разработчиков на React, где неизменяемость данных (immutability) — ключ к правильному управлению состоянием.</p><h2>Проблема методов, изменяющих массив «на месте»</h2><p>В JavaScript традиционные методы, такие как .sort(), .reverse() и .splice(), модифицируют сам массив, на котором были вызваны:</p><p>В таких фреймворках, как React, это может привести к непредсказуемому поведению при обновлении состояния, поскольку прямое изменение массивов не вызывает повторный рендер.</p><h2>Сравнение старых и новых методов</h2><ul><li>Для сортировки вместо arr.sort(), который изменяет исходный массив, теперь можно использовать arr.toSorted(), возвращающий новый.</li><li>Для обратного порядка вместо arr.reverse() следует применять arr.toReversed(), чтобы сохранить оригинал.</li><li>Для удаления или вставки элементов вместо arr.splice() можно использовать arr.toSpliced(), который создаёт копию массива с изменениями, не трогая исходный.</li></ul><p>Эти новые методы работают так же, как и их «изменяющие» аналоги, но вместо модификации исходного массива возвращают новый.</p><p>⚠️ Важно: это <a href="https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy">поверхностные копии</a>, поэтому если массив содержит объекты, сами объекты останутся теми же ссылками, а не новыми экземплярами.</p><h2>Решение: безопасные, не изменяющие оригинал методы</h2><p>В стандарте ES2023 появились новые версии привычных методов массивов, которые не изменяют исходный массив:</p><p>toSorted() — создаёт отсортированную копию массива, не затрагивая оригинал.</p><p>Вы также можете передать собственную функцию сравнения — точно так же, как в методе .sort():</p><p>toReversed() — возвращает обратную копию массива, не изменяя исходный порядок элементов в оригинале.</p><p>Отлично подходит для случаев, когда нужно вывести список в обратном порядке, не изменяя исходный массив.</p><p>toSpliced() — это более безопасная альтернатива методу .splice(). Она возвращает новый массив с добавленными или удалёнными элементами, не затрагивая оригинал.</p><p>Напоминание: метод .splice() возвращает удалённые элементы, а .toSpliced() — обновлённый массив.</p><h2>Почему это важно в React</h2><p>В React неизменяемость данных — ключевой принцип, который обеспечивает обновление компонентов и предсказуемость состояния.</p><p>Новые методы позволяют работать с массивами как с неизменяемыми структурами данных, не прибегая к использованию structuredClone() или сложных обходных решений для глубокой копии.</p><h2>Практический пример: сортировка задач в React</h2><p>Вот как можно использовать toSorted() или toReversed() в компоненте, чтобы безопасно отображать динамические списки, не изменяя исходные данные:</p><p>Такой подход избегает изменения массива tasks, что особенно важно, если он передан через props или вычисляется из state — в противном случае могут возникнуть ошибки. Кроме того, использование операторов опциональной цепочки (?.) и объединения с null (??) помогает предотвратить сбои, если tasks окажется undefined.</p><p>И это ещё не всё! Оба этих оператора — опциональная цепочка и nullish coalescing — улучшения синтаксиса, делающие код более надёжным и устойчивым к ошибкам во время выполнения, приближая его к современному стандарту JavaScript.</p><h2>Маленькое изменение в синтаксисе — большое улучшение</h2><p>Эти методы не требуют нового подхода к мышлению — это просто безопасные, неизменяемые версии уже привычных вам инструментов. Если вы работаете в современной среде (или используете сборщики вроде Babel или SWC), вы можете начать применять их уже сегодня.</p><h2>Поддержка браузерами</h2><p>Методы toSorted(), toReversed() и toSpliced() поддерживаются во всех современных средах:</p><p>✅ Chrome / Edge — с версии 110+</p><p>✅ Safari — с версии 16+</p><p>✅ Firefox — с версии 115+</p><p>✅ Node.js — с версии 20+</p><p>Для старых окружений можно использовать полифил, например, из библиотеки <a href="https://www.npmjs.com/package/core-js">core-js</a>.</p><h2>Основные выводы</h2><p>Метод .toSorted() возвращает отсортированную копию массива, не изменяя оригинал. Метод .toReversed() создаёт новый массив в обратном порядке, также без мутаций исходного. Метод .toSpliced() возвращает изменённую копию массива — с добавленными или удалёнными элементами, но оригинальный массив остаётся нетронутым.</p><p>ES2023 принёс не только заметные обновления вроде опциональной цепочки и<a href="https://allthingssmitty.com/2025/06/16/using-await-at-the-top-level-in-es-modules/"> top-level await,</a> но и такие, казалось бы, мелкие улучшения, которые делают код чище, понятнее и безопаснее. Попробуйте использовать новые методы в своих проектах — и вы больше не будете смотреть на .sort() с тем же доверием.</p>]]></content:encoded>
    </item>
    <item>
      <title>Глубокое погружение в работу импортов JavaScript</title>
      <link>https://tproger.ru/articles/glubokoe-pogruzhenie-v-rabotu-importov-javascript</link>
      <comments>https://tproger.ru/articles/glubokoe-pogruzhenie-v-rabotu-importov-javascript?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дима Державин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/glubokoe-pogruzhenie-v-rabotu-importov-javascript</guid>
      <description><![CDATA[<p>Рассмотрим различные типы импортов, способы их организации, частые проблемы (например, циклические зависимости) и инструменты для их решения. Дадим практические рекомендации по использованию алиасов, сортировке импортов и автоматизации процессов для улучшения читаемости и эффективности кода.
</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/glubokoe-pogruzhenie-v-rabotu-importov-javascript">Глубокое погружение в работу импортов JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 23 Oct 2025 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Неорганизованное использование импортов часто приводит к проблемам с циклическими зависимостями и увеличению размера приложения.</p><p>Этого можно избежать, если подойти к организации импортов системно.</p><p>В статье мы разложим по полочкам:</p><ul><li>Основные типы импортов и их влияние на сборку;</li><li>Известные способы импортировать модули;</li><li>Подходы по организации импортов;</li><li>Частые проблемы с импортами и инструменты их решения.</li></ul><p><i>Статья будет полезна как новичкам, желающим разобраться в основах, так и сеньорам, которые хотят систематизировать свои знания и узнать о тонкостях работы импортов.</i></p><h2>Как импорты влияют на производительность вашего приложения?</h2><p>Понимание работы импортов способствует созданию лёгкого и производительного web-приложения, в то время как неправильное использование — приводит к попаданию в сборку неиспользуемого кода, что напрямую влияет на время загрузки приложения.</p><h2>В чем разница CommonJS и ES Modules?</h2><h3>CommonJS</h3><p>CommonJS (далее CJS) — система импортов, которая долгое время была стандартом в Node.js.</p><h4>Синтаксис</h4><h4>Особенности</h4><p>Загрузка модулей в CJS происходит синхронно. Это хорошо подходит для сервера, где файловая система работает быстро, но в браузере такой подход является узким местом, особенно без использования сборщиков.</p><p>По той же причине CJS не подходит для SSR, поскольку блокирует рендеринг и замедляет отдачу конечной страницы.</p><p>Интересно: Хотя современные React-приложения используют ES-модули, многие библиотеки из экосистемы NPM до сих пор пишутся или транспилируются в CommonJS. Понимание различий между этими системами может вам понадобиться для отладки получения более эффективного tree-shaking</p><h4>Гибкость</h4><p>require() — это обычная функция, которая может быть вызвана в любом месте кода, что само по себе довольно удобно:</p><p>CJS-модуль выполняется один раз — при первом require(). Затем результат выполнения кешируется, и все последующие вызовы будут возвращать тот же объект.</p><h2>ES Modules</h2><p>ES Modules (дале ESM) — современная система модулей в JavaScript, которая пришла на замену CJS.</p><p>Node.js поддерживает ESM начиная с версии 12.</p><p>Важно: Расширение .mjs является рекомендованным расширением для NodeJS, которое явно указывает разработчику, что файл содержит ESM-модуль.</p><p>Интересно: ESM это официальная спецификация ECMAScript, в отличие от CJS, который по факту был решением сообщества NodeJS.</p><h3>Синтаксис</h3><h4>Особенности</h4><ul><li>ESM грузится асинхронно;</li><li>Импорты должны находиться в начале ESM — такое статическое расположение позволяет сборщикам проводить более эффективный tree-shaking;</li><li>ESM исполняется в строгом режиме и имеет собственную область видимости;</li><li>ESM поддерживает динамический импорт, который возвращает модуль в виде промиса.</li></ul><p>Интересно: Библиотеки часто публикуют dual-пакеты, то есть когда библиотека собирается и в CommonJS (.cjs), и в ES-модули (.mjs), чтобы быть более универсальными. Это может пригодится когда вы захотите оптимизировать сборку своего приложения.</p><p>Также ESM предоставляет объект import.meta для получения информации о модуле. В нём доступны свойства url dirname filename, которые содержат URL, директорию и имя файла модуля соответственно.</p><p>ESM можно подключать нативно, прямо в браузере:</p><h3>Import Maps</h3><p>Import Maps позволяют контролировать разрешение импортов в браузере, аналогично тому как это делают сборщики:</p><p>Import Maps обычно использую для:</p><ul><li>Алиасов;</li><li>Версионирования;</li><li>Когда выбор библиотеки зависит от окружения.</li></ul><blockquote>Также, Import maps очень полезен для быстрых прототипов на сервисах где можно пошарить какой-то компонент, чтобы не тащить сборку, например на Codepen.</blockquote><h2>Как можно импортировать модули?</h2><p>В JavaScript существует множество видов импорта, каждый из которых служит своей цели и имеет конкретные особенности:</p><h3>Именованный</h3><p>Особенности:</p><ul><li>Точное указание импортируемых сущностей;</li><li>Хорошо совместим с tree-shaking;</li><li><a href="https://github.com/ArnaudBarre/eslint-plugin-react-refresh">Необходим</a> для hot-reloading в dev-сервере.</li></ul><p>Применение:</p><ul><li>Компоненты. утилиты, константы.</li></ul><p>Важно: tree-shaking особенно хорошо работает при подходе один модуль = один файл.</p><h3>Дефолтный</h3><p>Особенности:</p><ul><li>Один основной экспорт на модуль;</li><li>Возможность переименования при импорте;</li><li>Плохо совместим с tree-shaking.</li></ul><p>Возможные проблемы:</p><ul><li>Содержимое импорта может быть непонятным, до момента пока не заглянешь во внутрь;</li><li>Один и тот же модуль может импортироваться под разными именами в разных файлах проекта, создавая путаницу;</li><li>Слабая поддержка IDE.</li></ul><p>Применение:</p><ul><li>Верхнеуровневые сущности;</li><li>Ленивая загрузка модулей через React.lazy.</li></ul><h3>Комбинированный</h3><p>Особенности:</p><ul><li>Совмещает импорт по умолчанию и именованный;</li><li>Важен порядок объявления.</li></ul><p>Применение:</p><ul><li>Модули с основным и вспомогательными экспортами.</li></ul><h3>Namespace-импорты</h3><p>Применение:</p><ul><li>Удобен для конфигов, API, утилитарных функций.</li></ul><p>Возможные проблемы:</p><ul><li>Легко поломать tree-shaking внутри модуля.</li></ul><blockquote>Здесь не стоит сильно пугаться что подход может поломать tree-shaking, чаще всего неиспользуемые функции, на которые нет ссылок вырезаются минификатором кода, так что если что-то в этом случае осталось в бандле, дело скорее всего в том, что функцию где-то есть ссылка.</blockquote><h3>Type-импорты</h3><p>Особенности:</p><ul><li>Используются только для типов;</li><li>Не попадают в runtime-код;</li><li>Помогают избежать циклических зависимостей типов.</li></ul><p>Применение:</p><ul><li>Импорт типов в TypeScript.</li></ul><blockquote>В TypeScript 5.0 появилась новая опция --verbatimModuleSyntax, призванная упростить ситуацию. Правила стали проще: все импорты и экспорты без type-модификаторов сохраняются. Всё, что использует type-модификатор, полностью удаляется.</blockquote><h3>Barrel-импорты (бочка импортов)</h3><p>Особенности:</p><ul><li>Упрощение путей импорта за счёт централизации экспортов.</li></ul><p>Возможные проблемы:</p><ul><li>Возможно снижение производительности при большом количестве реэкспортов.</li></ul><p>Применение:</p><ul><li>Организация публичного API для папок и пакетов.</li></ul><h3>Относительные импорты</h3><p>Особенности:</p><ul><li>Зависят от расположения файла на сервере или в файловой системе.</li></ul><p>Применение:</p><ul><li>Локальные модули в пределах проекта.</li></ul><h3>Абсолютные импорты</h3><p>Особенности:</p><ul><li>Независимость от структуры папок;</li><li>Требуется настройка в сборщике или компиляторе.</li></ul><p>Применение:</p><ul><li>Глобальные пути в проекте.</li></ul><h2>Как можно удобно организовать работу с импортами?</h2><h3>Алиасы</h3><p>Алиасы это короткие и удобные сокращения для импорта модулей вместо длинных относительных путей. Алиасы делают код чище и упрощают рефакторинг.</p><p>Интересно: Автору много раз приходилось рефакторить проект с относительным путями, в ходе которого, во время переноса файлов, IDE не всегда мог корректно рассчитать новый относительный путь. Это приводило к тому что множество битых путей приходилось прописывать вручную.</p><p>Любой современный сборщик умеет работать с алиасами:</p><ul><li><a href="https://webpack.js.org/configuration/resolve/#resolvealias">webpack</a>;</li><li><a href="https://vite.dev/config/shared-options.html#resolve-alias">vite</a>;</li><li><a href="https://github.com/rollup/plugins/tree/master/packages/alias#entries">rollup</a>.</li></ul><p>В нейминге алиасов можно встретить разные подходы:</p><ul><li>src/components — простой и понятный, но может быть избыточным при неглубокой вложенности;</li><li>@/components — популярный вариант, но может конфликтовать с названиями npm-пакетов;</li><li>$/components — наименее популярные вариант, но имеет наименьшие шансы на конфликты;</li><li>~/components — оптимальный вариант, широко используется, интуитивно понятен пользователям unix-систем;</li><li>#/components — современный подход, настраивается в package.json и поддерживается в TypeScript 4.9+.</li></ul><blockquote>Алиасы это очень мощный инструмент для того, чтобы быстро скопировать код компонентов в проекте, располагая их на разной вложенности, не нужно думать где лежит импортируемый файл, если он часто и много используется.</blockquote><blockquote>Настройка алиасов через сборщик уже сегодня можно считать анти-паттерном, так как NodeJS позволяет настроить этот функционал через package.json</blockquote><h3>Паттерн разработки «реэкспорт через директорию»</h3><p>Суть паттерн в создании единой точки входа для модуля через index.ts, который реэкспортирует нужные методы из вложенных директорий.</p><p>Преимущества:</p><ul><li>Короткие и чистые импорты;</li><li>Группировка логически связанных модулей;</li><li>Сокрытие внутренней структуры директорий.</li></ul><p>Файл index.ts можно представить публичным API модуля, который отделяет его потребителей от внутренней реализации, что соответствует принципу разделения интерфейса (ISP) из SOLID.</p><blockquote>Обычно, такой подход считается “антипатерном” в корпоративной разработке, так как чаще всего разработчики используют функцию автоимпорта в IDE, что может порождать разные пути импорта одного и того же файла. Лучше всего использовать его как некий фасад для публичного апи библиотеки, когда важна простота и лаконичность.</blockquote><h3>Сортировка импортов</h3><p>Сортировка импортов улучшает читаемость кода: нужные зависимости находятся быстрее, а структура модуля становится более очевидной и предсказуемой.</p><p>Базовый порядок импортов:</p><h3>Автоматизация</h3><p>Сортировку импортов можно настроить как с помощью ESLint:</p><p>…так и при помощи Prettier:</p><h2>Какие проблемы можно встретить при неправильном использовании импортов?</h2><h2>Циклические зависимости</h2><p>Проблема возникает когда два модуля начинают импортировать друг друга напрямую.</p><p>Чаще всего циклические зависимости встречаются:</p><ul><li>В роутинге и навигации;</li><li>В State-менеджменте.</li></ul><blockquote>Основной источник циклических зависимостей это отсутствие композиции в компонентах, что в свою очередь способствует бесконечной вложенности среди внутренних сущностей.</blockquote><p>Интересно: Автор чаще всего встречался с циклическими зависимостями при организации сложного стейт-менеджмента в Effector и RxJS.</p><p>Иногда циклическая зависимость возникает, когда модуль из длинной цепочки импортов начинает экспортировать другой модуль из той же цепочки:</p><p>Когда Module D начнёт импортировать Module A вы получите циклическую зависимость.</p><blockquote>Ну и самое главное, почему эту проблему невозможно обойти: в случае если на каком-то из шагов потребуется инициализация переменной, которая участвует в этом цикле, она вернется как undefined. Особенно если создалось “состояние гонки”, когда модуль одновременно участвует в нескольких импортах, один из которых циклический. Получается классическая проблема иголки в столе сена, а значит отлавливать и следить за ними надо как можно раньше, пока внезапно вылезший undefined не увеличил сроки разработки.</blockquote><h3>Как предотвратить?</h3><ul><li>Использовать архитектурные паттерны и методологии которые занимаются управлением потока данных: MVC, FLUX, FSD, Domain Driven Design;</li><li>Строго соблюдать направление зависимостей: нижние уровни не должны зависеть от верхних;</li><li>Стремится к минимальной вложенности модулей;</li><li>Подключить автоматическое отслеживание циклических зависимостей через линтеры.</li></ul><h3>Поломка tree-shaking и code-splitting</h3><p>Чтобы tree-shaking работал корректно с библиотеками, их нужно собирать, а затем подключать правильным образом. Например, именованный импорт из lodash затянет в сборку всю библиотеку:</p><p>В то время как точный импорт добавит в сборку только используемый метод:</p><h3>Неиспользуемые импорты</h3><p>Обычно это подключенные модули или зависимости, которые фактически не применяются в коде. Такого рода мёртвый код создает лишнюю нагрузку на проект, следовательно это нужно держать на контроле.</p><p>Это процесс можно автоматизировать при помощи ESLint:</p><p>…и TypeScript компилятора:</p><h2>Как проанализировать конечную сборку приложения?</h2><p>Для анализа итоговой сборки приложения существуют специальные инструменты визуализации бандла:</p><ul><li><a href="https://www.npmjs.com/package/webpack-bundle-analyzer">webpack-bundle-analyzer</a>;</li><li><a href="https://www.npmjs.com/package/vite-bundle-analyzer">vite-bundle-analyzer</a>;</li><li><a href="https://sonda.dev/">sonda.dev</a>.</li></ul><h2>Лучшие практики</h2><ul><li>Используйте абсолютные пути глобально, относительные — локально;</li><li>Придерживайтесь единого стиля именования алиасов;</li><li>Избегайте barrel-файлов для тяжёлых компонентов;</li><li>Отдавайте предпочтение именованным импортам;</li><li>Применяйте динамические импорты для редко используемых модулей;</li><li>Проверяйте поддержку tree-shaking в сторонних библиотеках;</li><li>Ограничивайте глубину относительных путей;</li><li>Группируйте импорты по типам;</li><li>Автоматизируйте соблюдение практик с помощью линтеров.</li></ul><h2>Заключение</h2><p>Организация импортов в JavaScript-приложениях — далеко не второстепенная задача, а важная часть приложения способная повлиять на скорость сборки, производительность и читаемость кода.</p><blockquote>Я бы еще добавил, что для теоретической базы архитектуры импортов стоит почитать про 2 принципа GRASP: Low cohesion и High Coupling. Cистема должна состоять из слабо связанных сущностей, которые должны содержать близкую бизнес логику. Соблюдение этих принципов позволяет удобно переиспользовать созданные сущности, не теряя понимания об их зоне ответственности.</blockquote><p>Если вам понравилась статья — подписывайтесь на мой <a href="https://t.me/+erU48ZurxxFiZjQy" rel="nofollow">телеграм</a>. Там я рассказываю о жизни разработчика, делюсь практическим опытом и инсайтами, которые помогут вам расти как специалисту ✨</p><p>Отдельное спасибо экспертам за их комментарии: Александру Коротаеву и Роману Титову, обязательно подпишитесь на их телеграмм-каналы <a href="https://t.me/korotaev_to_hard">Трудно быть Коротаевым</a> и <a href="https://t.me/parrotontheweb">🦜 on the web</a>.</p><p>Остались вопросы по статье? Давайте обсудим в комментариях 👇</p><h2>Рекомендуем</h2><p><a href="https://tproger.ru/articles/kak-uprostit-import-javascript-modulej-s-pomoshhyu-node-js-subpath-imports">Как упростить импорт JavaScript-модулей с помощью Node.js Subpath Imports</a></p><p><a href="https://tproger.ru/articles/podgotovka-okruzhenija-react-prilozhenija-vscode-prettier-eslint-stylelint-husky">Подготовка окружения React-приложения: VSCode, Prettier, ESLint, Stylelint, Husky</a></p><p><a href="https://tproger.ru/articles/reactjs-na-izi--chto-realno-nuzhno-znat-frontend-razrabotchiku-v-2025-godu">ReactJS на изи: что реально нужно знать фронтенд-разработчику в 2025 году</a></p><p><a href="https://tproger.ru/translations/how-to-boost-frontend">Ускоряем загрузку своего сайта</a></p>]]></content:encoded>
    </item>
    <item>
      <title>JavaScript: как логические операторы присваивания упрощают код и повышают читаемость</title>
      <link>https://tproger.ru/articles/javascript--kak-logicheskie-operatory-prisvaivaniya-uproshhayut-kod-i-povywayut-chitaemost</link>
      <comments>https://tproger.ru/articles/javascript--kak-logicheskie-operatory-prisvaivaniya-uproshhayut-kod-i-povywayut-chitaemost?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Даровская Маша]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/javascript--kak-logicheskie-operatory-prisvaivaniya-uproshhayut-kod-i-povywayut-chitaemost</guid>
      <description><![CDATA[<p>null</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/javascript--kak-logicheskie-operatory-prisvaivaniya-uproshhayut-kod-i-povywayut-chitaemost">JavaScript: как логические операторы присваивания упрощают код и повышают читаемость</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 22 Oct 2025 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Это<a href="https://allthingssmitty.com/2025/07/28/logical-assignment-operators-in-javascript-small-syntax-big-wins/"> перевод статьи Мэта Смитта</a> (<a href="https://allthingssmitty.com/">Matt Smith</a>) — веб-разработчика, фронтенд-инженера и UX-дизайнера.</p><p>В повседневной работе с JavaScript нам часто приходится проверять значение переменной перед тем, как присвоить ей новое. Подобные проверки быстро превращаются в шаблон, особенно если вы работаете с props в компонентах, глобальными настройками или объектами состояния.</p><p>Именно здесь на помощь приходят логические операторы присваивания — компактное нововведение стандарта ES2021, которое позволяет сократить типичные условные конструкции, не меняя при этом саму логику кода.</p><h2>Что такое логические операторы присваивания?</h2><p>Логические операторы присваивания объединяют обычный логический оператор (||, &amp;&amp; или ??) с оператором присваивания (=), создавая короткую и наглядную запись.</p><p>По сути, это синтаксический сахар, позволяющий писать условное присваивание в одну строку. При этом они работают по тому же принципу, что и обычные логические выражения: правая часть вычисляется только в том случае, если левая не проходит логическую проверку — то есть оказывается falsy, truthy или nullish (в зависимости от конкретного оператора).</p><p><b>Примечание: </b>Оператор опциональной последовательности (?.) нельзя использовать в левой части логического присваивания.</p><p>Попытка сделать это приведёт к синтаксической ошибке:</p><p>Если нужно безопасно присвоить значение, используйте обычную проверку или промежуточную переменную:</p><p>Оператор опциональной последовательности (?.) возвращает значение свойства, а не ссылку на само свойство.</p><p>А поскольку в JavaScript присваивание возможно только в том случае, если левая часть выражения является ссылкой (например, переменной или свойством объекта), использование ?. в левой части делает выражение некорректным.</p><h2>Логическое присваивание через OR (||=)</h2><p>Оператор ||= выполняет присваивание только тогда, когда значение слева является ложным (falsy) — то есть false, 0, “”, null, undefined или NaN.</p><p>Присвоить, если значение — falsy.</p><p>Это эквивалент записи:</p><p>Этот оператор удобен, чтобы задать значение по умолчанию, если переменная ещё не инициализирована. Однако он перезаписывает такие значения, как 0, ” или false, даже если они были установлены специально.</p><p>Другой способ задать значения по умолчанию — использовать параметры функций с дефолтными значениями. Подробнее об этом можно прочитать в <a href="https://allthingssmitty.com/2025/06/29/default-parameters-your-code-just-got-smarter/">статье о параметрах по умолчанию</a> в JavaScript.</p><h2>Логическое присваивание через AND (&amp;&amp;=)</h2><p>Присвоить, если значение — truthy:</p><p>Этот оператор полезен, когда нужно обновить значение только при наличии уже существующего truthy значения.</p><p><b>Примечание: </b>при использовании &amp;&amp;= правая часть выражения вычисляется только если левая часть является truthy, и результат этого вычисления присваивается, даже если он окажется falsy.</p><p>Исходное значение (true) играет роль «ворот»: именно оно определяет, выполняется ли присваивание.</p><p>Однако новое значение берётся из результата вызова checkPermissions (или любого выражения справа).</p><p>Важно: оператор &amp;&amp;= не сохраняет старое значение, а заменяет его результатом вычисления правой части.</p><h2>Присваивание через nullish-оператор (??=)</h2><p>Присвоить, если значение — null или undefined:</p><p>Эквивалентно:</p><p>Используйте ??=, когда нужно задать значение только если переменная действительно отсутствует — то есть равна null или undefined, а не просто falsy. В отличие от ||=, этот оператор сохраняет корректные значения, такие как 0, false и ”.</p><p>Подробнее: если хотите глубже разобраться в механике nullish-оператора и принципах работы с дефолтными значениями — посмотрите <a href="https://allthingssmitty.com/2025/04/10/mastering-default-values-in-javascript-with-the-nullish-coalescing-operator/">материал о том, как грамотно использовать значения по умолчанию в JavaScript</a>.</p><h2>Значения по умолчанию для пропсов компонентов</h2><p>Логические операторы присваивания особенно удобны при работе с пропсами в компонентах.</p><p>Например, если часть значений не передана, можно задать дефолты прямо в коде:</p><p>Такой подход:</p><ul><li>делает код компактнее и читаемее,</li><li>избавляет от лишних if или тернарных выражений,</li><li>позволяет контролировать поведение пропсов при разных типах значений (null, undefined, false, ”).</li></ul><p>Идеально подходит для случаев, когда вы хотите задать понятные значения по умолчанию, не ломая текущую логику компонента.</p><h2>Важно учитывать</h2><ul><li>Оператор ||= срабатывает, когда левая часть — falsy-значение (0, ”, false, null, undefined). Это может привести к нежелательному перезаписыванию:</li></ul><ul><li>Используйте ??= если нужно проверять только на null или undefined, сохраняя корректные falsy-значения (0, ”, false).</li><li>Правая часть вычисляется только при необходимости, что сохраняет производительность и предотвращает побочные эффекты:</li></ul><h2>Пример с побочным эффектом</h2><p>В этом примере:</p><ul><li>Изначально obj.val равно 0, что является falsy. Поэтому выражение ++calls вычисляется и присваивается obj.val.</li><li>После первой строки obj.val становится 1 (truthy).</li><li>На второй строке условие уже не выполняется, и ++calls не вызывается.</li></ul><p>В результате значение calls остаётся равным 1, потому что вторая операция не выполняет инкремент.</p><h2>Поддержка браузерами</h2><p>Логические операторы присваивания поддерживаются всеми современными окружениями:</p><p>✅ Chrome 85+, Firefox 79+, Safari 14+, Edge 85+</p><p>✅ Node.js 15+</p><p>❌ Internet Explorer — не поддерживается</p><p>Если нужно обеспечить работу в старых окружениях, используйте транспайлер, например, @babel/preset-env с настройками ES2021.</p><h2>Теперь вы готовы!</h2><p>Логические операторы присваивания — небольшое, но очень полезное дополнение к JavaScript. Они делают код понятнее, уменьшают шаблонные конструкции и упрощают условную логику.</p><p>Особенно полезны они во фронтенд-разработке, например:</p><ul><li>при работе с props и state;</li><li>при задании значений по умолчанию для API;</li><li>при очистке и валидации форм.</li></ul><p>Если вы уже уверенно используете ||, &amp;&amp; и ??, переход на ||=, &amp;&amp;= и ??= будет естественным — здесь всё дело лишь в привычке.</p>]]></content:encoded>
    </item>
  </channel>
</rss>