<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds/rss-style.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Nevermind</title>
        <link>https://blog.m1ng.space/ru</link>
        <description>Блог m1ngsama</description>
        <lastBuildDate>Thu, 13 Aug 2026 08:51:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Astro-Theme-Retypeset with Feed for Node.js</generator>
        <language>ru</language>
        <copyright>Copyright © 2026 m1ngsama</copyright>
        <atom:link href="https://blog.m1ng.space/ru/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Как создать свою собственную операционную систему]]></title>
            <link>https://blog.m1ng.space/ru/posts/myarch/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/myarch/</guid>
            <pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Arch Linux придерживается минимализма, позволяя пользователям самостоятельно создавать любые желаемые функции. В этом руководстве мы рассмот...]]></description>
            <content:encoded><![CDATA[<p>Arch Linux придерживается <a href="https://wiki.archlinux.org/title/Arch_Linux">минимализма</a>, позволяя пользователям самостоятельно создавать любые желаемые функции. В этом руководстве мы рассмотрим процесс установки Arch Linux на реальном оборудовании.</p>
<h2>Подготовка</h2>
<p>Вам понадобятся: компьютер, USB-накопитель (или любое съемное устройство хранения), подключение к интернету и базовые навыки поиска информации.</p>
<ul>
<li>Независимо от выбранного образа установки, даже для оффлайн-установки, рекомендуется иметь доступ к интернету для обновления ядра и инструментов. Если вы опытный пользователь, можете принять решение самостоятельно.</li>
<li>При использовании Wi-Fi убедитесь, что имя сети указано на английском языке, так как в среде tty не отображаются не-ASCII символы (например, кириллица), которые будут выглядеть как нечитаемые блоки.</li>
<li>Если вы планируете установить двойную загрузку на одном диске, выделите достаточно места для Arch Linux — рекомендуется не менее 100 ГБ для будущих установок программ. Убедитесь, что раздел EFI имеет объем не менее 256 МБ или <a href="https://wiki.archlinux.org/title/EFI_system_partition">создайте дополнительную точку монтирования</a>.</li>
<li>Проверьте, включено ли шифрование BitLocker на разделе Windows 10. Заранее получите ключ восстановления и отключите функцию быстрого запуска в настройках питания!</li>
</ul>
<blockquote>
<p>Перед началом внимательно прочитайте инструкции и изучите непонятные моменты. Действуйте осторожно, регулярно создавайте резервные копии — данные бесценны.</p>
</blockquote>
<h2>Создание установочного носителя</h2>
<ol>
<li>Загружайте установочный образ только с <a href="https://archlinux.org/download/">официальной страницы Arch Linux</a>. Обратите внимание, что Arch Linux — это дистрибутив с непрерывным обновлением.</li>
<li>Если вы хотите собрать собственное ядро, обратитесь к <a href="https://wiki.archlinux.org/title/Kernel/Traditional_compilation">руководству по традиционной компиляции ядра</a>.</li>
<li>Для официального установочного образа рекомендуется использовать <a href="https://www.ventoy.net/">Ventoy</a> для создания загрузочного USB-накопителя.</li>
</ol>
<h2>Базовая установка</h2>
<h3>1. Загрузка с носителя Arch Linux</h3>
<blockquote>
<p>Выключите компьютер, вставьте USB-накопитель и включите его. Войдите в BIOS, выберите USB в качестве загрузочного устройства, выберите первый пункт меню и нажмите Enter, чтобы войти в среду установки Arch Linux.</p>
</blockquote>
<h3>2. Проверка UEFI</h3>
<pre><code>systemctl stop reflector.service
# Отключите автоматическое обновление зеркал, так как географические особенности сети могут вызывать проблемы.
</code></pre>
<pre><code>ls /sys/firmware/efi/efivars
# Если отображается список EFI-переменных, система загружена в режиме UEFI. Большинство компьютеров в 2025 году используют UEFI.
</code></pre>
<h3>3. Настройка сети</h3>
<blockquote>
<p>Для установки Arch Linux требуется подключение к интернету. Оффлайн-установка сложнее, см. <a href="https://wiki.archlinux.org/title/Offline_installation">руководство по оффлайн-установке</a>.</p>
<p>Для проводного подключения вставьте кабель Ethernet, проверьте, мигает ли индикатор интерфейса, и подождите несколько секунд, пока соединение не установится.</p>
<p>В университетских сетях может потребоваться аутентификация через вышестоящий маршрутизатор. См. проект <a href="https://github.com/nbtca/nbtverify">nbtverify</a>.</p>
<p>Для Wi-Fi используйте <code>iwctl</code> для подключения.</p>
</blockquote>
<pre><code>lspci -k | grep Network
# Проверьте, работает ли беспроводной адаптер. Если уверены, что он исправен, этот шаг можно пропустить.
</code></pre>
<blockquote>
<p>Проверьте, загрузил ли ядро драйвер беспроводной сети.</p>
<p>Ожидаемый вывод: <code>00:14.3 Network controller: Intel Corporation Wi-Fi 6 AX201 (rev 20)</code>.</p>
<p>Если ничего не отображается, проверьте, не заблокировано ли беспроводное соединение (blocked: yes).</p>
</blockquote>
<pre><code>rfkill list
# Беспроводной адаптер обычно называется wlan0.
</code></pre>
<pre><code>ip link set wlan0 up
# Если появляется ошибка типа «Operation not possible due to RF-kill», выполните:
rfkill unblock wifi
</code></pre>
<pre><code># Подключение к Wi-Fi с помощью iwctl
iwctl # Вход в интерактивный режим
device list # Список беспроводных устройств, например wlan0
station wlan0 scan # Сканирование сетей
station wlan0 get-networks # Список доступных Wi-Fi сетей
station wlan0 connect wifi-name # Подключение к сети. Имена на кириллице не поддерживаются. Введите пароль и нажмите Enter.
exit # Выход после успешного подключения

ping www.google.com # Проверка соединения
</code></pre>
<blockquote>
<p>Если возникли проблемы с настройкой сети, обратитесь к <a href="https://wiki.archlinux.org/title/Network_configuration/Wireless">руководству по настройке сети/беспроводной сети</a>.</p>
</blockquote>
<h3>5. Синхронизация системных часов</h3>
<pre><code>timedatectl set-ntp true # Синхронизация системного времени с сетевым
timedatectl status # Проверка статуса службы
</code></pre>
<h3>6. Обновление списка зеркал (для России)</h3>
<pre><code>vim /etc/pacman.d/mirrorlist # Редактирование списка зеркал
Server = https://mirror.yandex.ru/mirrors/archlinux/$repo/os/$arch # Зеркало Яндекс
Server = https://mirror.truenetwork.ru/archlinux/$repo/os/$arch # TrueNetwork
Server = https://mirrors.dotsrc.org/archlinux/$repo/os/$arch # Dotsrc
</code></pre>
<h3>7. Создание разделов Btrfs</h3>
<h4>Проверка информации о дисках</h4>
<pre><code>lsblk
</code></pre>
<p>Отобразится текущая структура разделов. <strong>Тщательно проверьте целевой диск для установки Arch Linux</strong>.</p>
<p>Соглашения об именовании дисков:</p>
<ul>
<li><strong>SATA-диски</strong>: <code>sda</code>, <code>sdb</code>, <code>sdc</code> … Разделы: <code>sda1</code>, <code>sda2</code> и т.д.</li>
<li><strong>NVMe-диски</strong>: <code>nvme0n1</code>, <code>nvme1n1</code> … Разделы: <code>nvme0n1p1</code>, <code>nvme0n1p2</code> и т.д.</li>
</ul>
<blockquote>
<p>В примере используется SATA-диск. Замените <code>/dev/sdx</code> на ваш диск.</p>
</blockquote>
<pre><code>cfdisk /dev/sdx
</code></pre>
<p>Появится удобный TUI-интерфейс для работы с разделами. 😄</p>
<h4>Шаги по созданию разделов</h4>
<h5>1. Создание раздела подкачки (Swap)</h5>
<ul>
<li>С помощью стрелок выберите <strong>Free space</strong>.</li>
<li>Нажмите <code>[New]</code>, введите размер (рекомендуется 60–100% от объема оперативной памяти).</li>
<li>Нажмите <code>[Type]</code> и выберите <strong>Linux swap</strong>.</li>
</ul>
<h5>2. Создание корневого раздела (для Btrfs)</h5>
<ul>
<li>Выберите оставшееся Free space, нажмите <code>[New]</code> и Enter.</li>
<li>Укажите размер (по умолчанию: весь оставшийся объем).</li>
<li>Оставьте тип как <strong>Linux filesystem</strong>.</li>
</ul>
<h5>3. Запись таблицы разделов</h5>
<ul>
<li>Выберите <code>[Write]</code>, введите <code>yes</code> и нажмите Enter.
<blockquote>
<p>⚠️ <strong>Примечание: изменения не вступят в силу без записи!</strong></p>
</blockquote>
</li>
</ul>
<h4>Форматирование разделов</h4>
<h5>Повторная проверка дисков</h5>
<pre><code>fdisk -l
</code></pre>
<h5>Форматирование раздела EFI (если создается новый)</h5>
<pre><code>mkfs.fat -F32 /dev/sdxn
</code></pre>
<blockquote>
<p>💡 Для двойной загрузки можно использовать существующий EFI-раздел Windows без форматирования, но убедитесь, что места достаточно. См. <a href="https://wiki.archlinux.org/title/Dual_boot_with_Windows">Двойная загрузка с Windows</a>.</p>
</blockquote>
<h5>Форматирование раздела подкачки</h5>
<pre><code>mkswap /dev/sdxn
</code></pre>
<h5>Форматирование раздела Btrfs</h5>
<pre><code>mkfs.btrfs -L myArch /dev/sdxn
</code></pre>
<h4>Создание и монтирование подтомов Btrfs</h4>
<pre><code>mount -t btrfs -o compress=zstd /dev/sdxn /mnt

# Создание подтомов
btrfs subvolume create /mnt/@        # Корневой подтом
btrfs subvolume create /mnt/@home    # Подтом для /home

umount /mnt
</code></pre>
<h4>⚠️ Финальное напоминание</h4>
<ul>
<li>Еще раз проверьте команды и операции!</li>
<li><strong>Ошибки могут привести к потере данных, особенно при случайном удалении разделов Windows 😥.</strong></li>
</ul>
<h3>8. Монтирование разделов (начиная с корневого)</h3>
<pre><code>mount -t btrfs -o subvol=/@,compress=zstd /dev/sdxn /mnt # Монтирование / каталога
mkdir /mnt/home # Создание каталога /home
mount -t btrfs -o subvol=/@home,compress=zstd /dev/sdxn /mnt/home # Монтирование каталога /home
mkdir -p /mnt/boot # Создание каталога /boot
mount /dev/sdxn /mnt/boot # Монтирование каталога /boot
swapon /dev/sdxn # Активация раздела подкачки
</code></pre>
<pre><code>df -h # Проверка монтирования
free -h # Проверка монтирования раздела подкачки
</code></pre>
<h3>9. Установка системы</h3>
<pre><code>pacstrap /mnt base base-devel linux linux-firmware btrfs-progs
# Если используется файловая система Btrfs, установите пакет btrfs-progs
</code></pre>
<pre><code>pacman -S archlinux-keyring
# Если возникает ошибка GPG-ключа, возможно, образ устарел. Обновите archlinux-keyring для решения.
</code></pre>
<pre><code>pacstrap /mnt networkmanager vim sudo zsh zsh-completions
# Установка необходимых функциональных пакетов с помощью pacstrap
</code></pre>
<h3>10. Генерация файла fstab</h3>
<blockquote>
<p>Генерирует fstab для определения разделов диска на основе текущих точек монтирования.</p>
</blockquote>
<pre><code>genfstab -U /mnt &gt; /mnt/etc/fstab
</code></pre>
<h3>11. Вход в новую систему</h3>
<pre><code>arch-chroot /mnt
# Пропала подсветка кода? Не волнуйтесь, вы успешно вошли в chroot!
</code></pre>
<h3>12. Настройка имени хоста и часового пояса</h3>
<pre><code>vim /etc/hostname
# Задайте имя хоста (избегайте специальных символов и пробелов, иначе могут возникнуть проблемы; отсутствие имени хоста может привести к сбоям некоторых GUI-приложений).
</code></pre>
<pre><code>vim /etc/hosts
# Редактирование файла hosts
</code></pre>
<blockquote>
<p>Добавьте следующее (замените myarch на ваше имя хоста, используйте табуляцию для выравнивания):</p>
</blockquote>
<pre><code>127.0.0.1   localhost
::1         localhost
127.0.1.1   myarch.localdomain  myarch
</code></pre>
<pre><code>ln -sf /usr/share/zoneinfo/Europe/Moscow /etc/localtime
# Создание символической ссылки для часового пояса Москвы
</code></pre>
<pre><code>ls /usr/share/zoneinfo/
# Проверка доступных часовых поясов, при необходимости измените путь в команде выше
</code></pre>
<h3>13. Настройка аппаратных часов</h3>
<pre><code>hwclock --systohc
# Синхронизация системного времени с аппаратными часами
</code></pre>
<h3>14. Настройка локализации</h3>
<pre><code>vim /etc/locale.gen
# Отредактируйте /etc/locale.gen, раскомментируйте строки en_US.UTF-8 UTF-8 и ru_RU.UTF-8 UTF-8
# Этот шаг определяет язык и кодировку для программ
</code></pre>
<pre><code>locale-gen
# Генерация локалей
</code></pre>
<pre><code>echo 'LANG=en_US.UTF-8' &gt; /etc/locale.conf
# Настройка locale.conf. Русская локаль не рекомендуется, так как может вызвать проблемы с кодировкой в tty
</code></pre>
<h3>15. Установка пароля root</h3>
<pre><code>passwd root
# Ввод пароля не отображается — это нормально, клавиатура не сломана! 😄
</code></pre>
<h3>16. Установка микрокода</h3>
<pre><code>pacman -S intel-ucode # Для процессоров Intel
pacman -S amd-ucode # Для процессоров AMD
</code></pre>
<h3>17. Установка загрузчика Grub</h3>
<pre><code>pacman -S grub efibootmgr os-prober
# grub — загрузчик, efibootmgr записывает загрузочные записи в NVRAM, os-prober позволяет обнаружить Windows 10
</code></pre>
<pre><code>grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=ARCH
# Установка grub в раздел EFI
</code></pre>
<pre><code>vim /etc/default/grub
# Редактирование параметров загрузки
</code></pre>
<pre><code># Измените "loglevel=3 quiet" на "loglevel=5 nowatchdog"
# Добавьте в конец файла: GRUB_DISABLE_OS_PROBER=false
</code></pre>
<ul>
<li>Удалите параметр <code>quiet</code> из строки GRUB_CMDLINE_LINUX_DEFAULT.</li>
<li>Измените <code>loglevel</code> с 3 на 5 для упрощения отладки ошибок.</li>
<li>Добавьте <code>nowatchdog</code> для ускорения включения/выключения.</li>
<li>Включите <code>os-prober</code> для обнаружения Windows 10.</li>
</ul>
<pre><code>grub-mkconfig -o /boot/grub/grub.cfg
# Генерация конфигурационного файла grub
# Если обнаружена Windows 10, появится строка вроде: «Found Windows Boot Manager on /dev/nvme0n1p1@/EFI/Microsoft/Boot/bootmgfw.efi done»
# Если Windows на другом диске, вывода не будет. После входа в систему перемонтируйте и повторите команду.
</code></pre>
<blockquote>
<p>Подробности о параметрах см. в <a href="https://wiki.archlinux.org/title/GRUB">Arch Wiki</a>.</p>
</blockquote>
<h3>18. Завершение установки</h3>
<pre><code>exit # Возврат в среду установки
umount -R /mnt # Размонтирование новых разделов
reboot # Перезагрузка
</code></pre>
<blockquote>
<p>После перезагрузки войдите под учетной записью root.</p>
</blockquote>
<pre><code>systemctl enable --now NetworkManager # Включение и запуск службы NetworkManager
ping www.google.com # Проверка сетевого подключения
</code></pre>
<blockquote>
<p>Для Wi-Fi:</p>
</blockquote>
<pre><code>nmcli dev wifi list # Список ближайших Wi-Fi сетей
nmcli dev wifi connect "SSID Wi-Fi" password "пароль сети" # Подключение к указанной Wi-Fi сети
</code></pre>
<pre><code>nmtui
# Лично я предпочитаю nmtui — он удобнее! 😄
</code></pre>
<pre><code>pacman -S fastfetch
fastfetch
# Установка fastfetch для проверки системной информации
# Время для классического момента neofetch! 😄
</code></pre>
<pre><code>shutdown 0
shutdown -h now
poweroff
# Все три команды выключают систему. 😄 Не забудьте выключить, так как политики питания еще не настроены.
</code></pre>
<h2>Поздравляем 🎉</h2>
<blockquote>
<p>Вы успешно установили минимальную версию Arch Linux без графического интерфейса!</p>
<p>Руководство по установке графического интерфейса будет в следующем обновлении, но, как всегда: читайте документацию!</p>
<p>Это руководство — лишь отправная точка, надеемся, оно вдохновит больше энтузиастов присоединиться к сообществу!</p>
</blockquote>
<hr />
<blockquote>
<p>Связанные ресурсы: <a href="https://nbtca.space/">NBTCA</a></p>
</blockquote>
<ul>
<li>📧 Электронная почта NBTCA: <a href="mailto:contact@nbtca.com">contact@nbtca.com</a></li>
<li>🌐 NBTCA GitHub: <a href="https://github.com/nbtca">https://github.com/nbtca</a></li>
</ul>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Глубокое понимание Hydration: «необходимое зло» современных фронтенд-фреймворков?]]></title>
            <link>https://blog.m1ng.space/ru/posts/frameworks/deep-dive-into-hydration/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/frameworks/deep-dive-into-hydration/</guid>
            <pubDate>Sat, 10 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Серверный рендеринг (SSR) ускоряет первоначальную загрузку, но вместе с ним появляются Hydration и связанные с ней издержки. Что это такое? Почему возникает «задержка интерактивности»? И какие альтернативы исследует сообщество?]]></description>
            <content:encoded><![CDATA[<h2>1. Преимущества и проблемы SSR</h2>
<p>Серверный рендеринг (SSR) значительно улучшает «скорость загрузки первого экрана» (FCP) веб-приложений. Браузер сразу получает готовый HTML и немедленно отображает его, поэтому пользователь быстро видит содержимое страницы, что полезно и для SEO, и для воспринимаемой скорости.</p>
<p>Однако в этот момент страница представляет собой лишь «статическую оболочку». Хотя внешне загрузка уже завершилась, нажатия на кнопки и взаимодействие с полями ввода ни к чему не приводят. Чтобы «оживить» такую статическую страницу, необходим процесс <strong>Hydration</strong>.</p>
<h2>2. Что такое Hydration?</h2>
<p>Hydration — это процесс, в ходе которого клиентский JavaScript-фреймворк «берёт под управление» статический HTML, отрисованный на сервере.</p>
<p>В общих чертах процесс выглядит так:</p>
<ol>
<li>Браузер загружает и выполняет необходимые странице JavaScript-бандлы, например runtime React или Vue и код приложения.</li>
<li>Фреймворк заново строит дерево компонентов в памяти.</li>
<li>Фреймворк обходит отрисованный на сервере DOM и прикрепляет обработчики событий, например <code>onClick</code>, к соответствующим DOM-узлам.</li>
<li>Фреймворк проверяет, что состояние компонентов на клиенте совпадает с состоянием, использованным при серверном рендеринге.</li>
</ol>
<p>Только после завершения этого процесса страница становится по-настоящему интерактивной. Hydration можно представить так: вы получили уже собранную модель LEGO (HTML), но, чтобы заставить одну деталь двигаться, нужно от начала до конца прочитать инструкцию по сборке (JS), проверить расположение всех кирпичиков и лишь затем установить в эту деталь батарейку (прикрепить обработчики событий).</p>
<h2>3. Проблема Hydration: «неинтерактивный интерактивный интерфейс»</h2>
<p>Hydration решает проблему отсутствия интерактивности у SSR-страницы, но одновременно создаёт новое узкое место производительности, известное как <strong>«зловещая долина» (Uncanny Valley)</strong> или <strong>«разрыв Hydration» (Hydration Gap)</strong>.</p>
<p>Речь идёт о промежутке между моментом, когда пользователь видит содержимое страницы (FCP), и моментом, когда страница действительно может реагировать на его действия (TTI). В этот промежуток страница выглядит готовой к работе, но фактически остаётся «замороженной».</p>
<p>Причины этого таковы:</p>
<ul>
<li><strong>Блокирующие загрузка и выполнение JS</strong>: Прежде чем начать Hydration, браузер должен загрузить, разобрать и выполнить большой объём JavaScript-кода.</li>
<li><strong>Высокая стоимость запуска</strong>: Фреймворку приходится выполнять на клиенте много работы по восстановлению дерева компонентов и подключению обработчиков событий, даже если 90% страницы составляет неинтерактивный статический контент.</li>
</ul>
<h2>4. Оптимизация Hydration и альтернативы</h2>
<p>Чтобы решить проблемы производительности Hydration, сообщество исследовало несколько более совершенных подходов.</p>
<h4>a. Частичная Hydration (Partial Hydration)</h4>
<p><strong>Архитектура островов (Islands Architecture)</strong>, популяризированная фреймворком <strong>Astro</strong>, — типичная реализация частичной Hydration. Её основная идея состоит в том, что по умолчанию все компоненты выводят только статический HTML (ноль JS), а компоненты, которым нужна интерактивность, явно отмечаются как «острова».</p>
<p>Во время сборки Astro упаковывает и отправляет JavaScript только для этих компонентов-«островов». Поэтому браузеру нужно выполнить Hydration лишь для нескольких изолированных частей страницы, а не для всей страницы, что значительно сокращает объём JavaScript, загружаемого и выполняемого при запуске.</p>
<h4>b. Прогрессивная Hydration (Progressive Hydration)</h4>
<p>Это более детальная стратегия оптимизации, при которой компоненты проходят Hydration в порядке приоритета. Например, сначала можно обработать компоненты в области просмотра или те, с которыми пользователь скоро начнёт взаимодействовать, а некритичные компоненты внизу страницы отложить.</p>
<h4>c. Возобновляемость (Resumability)</h4>
<p>Фреймворк <strong>Qwik</strong> предлагает революционную концепцию — <strong>Resumability</strong>, цель которой полностью устранить Hydration.</p>
<p>Она работает следующим образом:</p>
<ol>
<li><strong>Сериализация</strong>: На сервере Qwik сериализует всё состояние приложения, связи между компонентами, сведения об обработчиках событий и другие данные, а затем встраивает их в HTML.</li>
<li><strong>Возобновление</strong>: На клиенте крошечному runtime Qwik (около 1KB) не требуется при запуске заново строить дерево компонентов или прикреплять все события. Благодаря глобальному обработчику событий он может точно определить, какой небольшой фрагмент кода следует загрузить и выполнить, когда пользователь нажмёт конкретную кнопку.</li>
</ol>
<p>Цель Qwik — обеспечить <strong>Instant-on</strong>, то есть мгновенную интерактивность. Он не «выполняет заново» работу сервера, а «возобновляет» выполнение с того места, где сервер остановился.</p>
<h2>Заключение</h2>
<p>Hydration служит мостом между серверным рендерингом и клиентской интерактивностью, однако в традиционных реализациях это дорогой процесс, способный ухудшить пользовательский опыт. Современные фронтенд-фреймворки пытаются избавиться от этого «необходимого зла» с помощью таких инновационных подходов, как частичная Hydration (Astro) и возобновляемость (Qwik), стремясь к ещё более высокой производительности и быстрой интерактивности.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Как WebAssembly (WASM) меняет фронтенд-разработку]]></title>
            <link>https://blog.m1ng.space/ru/posts/wasm/wasm-is-changing-frontend/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/wasm/wasm-is-changing-frontend/</guid>
            <pubDate>Sun, 30 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[JavaScript больше не единственный «игрок» в браузере. WebAssembly (WASM) обеспечивает производительность, близкую к нативной, и переносит в Web сложные приложения настольного уровня, такие как Photoshop и Figma.]]></description>
            <content:encoded><![CDATA[<h2>1. «Второй язык» браузера</h2>
<p>Долгое время JavaScript оставался практически единственным языком фронтенд-разработки для Web. Он добился огромного успеха, однако как динамический интерпретируемый язык по-прежнему сталкивается с ограничениями производительности в задачах с высокой нагрузкой на процессор: трёхмерном рендеринге, кодировании и декодировании видео, сложных вычислениях.</p>
<p>WebAssembly, или WASM, появился именно для того, чтобы преодолеть это ограничение. Он не должен заменить JavaScript, а служит мощным дополнением, которое приносит Web-платформе невиданную прежде производительность и новые возможности.</p>
<h2>2. Что такое WebAssembly?</h2>
<p>WebAssembly — это <strong>двоичный формат инструкций</strong>, предназначенный для виртуальной машины со стековой архитектурой. Это низкоуровневый язык, похожий на ассемблер, но разработчикам не предполагается писать на нём напрямую.</p>
<p>Вместо этого он создан как <strong>цель компиляции</strong> для языков высокого уровня — C, C++, Rust, Go и других. Код можно написать на одном из этих производительных языков, скомпилировать в файл <code>.wasm</code> и выполнять в браузере со скоростью, близкой к нативной.</p>
<p><strong>Ключевые положения:</strong></p>
<ul>
<li><strong>Это не замена JavaScript</strong>: WASM и JavaScript работают вместе.</li>
<li><strong>Это цель компиляции</strong>: вы пишете на C++ или Rust и компилируете код в WASM.</li>
<li><strong>Он быстр, эффективен и переносим</strong>: производительность заложена в его основу с самого начала.</li>
</ul>
<h2>3. Как взаимодействуют JavaScript и WASM</h2>
<p>Модуль WASM работает в изолированной среде. Он не может напрямую обращаться к DOM, вызывать Web API или отправлять сетевые запросы. Посредником для всех этих действий служит JavaScript — своего рода связующий слой.</p>
<p>Типичная схема взаимодействия выглядит так:</p>
<ol>
<li><strong>JavaScript отвечает за координацию</strong>: JS-код управляет общей логикой приложения, обрабатывает пользовательские события и обновляет DOM.</li>
<li><strong>WASM отвечает за вычисления</strong>: когда возникает ресурсоёмкая вычислительная задача, JS вызывает функции, экспортированные из модуля <code>.wasm</code>.</li>
<li><strong>Обмен данными</strong>: JS и WASM могут эффективно обмениваться данными — прежде всего числами и блоками линейной памяти.</li>
</ol>
<p>JavaScript можно представить как «руководителя», а WebAssembly — как «инженера-эксперта». Руководитель общается и координирует работу, а инженер решает самые трудные технические задачи.</p>
<h2>4. Примеры применения в реальном мире</h2>
<p>WASM уже перестал быть экспериментальной технологией. Многие ведущие Web-приложения используют его для своих основных функций:</p>
<ul>
<li><strong>Figma</strong>: основной движок рендеринга этого популярного онлайн-инструмента для дизайна написан на C++ и скомпилирован в WASM, что обеспечивает плавное редактирование графики.</li>
<li><strong>Adobe Photoshop &amp; Lightroom</strong>: с помощью WASM компания Adobe успешно перенесла в Web ядро своих флагманских настольных приложений, написанное на C++, и дала возможность пользоваться мощными функциями Photoshop в браузере.</li>
<li><strong>Google Earth</strong>: новая версия Google Earth полностью работает в браузере, а сложный рендеринг трёхмерного земного шара обеспечивает WASM.</li>
<li><strong>AutoCAD Web App</strong>: Autodesk скомпилировала свой крупный CAD-движок на C++ в WASM, благодаря чему полноценный AutoCAD может работать в браузере.</li>
</ul>
<p>Эти примеры доказывают, что WASM уже способен переносить на Web-платформу сложные программы, которые раньше считались возможными только в виде настольных приложений.</p>
<h2>5. Будущее: WASI и мир за пределами браузера</h2>
<p>Амбиции WASM не ограничиваются браузером. <strong>WASI (WebAssembly System Interface)</strong> — развивающийся стандарт, цель которого состоит в том, чтобы предоставить WASM единый набор системных API, например доступ к файловой системе и сети.</p>
<p>Это означает, что в будущем файлы <code>.wasm</code> могут стать универсальным, кроссплатформенным и безопасным двоичным форматом, способным работать где угодно: на серверах, где он может составить конкуренцию Docker, на узлах периферийных вычислений и в устройствах Интернета вещей. Так действительно воплотится принцип «скомпилировать один раз, запускать везде».</p>
<h2>Заключение</h2>
<p>WebAssembly глубоко меняет наше представление о границах возможностей Web-приложений. JavaScript может сосредоточиться на том, что умеет лучше всего, — взаимодействии с интерфейсом и координации логики приложения, а потолок производительности берут на себя модули WASM, скомпилированные из системных языков вроде Rust и C++. Для фронтенд-разработчика понимание возможностей WASM и подходящих сценариев его применения станет ключом к созданию следующего поколения высокопроизводительных Web-приложений.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Создание плавных переходов между страницами с помощью View Transitions API]]></title>
            <link>https://blog.m1ng.space/ru/posts/css/view-transitions-api/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/css/view-transitions-api/</guid>
            <pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[«Белая вспышка» при переходе между страницами давно ухудшает пользовательский опыт. Теперь браузеры предлагают нативный View Transitions API, позволяющий создать кинематографичные переходы всего несколькими строками кода.]]></description>
            <content:encoded><![CDATA[<h2>1. Разрыв в восприятии при переходе между страницами</h2>
<p>В веб-разработке долгое время существовала дилемма, связанная с пользовательским опытом:</p>
<ul>
<li><strong>Многостраничные приложения (MPA)</strong>: Их архитектура проста и стабильна, но каждый переход вызывает полную загрузку страницы, сопровождается «белым экраном» или «вспышкой» и нарушает непрерывность взаимодействия.</li>
<li><strong>Одностраничные приложения (SPA)</strong>: Фронтенд-маршрутизация обеспечивает плавную работу без перезагрузки, но разработчику приходится вручную управлять сложными анимациями переходов, состоянием и разделением кода, что повышает стоимость разработки.</li>
</ul>
<p>Можно ли сохранить простоту MPA и одновременно получить плавные переходы SPA? View Transitions API создан именно для этого.</p>
<h2>2. Что такое View Transitions API?</h2>
<p>View Transitions API — нативный браузерный API, предоставляющий механизм для простого создания анимированных переходов между двумя разными состояниями DOM.</p>
<p>Основной процесс очень прост:</p>
<ol>
<li>При запуске перехода API делает «снимок» текущей страницы (старого состояния).</li>
<li>Затем с помощью JavaScript DOM обновляется до нового состояния.</li>
<li>API также делает «снимок» новой страницы (нового состояния).</li>
<li>Наконец браузер создаёт плавный переход по умолчанию, обычно с затуханием, между «старым снимком» и «новым снимком».</li>
</ol>
<p>Всё это эффективно обрабатывается на низком уровне браузером; нам достаточно вызвать одну простую функцию.</p>
<h2>3. Как его использовать?</h2>
<p>Использовать View Transitions в одностраничном приложении очень просто. Достаточно обернуть логику обновления DOM в функцию <code>document.startViewTransition</code>.</p>
<pre><code>// 假设你有一个更新页面内容的函数
async function updatePageContent(url) {
  const response = await fetch(url);
  const newHtml = await response.text();
  // 用新内容替换旧内容 (具体实现取决于你的架构)
  document.body.innerHTML = newHtml;
}

// 在导航链接的点击事件中调用
document.querySelector('a').addEventListener('click', (event) =&gt; {
  event.preventDefault();
  const url = event.target.href;

  // 检查浏览器是否支持
  if (!document.startViewTransition) {
    updatePageContent(url); // 不支持则直接更新
    return;
  }

  // 使用 View Transition
  document.startViewTransition(() =&gt; updatePageContent(url));
});
</code></pre>
<p>Этого достаточно, чтобы переходы между страницами получили стандартный эффект плавного затухания!</p>
<h2>4. Настройка анимации перехода</h2>
<p>Стандартный эффект затухания хорош, но настоящая сила API заключается в гибкой настройке. Во время работы <code>startViewTransition</code> браузер создаёт структуру DOM со следующими псевдоэлементами:</p>
<ul>
<li><code>::view-transition</code>: Корневой элемент, содержащий весь переход.</li>
<li><code>::view-transition-old(root)</code>: «Снимок» старого представления.</li>
<li><code>::view-transition-new(root)</code>: «Снимок» нового представления.</li>
</ul>
<p>Стандартный эффект можно переопределить с помощью CSS-свойства <code>animation</code>, например реализовать анимацию появления со сдвигом:</p>
<pre><code>@keyframes slide-in {
  from { transform: translateX(100%); }
}

::view-transition-new(root) {
  animation: slide-in 0.5s ease-out;
}
</code></pre>
<h2>5. «Преобразование» между элементами: <code>view-transition-name</code></h2>
<p>Самая впечатляющая возможность API — распознавать <strong>разные элементы</strong> на двух страницах как <strong>один и тот же объект</strong> и создавать между ними плавную анимацию «преобразования».</p>
<p>Это реализуется с помощью CSS-свойства <code>view-transition-name</code>.</p>
<p><strong>Страница A (список):</strong></p>
<pre><code>&lt;img src="thumbnail.jpg" style="view-transition-name: hero-image;" /&gt;
</code></pre>
<p><strong>Страница B (подробности):</strong></p>
<pre><code>&lt;img src="full-size.jpg" style="view-transition-name: hero-image;" /&gt;
</code></pre>
<p>При переходе со страницы A на страницу B браузер видит одинаковое значение <code>view-transition-name</code> у двух элементов, автоматически вычисляет различия в их положении, размере и форме и создаёт плавную анимацию вместо простого затухания. Это особенно хорошо подходит для переходов изображений, карточек и подобных элементов.</p>
<h2>Заключение</h2>
<p>View Transitions API приносит в Web долгожданную нативную поддержку переходов. Он значительно упрощает эффекты, для которых раньше требовались сложные JavaScript-библиотеки анимации, и как никогда облегчает создание плавного, кинематографичного опыта. По мере интеграции во фреймворки вроде Astro и Nuxt и будущего распространения в многостраничных приложениях он наверняка станет одним из стандартных навыков современного веб-разработчика.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Глубокое понимание React Server Components]]></title>
            <link>https://blog.m1ng.space/ru/posts/react/deep-dive-into-rsc/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/react/deep-dive-into-rsc/</guid>
            <pubDate>Fri, 15 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[React Server Components (RSC) — один из важнейших сдвигов парадигмы React за последние годы. В статье подробно рассматриваются основные идеи RSC, решаемые ими проблемы и то, как они меняют подход к созданию приложений.]]></description>
            <content:encoded><![CDATA[<h2>1. Что такое React Server Components (RSC)?</h2>
<p>React Server Components (RSC) — это новый тип компонентов React, которые рендерятся <strong>во время сборки или на сервере</strong>. В отличие от традиционных компонентов, рендерящихся в браузере клиента и теперь называемых «Client Components», код RSC никогда не отправляется клиенту. Браузер получает только готовый HTML или данные в специальном потоковом формате.</p>
<p>Это знаменует превращение React из исключительно клиентской библиотеки в full-stack-фреймворк, способный бесшовно работать между сервером и клиентом.</p>
<h2>2. Какие проблемы решают RSC?</h2>
<p>RSC появились прежде всего для решения следующих ключевых проблем:</p>
<ul>
<li><strong>Огромные пакеты JavaScript</strong>: Традиционные React-приложения упаковывают и отправляют клиенту JS-код всех компонентов, в том числе не имеющих прямой интерактивности, что замедляет первоначальную загрузку. RSC позволяют оставить множество компонентов — например, текст, макеты и представления данных — на сервере, добиваясь для них <strong>нулевого объёма JS</strong>.</li>
<li><strong>Водопады запросов (Request Waterfalls)</strong>: Типичный шаблон получения данных на клиенте выглядит так: рендеринг компонента -&gt; <code>useEffect</code> -&gt; отправка запроса -&gt; ожидание данных -&gt; повторный рендеринг. При глубокой вложенности компонентов возникает водопад запросов, замедляющий отображение страницы. RSC могут получать данные прямо на сервере через <code>async/await</code> и передавать их клиенту потоком вместе с компонентами, устраняя первопричину проблемы.</li>
<li><strong>Прямой доступ к ресурсам бэкенда</strong>: RSC работают в серверной среде, например Node.js. Это позволяет им прямо и безопасно обращаться к базам данных, файловой системе или внутренним API, не открывая клиенту дополнительные API-эндпоинты.</li>
</ul>
<h2>3. Server Components и Client Components</h2>
<p>Понимание различий между ними — ключ к освоению RSC.</p>
<table>
<thead>
<tr>
<th>Характеристика</th>
<th>Server Components (серверные компоненты)</th>
<th>Client Components (клиентские компоненты)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Среда выполнения</strong></td>
<td>Сервер (Node.js и т. п.)</td>
<td>Клиент (браузер)</td>
</tr>
<tr>
<td><strong>JS отправляется клиенту</strong></td>
<td>Нет</td>
<td>Да</td>
</tr>
<tr>
<td><strong>Состояние (State)</strong></td>
<td>Не поддерживается (например, <code>useState</code>)</td>
<td>Поддерживается</td>
</tr>
<tr>
<td><strong>Жизненный цикл/Hooks</strong></td>
<td>Не поддерживаются (например, <code>useEffect</code>)</td>
<td>Поддерживаются</td>
</tr>
<tr>
<td><strong>Взаимодействие (Events)</strong></td>
<td>Не поддерживается (например, <code>onClick</code>)</td>
<td>Поддерживается</td>
</tr>
<tr>
<td><strong>Получение данных</strong></td>
<td>Поддерживает <code>async/await</code></td>
<td>Через <code>useEffect</code> или библиотеку получения данных</td>
</tr>
<tr>
<td><strong>Правила импорта</strong></td>
<td>Не может импортировать Client Components</td>
<td>Может импортировать Server Components (как <code>children</code> или <code>prop</code>)</td>
</tr>
</tbody>
</table>
<p><strong>Практическое правило</strong>: По умолчанию считайте все компоненты Server Components. Только когда компоненту нужны <code>useState</code>, <code>useEffect</code> или обработка пользовательских событий вроде <code>onClick</code>, добавляйте директиву <code>"use client";</code> в начало файла и помечайте его как Client Component.</p>
<h2>4. Как они работают вместе?</h2>
<p>Современное React-приложение сочетает оба типа. Например, страница записи в блоге может состоять из следующих частей:</p>
<ul>
<li><code>PageLayout</code> (Server Component): Отвечает за общий макет.</li>
<li><code>ArticleContent</code> (Server Component): Получает данные статьи из базы данных или Markdown-файла и рендерит их.</li>
<li><code>LikeButton</code> (Client Component): Содержит <code>useState</code> и событие <code>onClick</code> для обработки отметки «Нравится».</li>
<li><code>Comments</code> (Client Component): Получает и отображает комментарии, содержит отправку формы и другие интерактивные элементы.</li>
</ul>
<p>Серверные компоненты могут на сервере «держать места» для клиентских компонентов, а затем браузер бесшовно соединит их вместе.</p>
<h2>Заключение</h2>
<p>React Server Components — глубокий сдвиг парадигмы, расширяющий возможности React с браузера на сервер и приносящий заметные преимущества в производительности и удобстве разработки. Хотя RSC требуют новой ментальной модели, практика в таких фреймворках, как Next.js App Router, быстро превращает их в новый стандарт создания высокопроизводительных и масштабируемых Web-приложений.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Прощай, process.env.UNDEFINED: типобезопасные переменные окружения в проекте]]></title>
            <link>https://blog.m1ng.space/ru/posts/typescript/typesafe-environment-variables/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/typescript/typesafe-environment-variables/</guid>
            <pubDate>Sun, 25 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Отсутствующие или неверно оформленные переменные окружения часто вызывают ошибки во время выполнения. С библиотекой валидации вроде Zod можно при запуске убедиться, что все переменные существуют и имеют правильный тип, и полностью устранить этот класс проблем.]]></description>
            <content:encoded><![CDATA[<h2>1. «Ловушка» <code>process.env</code></h2>
<p>В приложениях Node.js доступ к переменным окружения осуществляется через <code>process.env</code>. Это простой и эффективный механизм, однако у него есть врождённая проблема: <strong>отсутствие типобезопасности</strong>.</p>
<p>Значение в объекте <code>process.env</code> имеет тип <code>string</code> либо <code>undefined</code>. Из-за этого возникает несколько распространённых проблем:</p>
<ul>
<li><strong>Неожиданный <code>undefined</code></strong>: если забыть задать переменную в файле <code>.env</code> или на сервере, во время выполнения <code>process.env.MY_VAR</code> окажется равным <code>undefined</code>. Это может привести к <code>TypeError</code> глубоко внутри логики приложения.</li>
<li><strong>Несоответствие типов</strong>: можно ожидать, что номер порта имеет тип <code>number</code>, но <code>process.env.PORT</code> всегда является строкой, поэтому в каждом месте использования приходится вручную вызывать <code>parseInt</code>.</li>
<li><strong>Разрозненная логика проверки</strong>: мы часто пишем защитный код в разных частях проекта, например <code>const port = process.env.PORT || 3000;</code>, из-за чего управление переменными окружения становится хаотичным.</li>
</ul>
<h2>2. Основной принцип: немедленная ошибка (Fail-Fast)</h2>
<p>Для такой критически важной конфигурации, как переменные окружения, рекомендуется применять принцип <strong>немедленной ошибки (Fail-Fast)</strong>.</p>
<p>Это значит, что в самом начале запуска приложение должно проверить наличие и правильность формата всех обязательных переменных окружения. При любой проблеме оно должно сразу выдать ошибку и остановиться, а не аварийно завершиться позднее, в непредсказуемый момент, из-за неверной конфигурации.</p>
<p>Так проблемы конфигурации обнаруживаются сразу при развёртывании или разработке, а не в момент, когда к приложению обращается пользователь.</p>
<h2>3. Типобезопасная проверка с помощью Zod</h2>
<p>Zod — библиотека для объявления и проверки схем, изначально спроектированная для TypeScript. Она прекрасно подходит для решения проблемы типобезопасности переменных окружения.</p>
<p>Наша стратегия:</p>
<ol>
<li>Определить схему Zod для всех переменных окружения.</li>
<li>При запуске приложения разобрать <code>process.env</code> с помощью этой схемы.</li>
<li>Если разбор не удался, то есть данные не прошли проверку, Zod выдаёт ошибку, а запуск приложения завершается неудачей.</li>
<li>Если разбор успешен, мы получаем полностью типизированный объект и используем его во всём приложении.</li>
</ol>
<h4>Пример реализации</h4>
<p>Сначала установим Zod: <code>pnpm add zod</code></p>
<p>Затем создадим отдельный файл для переменных окружения, например <code>src/env.ts</code>:</p>
<pre><code>// src/env.ts
import { z } from 'zod';

// 1. 定义 Schema
const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  DATABASE_URL: z.string().min(1, 'DATABASE_URL is required.'),
  PORT: z.coerce.number().int().positive().default(3000),
  // z.coerce 会尝试将字符串转换为数字
});

// 2. 解析和导出
// .parse 会在验证失败时抛出错误，实现 "Fail-Fast"
export const env = envSchema.parse(process.env);
</code></pre>
<p>Теперь из <code>src/env</code> можно импортировать объект <code>env</code> в любое другое место приложения.</p>
<pre><code>// src/server.ts
import { env } from './env'; // 导入经过验证和类型化的 env 对象

// env.PORT 的类型是 `number`，而不是 `string | undefined`
const port = env.PORT;

// env.NODE_ENV 的类型是 'development' | 'production' | 'test'
if (env.NODE_ENV === 'development') {
  console.log('Running in development mode');
}

// 如果 DATABASE_URL 未设置，应用在启动时就已经崩溃了，
// 所以在这里我们可以放心地认为它是存在的，并且类型是 `string`。
connectToDatabase(env.DATABASE_URL);
</code></pre>
<h2>4. Преимущества</h2>
<p>Этот подход сразу приносит заметную пользу:</p>
<ul>
<li><strong>Полная типобезопасность</strong>: обращаясь в коде к <code>env.PORT</code>, TypeScript знает, что это <code>number</code>.</li>
<li><strong>Централизованные документация и проверка</strong>: сам файл <code>env.ts</code> становится авторитетным описанием переменных окружения, и вся логика проверки находится в одном месте.</li>
<li><strong>Надёжное выполнение</strong>: исключаются ошибки времени выполнения из-за опечаток, отсутствующих переменных окружения или неверного формата.</li>
<li><strong>Fail-Fast</strong>: любая проблема конфигурации обнаруживается в самом начале развёртывания или разработки.</li>
</ul>
<h2>Заключение</h2>
<p>Перенос проверки переменных окружения на момент запуска приложения и обеспечение типобезопасности с помощью такого инструмента, как Zod, — инженерная практика с очень высокой отдачей. Она заметно повышает надёжность приложения и уверенность разработчиков и является необходимой частью современного проекта на TypeScript.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Обновление Vue 3.4: более лаконичный v-bind и ещё выше производительность]]></title>
            <link>https://blog.m1ng.space/ru/posts/vue/vue-3-4-v-bind-updates/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/vue/vue-3-4-v-bind-updates/</guid>
            <pubDate>Mon, 15 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Вышла Vue 3.4 «🏀 Slam Dunk»: более эффективная переработанная система реактивности и стабильный API defineModel значительно улучшают разработку компонентов с двусторонней привязкой.]]></description>
            <content:encoded><![CDATA[<h2>1. Знакомство с Vue 3.4 «Slam Dunk»</h2>
<p>В начале 2024 года команда Vue выпустила версию 3.4 под кодовым названием «Slam Dunk». Это обновление не столь радикально, как версия 3.0, но приносит важные улучшения в двух ключевых областях: <strong>внутренней оптимизации производительности</strong> и <strong>удобстве разработки</strong>.</p>
<p>Самые заметные изменения — переработанная система реактивности, стабильный API <code>defineModel</code> и более лаконичный синтаксис <code>v-bind</code>.</p>
<h2>2. Переработка системы реактивности</h2>
<p>В Vue 3.4 была значительно переработана основная система реактивности. Главная цель этой работы — повысить эффективность вычисляемых свойств <code>computed</code>.</p>
<p>В предыдущих версиях вычисляемое свойство иногда пересчитывалось без необходимости, даже если его зависимости фактически не изменились. Благодаря более продуманному отслеживанию зависимостей новая версия запускает вычисления только тогда, когда они действительно нужны, и сокращает лишние повторные рендеры компонентов.</p>
<p>Для большинства разработчиков это «бесплатный обед»: изменять код не требуется, а переход на версию 3.4 автоматически даёт прирост производительности.</p>
<h2>3. <code>defineModel</code>: элегантная двусторонняя привязка в компонентах</h2>
<p>До версии 3.4 для реализации в компоненте двусторонней привязки наподобие <code>v-model</code> приходилось писать довольно много шаблонного кода:</p>
<p><strong>Раньше 👎:</strong></p>
<pre><code>&lt;script setup&gt;
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

// 通常需要一个计算属性来包装
const value = computed({
  get: () =&gt; props.modelValue,
  set: (val) =&gt; emit('update:modelValue', val)
})
&lt;/script&gt;

&lt;template&gt;
  &lt;input v-model="value" /&gt;
&lt;/template&gt;
</code></pre>
<p>В Vue 3.4 API <code>defineModel</code> вышел из экспериментального статуса и стал стабильным. Теперь для того же поведения достаточно одной строки кода:</p>
<p><strong>Теперь (Vue 3.4) 👍:</strong></p>
<pre><code>&lt;script setup&gt;
const model = defineModel()
&lt;/script&gt;

&lt;template&gt;
  &lt;input v-model="model" /&gt;
&lt;/template&gt;
</code></pre>
<p>Макрос <code>defineModel</code> автоматически регистрирует prop <code>modelValue</code> и событие <code>update:modelValue</code>, а затем возвращает <code>ref</code>, который можно непосредственно читать и изменять. Это значительно упрощает разработку компонентов с поддержкой <code>v-model</code>.</p>
<h2>4. Сокращённая запись <code>v-bind</code> для одноимённых свойств</h2>
<p>Ещё одно синтаксическое улучшение для удобства разработки — сокращённая запись <code>v-bind</code> при совпадении имён. Если передаваемый компоненту prop называется так же, как переменная в <code>&lt;script setup&gt;</code>, значение атрибута теперь можно опустить.</p>
<p><strong>Раньше 👎:</strong></p>
<pre><code>&lt;script setup&gt;
const id = 'my-id'
const title = 'Hello Vue'
&lt;/script&gt;

&lt;template&gt;
  &lt;MyComponent :id="id" :title="title" /&gt;
&lt;/template&gt;
</code></pre>
<p><strong>Теперь (Vue 3.4) 👍:</strong></p>
<pre><code>&lt;script setup&gt;
const id = 'my-id'
const title = 'Hello Vue'
&lt;/script&gt;

&lt;template&gt;
  &lt;MyComponent :id :title /&gt;
&lt;/template&gt;
</code></pre>
<p>Это небольшое изменение делает код шаблонов чище и понятнее.</p>
<h2>Заключение</h2>
<p>Vue 3.4 — основательное эволюционное обновление. Благодаря внутренней оптимизации производительности и упрощению API верхнего уровня оно ещё сильнее закрепляет философию Vue как «прогрессивного» фреймворка: обеспечивает высокую производительность крупных приложений и одновременно даёт ощутимые удобства в повседневной разработке. Особенно важна стабилизация <code>defineModel</code>: она решает давнюю проблему инкапсуляции компонентов и является самым заметным достоинством этого обновления.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Прощай, Rome, здравствуй, Biome: новый универсальный набор фронтенд-инструментов]]></title>
            <link>https://blog.m1ng.space/ru/posts/build-tools/intro-to-biome/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/build-tools/intro-to-biome/</guid>
            <pubDate>Fri, 10 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Идея набора инструментов Rome когда-то вдохновила множество разработчиков. После неудачи коммерческого проекта сообщество продолжило его дело под именем Biome. Сможет ли этот универсальный набор на Rust стать следующим инструментом в нашем арсенале?]]></description>
            <content:encoded><![CDATA[<h2>1. «Усталость от инструментов» во фронтенд-разработке</h2>
<p>В современной фронтенд-разработке проекту обычно требуется сложное сочетание инструментов для бесперебойной работы:</p>
<ul>
<li><strong>Linter</strong>: ESLint проверяет качество кода JavaScript/TypeScript.</li>
<li><strong>Formatter</strong>: Prettier обеспечивает единый стиль кода.</li>
<li><strong>Compiler</strong>: Babel или TypeScript (tsc) преобразует код.</li>
<li><strong>Bundler</strong>: Webpack, Rollup или Vite собирает приложение.</li>
</ul>
<p>Управление настройками, плагинами, версиями и взаимодействием этих инструментов само по себе требует немало труда. Это и называют «усталостью от инструментов». В течение многих лет сообщество изучало возможность универсального набора инструментов, надеясь решить все задачи одним средством.</p>
<h2>2. Идея Rome и рождение Biome</h2>
<p>Rome был амбициозным проектом, начатым бывшим сотрудником Facebook и автором Babel Себастьяном Маккензи. Он стремился переписать весь набор фронтенд-инструментов на TypeScript. Идея заключалась в едином высокопроизводительном инструменте, не требующем настройки. Однако после многих лет разработки и попыток коммерциализации компания Rome Labs в 2023 году объявила о прекращении работы.</p>
<p>К счастью, основной код Rome был открытым. Сообщество быстро создало форк под названием <strong>Biome</strong>, который стала поддерживать специальная общественная организация, чтобы унаследовать и воплотить первоначальную идею Rome.</p>
<h2>3. Что такое Biome?</h2>
<p>Biome — высокопроизводительный набор фронтенд-инструментов, переписанный на Rust. Он призван обеспечить единый и чрезвычайно быстрый процесс разработки, заменив ряд независимых инструментов, таких как ESLint и Prettier.</p>
<p>К концу 2023 года основные возможности Biome были сосредоточены прежде всего на <strong>Linter</strong> и <strong>Formatter</strong>, и в обеих областях он уже демонстрировал впечатляющие результаты.</p>
<h4>Основные преимущества:</h4>
<ul>
<li><strong>Высочайшая производительность</strong>: Благодаря Rust Biome работает намного быстрее написанных на JavaScript ESLint и Prettier. Для крупных кодовых баз прирост может достигать целого порядка.</li>
<li><strong>Единая конфигурация</strong>: Всего один файл <code>biome.json</code> позволяет управлять поведением всех инструментов, заменяя разбросанные <code>.eslintrc</code>, <code>.prettierrc</code> и <code>.editorconfig</code>.</li>
<li><strong>Подробная диагностика</strong>: Linter Biome не только указывает на ошибки, но и предоставляет подробный контекст и рекомендации по исправлению, помогая разработчикам понять причину проблемы.</li>
<li><strong>Совместимость с Prettier</strong>: Форматтер Biome стремится поддерживать более 95% правил Prettier, поэтому стоимость миграции с Prettier остается очень низкой.</li>
</ul>
<h2>4. Быстрый старт</h2>
<p>Biome можно быстро опробовать в проекте с помощью <code>npx</code>:</p>
<pre><code># 检查当前项目中的代码问题
npx @biomejs/biome check ./src

# 自动修复可修复的 lint 问题
npx @biomejs/biome check --apply ./src

# 格式化代码
npx @biomejs/biome format --write ./src
</code></pre>
<p>Создайте файл <code>biome.json</code>, чтобы настроить правила и параметры:</p>
<pre><code>{
  "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}
</code></pre>
<h2>5. Взгляд в будущее</h2>
<p>У Biome очень ясная дорожная карта: после стабилизации Linter и Formatter постепенно реализовать Compiler, Bundler, Test Runner и другие функции, чтобы в итоге стать по-настоящему универсальным набором инструментов.</p>
<p>Хотя Biome еще молод, благодаря превосходной производительности, открытой модели развития силами сообщества и вниманию к удобству разработчиков он уже стал заметной новой силой среди фронтенд-инструментов. Для команд, которые хотят упростить набор инструментов и повысить эффективность разработки, Biome предлагает очень привлекательный вариант на будущее.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Bun 1.0 вышел: универсальная среда выполнения бросает вызов Node.js]]></title>
            <link>https://blog.m1ng.space/ru/posts/build-tools/bun-1-0-release/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/build-tools/bun-1-0-release/</guid>
            <pubDate>Sun, 10 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[В сентябре 2023 года выпуск Bun 1.0 вызвал большой отклик в сообществе JavaScript. Созданный с нуля набор инструментов официально бросил вызов господству Node.js благодаря поразительной скорости и универсальному дизайну.]]></description>
            <content:encoded><![CDATA[<h2>1. Новый конкурент среди сред выполнения JavaScript</h2>
<p>Более десяти лет Node.js был синонимом серверного JavaScript. Хотя Deno предложил новые идеи в области безопасности и современных API, положение Node.js, казалось, никогда по-настоящему не подвергалось угрозе. Однако в сентябре 2023 года с официальным выпуском Bun 1.0 ситуация, возможно, начала меняться.</p>
<p>Bun — не просто еще одна среда выполнения JavaScript. Это ориентированный на производительность универсальный набор инструментов, разработанный с нуля и призванный охватить весь жизненный цикл от разработки до развертывания.</p>
<h2>2. Основа Bun: скорость, скорость и еще раз скорость</h2>
<p>Главная цель Bun — производительность. Для ее достижения проект сделал несколько необычных технических выборов:</p>
<ul>
<li><strong>Язык Zig</strong>: В основе Bun лежит код на Zig — современном системном языке программирования, ориентированном на производительность и контроль памяти, что позволяет предельно оптимизировать низкоуровневые детали.</li>
<li><strong>Движок JavaScriptCore</strong>: В отличие от Node.js и Deno, использующих движок Google V8, Bun выбрал Apple JavaScriptCore (JSC). JSC известен более быстрым запуском и, как правило, меньшим потреблением памяти.</li>
</ul>
<p>Благодаря этим решениям Bun демонстрирует впечатляющие показатели при запуске скриптов, выполнении <code>bun install</code> и работе встроенных API.</p>
<h2>3. Не только среда выполнения</h2>
<p>Универсальный дизайн Bun — еще одна его важная особенность. Это не просто замена команде <code>node</code>: в него также встроены:</p>
<ul>
<li><strong>Менеджер пакетов</strong>: <code>bun install</code> работает в несколько, а иногда и в десятки раз быстрее <code>npm install</code>. Глобальный кеш модулей и эффективные алгоритмы разрешения зависимостей значительно сокращают время установки.</li>
<li><strong>Инструмент сборки/сборщик</strong>: Bun включает высокопроизводительный сборщик, способный напрямую упаковать проект в исполняемый файл или код для браузера, а его производительность сопоставима с esbuild.</li>
<li><strong>Встроенный транспайлер</strong>: Файлы TypeScript (<code>.ts</code>) и JSX (<code>.jsx</code>, <code>.tsx</code>) можно запускать напрямую, без предварительной настройки <code>tsc</code> или <code>babel</code>. Bun преобразует их во время выполнения с очень высокой скоростью.</li>
<li><strong>Средство запуска тестов</strong>: <code>bun test</code> предоставляет тестовую среду с высокой совместимостью с Jest, но работает гораздо быстрее.</li>
</ul>
<p>Это означает, что для нового проекта может быть достаточно установить единственный инструмент <code>bun</code>, чтобы выполнять весь процесс разработки, тестирования и сборки.</p>
<h2>4. Совместимость с Node.js</h2>
<p>Чтобы упростить миграцию, команда Bun вложила значительные усилия в совместимость с API Node.js. Bun поддерживает разрешение <code>node_modules</code>, модули CommonJS (<code>require</code>) и ESM (<code>import</code>), а также многие основные модули Node.js, такие как <code>fs</code>, <code>path</code> и <code>http</code>.</p>
<p>Теоретически многие существующие проекты Node.js можно запускать непосредственно с помощью <code>bun</code> и сразу получать прирост производительности.</p>
<h2>5. Трудности и будущее</h2>
<p>Появление Bun бросает вызов давней философии фронтенд-сообщества — «комбинировать небольшие специализированные инструменты». Его универсальная модель обеспечивает непревзойденное удобство и высокую производительность из коробки, но может пожертвовать частью гибкости.</p>
<p>Node.js обладает огромной и зрелой экосистемой, до которой Bun вряд ли сможет дорасти в краткосрочной перспективе. Тем не менее выпуск Bun 1.0 показал, что он готов к производственной среде. Его впечатляющая производительность и целостный процесс разработки чрезвычайно привлекательны для новых проектов и команд, стремящихся к максимальной эффективности.</p>
<p>Независимо от того, сможет ли Bun в будущем пошатнуть положение Node.js, его появление уже придало новый импульс развитию инструментов JavaScript и установило новый ориентир производительности.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Нативная вложенность CSS наконец появилась!]]></title>
            <link>https://blog.m1ng.space/ru/posts/css/native-css-nesting/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/css/native-css-nesting/</guid>
            <pubDate>Fri, 01 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Мы ждали десять лет! Вложенность селекторов, одна из ключевых функций Sass/Less, наконец получила нативную поддержку в основных браузерах. Попрощайтесь с препроцессорами и встречайте более ясный и интуитивный способ писать CSS.]]></description>
            <content:encoded><![CDATA[<h2>1. «Неофициальная» функция, которой мы пользовались десять лет</h2>
<p>Если вы писали фронтенд-код несколько лет назад, то наверняка знакомы с Sass или Less. В этих препроцессорах CSS есть функция, которую любит почти каждый разработчик: <strong>вложенность селекторов (Nesting)</strong>. Она позволяет записывать правила дочерних селекторов внутри родительского, подобно разметке HTML, значительно улучшая читаемость и организацию кода.</p>
<pre><code>// Sass 语法
nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  li { display: inline-block; }

  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}
</code></pre>
<p>Много лет мы полагались на инструменты сборки, которые преобразовывали этот синтаксис в обычный CSS, понятный браузерам. Но теперь ситуация изменилась.</p>
<h2>2. Появление нативной вложенности CSS</h2>
<p>С начала 2023 года основные браузеры — Chrome, Safari и Firefox — один за другим объявили о поддержке нативного модуля CSS Nesting. Это означает, что вложенные правила можно писать непосредственно в файлах <code>.css</code>, без какой-либо предварительной обработки.</p>
<p>Приведенный выше пример на нативном CSS выглядит так:</p>
<pre><code>/* 原生 CSS 语法 */
nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  li { display: inline-block; }

  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}
</code></pre>
<p>Да, он выглядит точно так же, как Sass!</p>
<h2>3. Важная роль <code>&amp;</code></h2>
<p>Как и в препроцессорах, символ <code>&amp;</code> играет ключевую роль в нативной вложенности: он обозначает родительский селектор. Это особенно полезно при работе с псевдоклассами, псевдоэлементами и комбинированными селекторами.</p>
<pre><code>.card {
  background: white;
  border-radius: 8px;

  /* &amp; 代表 .card */
  &amp;:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  }

  /* &amp; 代表 .card，组合成 .card.dark-mode */
  &amp;.dark-mode {
    background: #333;
  }

  /* &amp; 代表 .card，组合成 .card &gt; .header */
  &gt; .header {
    font-weight: bold;
  }
}
</code></pre>
<h2>4. Небольшое, но важное отличие от Sass</h2>
<p>В Sass можно напрямую вложить селектор класса, например <code>.card { .title { ... } }</code>. Ранние версии спецификации нативного CSS Nesting этого не позволяли: каждое вложенное правило должно было начинаться с символа, такого как <code>&amp;</code>, <code>&gt;</code>, <code>+</code> или <code>~</code>.</p>
<p>Хотя последняя версия спецификации смягчила это ограничение и разрешила напрямую вкладывать селекторы типа, например <code>article { h1 { ... } }</code>, при вложении селекторов классов по-прежнему рекомендуется использовать <code>&amp;</code>, чтобы явно выразить намерение и избежать неоднозначности.</p>
<pre><code>/* 推荐写法 */
.article {
  &amp; .author-name {
    font-style: italic;
  }
}

/* 不推荐的写法 (在某些早期实现或严格解析器中可能无效) */
.article {
  .author-name {
    font-style: italic;
  }
}
</code></pre>
<p>Использование <code>&amp;</code> ясно показывает, что <code>.author-name</code> является потомком <code>.article</code>.</p>
<h2>5. Поддержка браузерами и будущее</h2>
<p>К сентябрю 2023 года все основные современные браузеры — Chrome 112+, Safari 16.5+ и Firefox 117+ — поддерживали CSS Nesting. Это означает, что его можно постепенно внедрять в производственной среде.</p>
<p>Появление нативной вложенности CSS — еще одна важная веха в развитии самой веб-платформы. Она уменьшает зависимость от инструментов сборки, делает CSS более мощным и выразительным, а также позволяет начинающим фронтенд-разработчикам интуитивнее писать и понимать код стилей.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Расцвет Utility-First CSS на примере Tailwind CSS]]></title>
            <link>https://blog.m1ng.space/ru/posts/css/the-rise-of-utility-first/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/css/the-rise-of-utility-first/</guid>
            <pubDate>Sun, 20 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Можно ли забыть о проблемах BEM и CSS-in-JS? Подход Utility-First CSS и его главный представитель Tailwind CSS в последние годы охватили фронтенд-сообщество. В чём их привлекательность?]]></description>
            <content:encoded><![CDATA[<h2>1. Трудности традиционных методологий CSS</h2>
<p>До появления Utility-First существовало множество отличных методологий организации CSS-стилей, например:</p>
<ul>
<li><strong>BEM (Block, Element, Modifier)</strong>: Использует соглашение об именовании <code>block__element--modifier</code>, чтобы обеспечить независимость и удобство сопровождения стилей.</li>
<li><strong>OOCSS (Object-Oriented CSS)</strong>: Предлагает отделять структуру от оформления, а контейнер — от содержимого.</li>
<li><strong>CSS-in-JS</strong>: Позволяет писать CSS непосредственно внутри JavaScript-компонентов и инкапсулировать стили на уровне компонента.</li>
</ul>
<p>Эти методы в определённой степени решают проблемы глобального загрязнения CSS и организации кода, но часто создают новые: умственные усилия на именование бесчисленных классов, неудобное переключение между файлами и возможные расходы CSS-in-JS во время выполнения.</p>
<h2>2. Что такое Utility-First?</h2>
<p>Utility-First — это подход к CSS, предлагающий набор <strong>неизменяемых утилитарных классов, каждый из которых решает одну задачу</strong>, а сложные интерфейсы строятся путём их сочетания.</p>
<p>Например, вместо того чтобы создавать класс <code>.card</code> и определять его <code>display</code>, <code>padding</code> и <code>box-shadow</code> в CSS-файле,
можно написать прямо в HTML:</p>
<pre><code>&lt;div class="block p-6 rounded-lg shadow-lg bg-white"&gt;
  
&lt;/div&gt;
</code></pre>
<p>Здесь <code>block</code>, <code>p-6</code>, <code>rounded-lg</code> и другие утилитарные классы отвечают каждый только за одну небольшую задачу.</p>
<h2>3. Tailwind CSS — образец Utility-First</h2>
<p>Tailwind CSS — самый популярный на сегодняшний день CSS-фреймворк в стиле Utility-First. Он предоставляет чрезвычайно подробный набор утилитарных классов, охватывающий почти все распространённые свойства CSS.</p>
<p><strong>Основные преимущества:</strong></p>
<ul>
<li><strong>Очень быстрая разработка</strong>: Покидать HTML-файл почти не требуется. Комбинируя атомарные классы, можно быстро собрать практически любой дизайн и значительно ускорить создание прототипов и разработку.</li>
<li><strong>Принудительная дизайн-система</strong>: Поскольку все стили — цвета, отступы и размеры шрифта — берутся из заранее определённой конфигурации (theme), участникам команды легко поддерживать единообразие UI и избегать «магических чисел».</li>
<li><strong>Никаких мучений с именами</strong>: «В информатике есть две трудные задачи: инвалидация кэша и именование». Tailwind почти полностью избавляет от необходимости придумывать имена CSS-классам.</li>
<li><strong>Исключительная производительность</strong>: При производственной сборке Tailwind сканирует файлы и удаляет все неиспользуемые CSS-классы с помощью PurgeCSS (или встроенного JIT-движка), поэтому итоговый CSS-файл обычно получается очень небольшим.</li>
</ul>
<h2>4. «Ад из имён классов»? — Другой взгляд на недостатки</h2>
<p>Самая частая критика Tailwind состоит в том, что HTML становится раздутым и плохо читаемым, словно мы вернулись в эпоху «встроенных стилей».</p>
<pre><code>&lt;button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-blue-500 hover:bg-blue-700"&gt;
  Click me
&lt;/button&gt;
</code></pre>
<p>К этому действительно нужно привыкнуть. Однако сторонники считают, что:</p>
<ol>
<li>Стили и структура и так тесно связаны, поэтому их совместное размещение может даже облегчить сопровождение.</li>
<li>Если выделять повторно используемые компоненты, например компоненты React или Vue, эту «неразбериху» можно инкапсулировать.</li>
</ol>
<p>Для повторно используемых сочетаний стилей Tailwind предоставляет директиву <code>@apply</code>:</p>
<pre><code>/* in your css file */
.btn-primary {
  @apply py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-blue-500 hover:bg-blue-700;
}
</code></pre>
<p>После этого в HTML можно использовать <code>.btn-primary</code>. Однако официальная рекомендация Tailwind — решать задачу повторного использования с помощью компонентов.</p>
<h2>Заключение</h2>
<p>Успех Utility-First и Tailwind CSS отражает изменение представлений о «разделении ответственности» во фронтенд-разработке: от разделения языков (HTML/CSS/JS) к разделению компонентов. На первый взгляд «примитивный» подход решает многие практические проблемы современной веб-разработки и предлагает разработчикам короткий путь к эффективным, единообразным и производительным интерфейсам.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Новое поколение E2E-тестирования: руководство по Playwright]]></title>
            <link>https://blog.m1ng.space/ru/posts/testing/intro-to-playwright/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/testing/intro-to-playwright/</guid>
            <pubDate>Thu, 20 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Устали от нестабильных E2E-тестов? Playwright от Microsoft становится новым ориентиром автоматизации благодаря кроссбраузерности, автоматическому ожиданию и мощным средствам отладки.]]></description>
            <content:encoded><![CDATA[<h2>1. Трудности сквозного тестирования</h2>
<p>Сквозные тесты (End-to-End, E2E) — последняя линия защиты качества приложения. Они имитируют действия реального пользователя — нажатия, ввод и переходы — и проверяют, что весь сценарий приложения работает без сбоев. Однако E2E-тесты известны своей «хрупкостью» и «нестабильностью»:</p>
<ul>
<li><strong>Асинхронность</strong>: во время выполнения сценария элементы DOM могут ещё не загрузиться, из-за чего тест завершается ошибкой. Разработчикам приходится вручную добавлять множество <code>wait</code> и <code>sleep</code>, а тесты становятся ненадёжными.</li>
<li><strong>Кроссбраузерная совместимость</strong>: добиться стабильного выполнения тестов во всех основных браузерах — Chrome, Firefox и Safari — очень трудно.</li>
<li><strong>Сложная отладка</strong>: когда тест падает в среде CI/CD, воспроизвести и найти проблему сложно из-за отсутствия снимков состояния и сетевых журналов.</li>
</ul>
<p>Для решения этих проблем появилось новое поколение инструментов тестирования, и Playwright — один из его лучших представителей.</p>
<h2>2. Что такое Playwright?</h2>
<p>Playwright — разработанная Microsoft библиотека Node.js для автоматизации браузеров Chromium (Chrome и Edge), Firefox и WebKit (Safari). Её создали ключевые участники команды, которая ранее разработала Google Puppeteer. Playwright можно считать духовным наследником Puppeteer, но с более современным и мощным устройством.</p>
<h2>3. Основные преимущества Playwright</h2>
<h4>a. Настоящее кроссбраузерное тестирование</h4>
<p>Это самое заметное преимущество Playwright. Единый API позволяет писать тесты для всех трёх основных браузерных движков. Благодаря этому легко находить и исправлять ошибки, которые проявляются только в определённом браузере, например Safari.</p>
<h4>b. Автоматическое ожидание (Auto-Waits)</h4>
<p>Playwright решает проблему асинхронности на фундаментальном уровне. Перед выполнением действия, например <code>page.click()</code>, он автоматически проводит ряд проверок доступности элемента для взаимодействия:</p>
<ul>
<li>Ждёт появления элемента в DOM.</li>
<li>Ждёт, пока элемент станет видимым.</li>
<li>Ждёт, пока анимация перестанет перекрывать элемент.</li>
<li>Ждёт, пока элемент сможет принимать события.</li>
</ul>
<p>Поэтому вручную писать код ожидания почти не требуется, а стабильность тестов значительно возрастает.</p>
<h4>c. Мощные сопутствующие инструменты</h4>
<ul>
<li>
<p><strong>Codegen (генератор кода)</strong>: революционная возможность. Можно выполнить <code>pnpm exec playwright codegen example.com</code>, и Playwright откроет окно браузера. Все действия в нём будут автоматически записаны и преобразованы в код теста. Это значительно снижает порог входа в E2E-тестирование.</p>
</li>
<li>
<p><strong>Trace Viewer (просмотр трассировки)</strong>: «машина времени» Playwright. При падении теста можно создать полный файл трассировки. Открыв его в Trace Viewer, можно:</p>
<ul>
<li>Просмотреть снимок DOM на каждом шаге теста.</li>
<li>Просмотреть полный журнал сетевых запросов.</li>
<li>Просмотреть сообщения и ошибки console.</li>
<li>Перемещаться по временной шкале и наглядно видеть, как страница менялась до и после каждого действия.</li>
</ul>
</li>
</ul>
<p>Благодаря этому отлаживать падения в CI стало проще, чем когда-либо.</p>
<h2>4. Простой пример теста</h2>
<p>API Playwright устроен очень понятно.</p>
<pre><code>import { test, expect } from '@playwright/test';

test('页面应有正确的标题', async ({ page }) =&gt; {
  await page.goto('https://playwright.dev/');

  // 断言页面的 title 包含 "Playwright"
  await expect(page).toHaveTitle(/Playwright/);
});

test('点击 "Get started" 链接应跳转到介绍页', async ({ page }) =&gt; {
  await page.goto('https://playwright.dev/');

  // 通过角色和名称定位并点击链接
  await page.getByRole('link', { name: 'Get started' }).click();

  // 断言 URL 包含 "intro"
  await expect(page).toHaveURL(/.*intro/);
});
</code></pre>
<h2>Заключение</h2>
<p>Благодаря превосходной кроссбраузерной поддержке, надёжному автоматическому ожиданию и непревзойдённым средствам отладки — особенно Trace Viewer — Playwright устанавливает новый стандарт E2E-тестирования современных Web-приложений. Он не только повышает стабильность и надёжность тестов, но и значительно улучшает процесс их написания с помощью таких средств, как Codegen. Если вашему проекту нужен современный и мощный инструмент автоматизации тестирования, Playwright определённо заслуживает места среди основных кандидатов.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Signals: новая волна реактивности во фронтенде]]></title>
            <link>https://blog.m1ng.space/ru/posts/frameworks/the-rise-of-signals/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/frameworks/the-rise-of-signals/</guid>
            <pubDate>Sat, 20 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[От SolidJS и Qwik до Preact и Svelte 5 — Signals становятся центральной парадигмой высокопроизводительных реактивных обновлений в современных фронтенд-фреймворках. В статье подробно рассматриваются принципы их работы и влияние на экосистему.]]></description>
            <content:encoded><![CDATA[<h2>1. Эволюция реактивных моделей</h2>
<p>Суть фронтенд-разработки заключается в том, чтобы «отобразить состояние в пользовательский интерфейс». За многие годы мы стали свидетелями непрерывной эволюции реактивных моделей: от ручных операций с DOM через модели MVC/MVVM к виртуальному DOM (Virtual DOM), популяризированному React. Благодаря пакетной обработке изменений состояния и сравнению на уровне компонентов VDOM значительно упростил разработку интерфейсов.</p>
<p>Однако VDOM — не универсальное решение. Затраты на повторный рендеринг и сравнение на уровне компонентов в некоторых высокодинамичных сценариях могут стать узким местом производительности. В последние годы более старая парадигма, оптимизированная современными компиляторами, — мелкогранулярная реактивность (Fine-Grained Reactivity) — вновь оказалась в центре внимания, а её основным носителем стали <strong>Signals</strong>.</p>
<h2>2. Что такое Signals?</h2>
<p>Signal — это реактивный примитив (Reactive Primitive), который инкапсулирует значение и автоматически уведомляет все его зависимости при изменении этого значения. В отличие от VDOM-фреймворков, отслеживающих изменения на уровне компонентов, Signal автоматически создаёт граф зависимостей на уровне чтения данных.</p>
<p>При обновлении значения Signal повторно выполняются только те вычисления (<code>Memo</code>) или побочные эффекты (<code>Effect</code>), которые прямо или косвенно зависят от этого Signal. Так достигается точечное, «хирургическое» обновление DOM без необходимости сравнивать VDOM.</p>
<p>Обычно его основу составляют три примитива:</p>
<ul>
<li><strong><code>createSignal(value)</code></strong>: Создаёт реактивную единицу состояния.</li>
<li><strong><code>createEffect(() =&gt; {})</code></strong>: Создаёт побочный эффект, который автоматически отслеживает зависимости и реагирует на изменения.</li>
<li><strong><code>createMemo(() =&gt; {})</code></strong>: Создаёт производное кэшируемое реактивное вычисление.</li>
</ul>
<pre><code>// SolidJS 语法示例
const [count, setCount] = createSignal(0);

const doubleCount = createMemo(() =&gt; count() * 2);

createEffect(() =&gt; {
  console.log(`The double count is: ${doubleCount()}`);
});

setCount(1); // 这将触发 memo 重新计算，并触发 effect 重新执行
</code></pre>
<p>Важная ментальная модель состоит в том, что во фреймворках на основе Signal, таких как SolidJS, сама функция компонента выполняется лишь один раз при инициализации. Все последующие обновления полностью управляются реактивной системой, а не повторным рендерингом компонента.</p>
<h2>3. Широкое распространение в экосистеме</h2>
<p>Signal — не совершенно новая концепция: её идеи восходят к ранним фреймворкам вроде Knockout.js. Однако современные компиляторы JavaScript дали ей новую жизнь, и сегодня её широко применяют крупные фреймворки:</p>
<ul>
<li><strong>SolidJS &amp; Qwik</strong>: Полностью построены на Signal и служат эталонными реализациями этой парадигмы.</li>
<li><strong>Preact</strong>: Добавил официальную поддержку через пакет <code>@preact/signals</code>, который можно интегрировать в существующие проекты Preact/React.</li>
<li><strong>Vue</strong>: <code>ref</code> и <code>computed</code> из Composition API, по сути, представляют собой одну из реализаций Signal.</li>
<li><strong>Svelte 5</strong>: Грядущее обновление «Runes» перестраивает реактивную модель Svelte, опираясь в своей основе на идеи и реализацию парадигмы Signal.</li>
<li><strong>Angular</strong>: В недавних версиях также появились Signals как новые реактивные примитивы.</li>
</ul>
<p>Такое сближение разных фреймворков доказывает ценность Signal как эффективной и предсказуемой реактивной модели.</p>
<h2>4. Преимущества и компромиссы</h2>
<p><strong>Преимущества:</strong></p>
<ol>
<li><strong>Выдающаяся производительность</strong>: Отказ от VDOM и повторного рендеринга на уровне компонентов обычно обеспечивает отличные результаты в тестах производительности.</li>
<li><strong>Предсказуемость</strong>: Поток обновлений состояния прозрачен, его легко понимать и отлаживать.</li>
<li><strong>Низкое потребление памяти</strong>: Не требуется хранить виртуальное представление всего дерева компонентов.</li>
</ol>
<p><strong>Компромиссы:</strong></p>
<ol>
<li><strong>Смена ментальной модели</strong>: Разработчикам, привыкшим к циклу рендеринга React, нужно освоить новую модель, в которой «компонент выполняется только один раз».</li>
<li><strong>Интеграция с экосистемой</strong>: Хотя Signals сами по себе обладают высокой производительностью, глубокая интеграция с обширной VDOM-экосистемой React — например, с некоторыми UI-библиотеками — может потребовать дополнительной адаптации.</li>
</ol>
<h2>Заключение</h2>
<p>Возрождение Signals знаменует важный этап эволюции парадигм управления состоянием во фронтенде. Оно возвращает внимание разработчиков от вопроса «как рендерится компонент» к более фундаментальному вопросу «как движется состояние». Благодаря глубокой интеграции с компиляторами Signal обеспечивает отличный опыт разработки и одновременно открывает новые возможности на границах производительности Web-приложений.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Прощай, документация API: сквозная типобезопасность приложений с tRPC]]></title>
            <link>https://blog.m1ng.space/ru/posts/typescript/typesafe-apis-with-trpc/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/typescript/typesafe-apis-with-trpc/</guid>
            <pubDate>Wed, 15 Mar 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Как синхронизировать контракт данных фронтенда и бэкенда в full-stack-проекте на TypeScript? Без генерации кода и промежуточных схем tRPC обеспечивает настоящую сквозную типобезопасность.]]></description>
            <content:encoded><![CDATA[<h2>1. Проблема «контракта» между фронтендом и бэкендом</h2>
<p>При традиционной раздельной разработке фронтенда и бэкенда API служит «контрактом» между ними. Обычно этот контракт поддерживают следующими способами:</p>
<ul>
<li><strong>RESTful API</strong>: с помощью таких инструментов, как OpenAPI (Swagger), которые создают и поддерживают подробную документацию API.</li>
<li><strong>GraphQL</strong>: с помощью строгого Schema Definition Language (SDL), определяющего структуры данных и операции.</li>
</ul>
<p>Эти подходы работают, но у них есть общая проблема: <strong>контракт отделён от реализации</strong>. Вызывая API, фронтенд-разработчик верит, что тот вернёт структуру данных, описанную в документации или схеме. Если реализация бэкенда изменилась — например, было переименовано поле, — а документация или схема не обновились вовремя, несоответствие проявится только во время выполнения и приведёт к ошибке.</p>
<p>Можно ли автоматически синхронизировать этот «контракт» с реализацией и даже выявлять несоответствия во время компиляции?</p>
<h2>2. Главная идея tRPC: общие типы вместо схем</h2>
<p>tRPC (TypeScript Remote Procedure Call) предлагает радикальное, но крайне простое решение: <strong>если и фронтенд, и бэкенд написаны на TypeScript, почему бы не использовать типы напрямую?</strong></p>
<p>tRPC позволяет писать обычные функции TypeScript в качестве API бэкенда и непосредственно вызывать их во фронтенде с полной поддержкой вывода типов и автодополнения — словно это функции локального модуля.</p>
<p>Ни схемы, ни генерация кода не требуются. Единственный «контракт» — сами типы TypeScript.</p>
<h2>3. Как это работает?</h2>
<p>Магия tRPC основана на выводе типов и небольшой хитроумной обёртке.</p>
<h4>a. Бэкенд: определяем API Router</h4>
<p>На бэкенде — обычно в сервисе Node.js — с помощью функций tRPC создаётся один или несколько «Router». Каждый Router представляет собой набор доступных для вызова «Procedure», то есть конечных точек API.</p>
<pre><code>// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod'; // 使用 Zod 进行运行时校验

const t = initTRPC.create();

export const appRouter = t.router({
  // 定义一个名为 `getUser` 的查询 procedure
  getUser: t.procedure
    .input(z.object({ userId: z.string() }))
    .query(({ input }) =&gt; {
      // 在这里查询数据库或执行其他逻辑
      const user = { id: input.userId, name: 'Alex' };
      return user;
    }),

  // 定义一个名为 `createUser` 的变更 procedure
  createUser: t.procedure
    .input(z.object({ name: z.string() }))
    .mutation(({ input }) =&gt; {
      const user = { id: `${Math.random()}`, name: input.name };
      return user;
    }),
});

// 导出 router 的类型定义
export type AppRouter = typeof appRouter;
</code></pre>
<h4>b. Фронтенд: создаём клиент и вызываем API</h4>
<p>Во фронтенде достаточно импортировать из бэкенда <code>AppRouter</code> как <strong>тип</strong>. Обратите внимание: импортируется только тип, а не серверный код.</p>
<pre><code>// client/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server/router'; // 只导入类型

export const trpc = createTRPCReact&lt;AppRouter&gt;();
</code></pre>
<p>Теперь API можно вызывать из компонента React словно локальную функцию, пользуясь полной типобезопасностью и автодополнением.</p>
<pre><code>// client/components/UserInfo.tsx
import { trpc } from '../trpc';

function UserInfo({ userId }: { userId: string }) {
  // `useQuery` 的第一个参数是 procedure 的路径
  // 你输入 `trpc.` 时，IDE 会自动提示 `getUser` 和 `createUser`
  const userQuery = trpc.getUser.useQuery({ userId });

  if (userQuery.isLoading) {
    return &lt;div&gt;Loading...&lt;/div&gt;;
  }

  // `userQuery.data` 的类型被自动推断为 { id: string; name: string }
  return &lt;div&gt;User: {userQuery.data?.name}&lt;/div&gt;;
}
</code></pre>
<p>Если теперь разработчик бэкенда переименует поле, возвращаемое <code>getUser</code>, с <code>name</code> на <code>fullName</code>, выражение <code>userQuery.data?.name</code> во фронтенде немедленно вызовет ошибку компиляции TypeScript, а не обнаружится лишь во время выполнения.</p>
<h2>4. Почему стоит выбрать tRPC?</h2>
<ul>
<li><strong>Абсолютная сквозная типобезопасность</strong>: в этом заключается его основная ценность. Устраняется целый класс ошибок, вызванных несовпадением контрактов API.</li>
<li><strong>Превосходный процесс разработки</strong>: благодаря автодополнению IDE больше не нужно сверяться с документацией или угадывать структуру API. Рефакторинг становится исключительно простым и безопасным.</li>
<li><strong>Без генерации кода</strong>: дополнительных этапов сборки нет, поэтому обратная связь поступает очень быстро.</li>
<li><strong>Лёгкость и гибкость</strong>: сам tRPC очень мал и может интегрироваться с любым фронтенд-фреймворком и бэкенд-сервисом.</li>
</ul>
<h2>Заключение</h2>
<p>tRPC делает full-stack-разработку на TypeScript беспрецедентно плавной. Отказ от зависимости от документации API и схем обеспечивает бесшовное и крайне безопасное взаимодействие фронтенда с бэкендом. В архитектуре монорепозитория эти преимущества становятся ещё заметнее. Если вы создаёте full-stack-приложение на TypeScript, tRPC — революционный инструмент, которому определённо стоит уделить время.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Знакомство с Astro: создан для сайтов, ориентированных на контент]]></title>
            <link>https://blog.m1ng.space/ru/posts/astro/exploring-astro-for-content-sites/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/astro/exploring-astro-for-content-sites/</guid>
            <pubDate>Tue, 10 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[В эпоху повсеместных одностраничных приложений (SPA) Astro идет другим путем и сосредоточен на максимальной производительности контентных сайтов. В чем сила его «островной архитектуры»?]]></description>
            <content:encoded><![CDATA[<h2>1. Действительно ли нам нужно столько JavaScript?</h2>
<p>Современные фронтенд-фреймворки, такие как React, Vue и Svelte, значительно улучшили процесс разработки сложных веб-приложений. Но когда мы используем их для создания блогов, портфолио, документации или маркетинговых сайтов, возникает вопрос: действительно ли сайтам, предназначенным прежде всего для представления контента, нужно загружать всю страницу как одностраничное приложение (SPA)?</p>
<p>Обычно ответ — «нет». Для таких сайтов главная потребность пользователя заключается в быстром доступе к контенту. Избыток JavaScript, напротив, замедляет время до интерактивности (TTI), ухудшая пользовательский опыт и SEO.</p>
<p>Astro появился именно для решения этой проблемы.</p>
<h2>2. Основная идея Astro: сначала контент, по умолчанию без JS</h2>
<p>Astro — современный генератор статических сайтов (SSG), основная идея которого заключается в подходе <strong>сначала контент</strong>. Он стремится выполнить как можно больше работы во время сборки и отправить в браузер как можно меньше JavaScript.</p>
<p>Для этого Astro предложил два ключевых нововведения:</p>
<ul>
<li><strong>По умолчанию без JavaScript (Zero-JS by Default)</strong>: Astro во время сборки преобразует все ваши UI-компоненты, независимо от того, написаны они на React, Vue или Svelte, в чистый HTML. Поэтому браузер получает статическую страницу и не должен загружать JS-среду какого-либо фреймворка, что обеспечивает очень быструю загрузку.</li>
<li><strong>Островная архитектура (Islands Architecture)</strong>: В этом заключается суть Astro. В «океане» статического HTML любой компонент, которому нужно взаимодействие на стороне клиента, можно отметить как «остров». Astro отдельно упаковывает и загружает JavaScript, необходимый этим островам, а остальная часть страницы остается полностью статической.</li>
</ul>
<h2>3. Как работают «острова»?</h2>
<p>В Astro компонент можно отметить как интерактивный остров, добавив директиву <code>client:*</code>.</p>
<p>Например, предположим, что у нас есть написанный на React компонент счетчика <code>Counter.jsx</code>. На странице Astro его можно использовать так:</p>
<pre><code>---
// src/pages/index.astro
import Counter from '../components/Counter.jsx';
---
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;Astro 示例&lt;/h1&gt;
    &lt;p&gt;这是一个静态的段落，它不会加载任何 JS。&lt;/p&gt;

    {/* 这个组件是一个交互式岛屿 */}
    &lt;Counter client:load /&gt;

    &lt;p&gt;这是另一个静态段落。&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Директива <code>client:load</code> сообщает Astro:</p>
<ol>
<li>Отобразить начальный HTML компонента <code>Counter</code> на сервере.</li>
<li>Создать отдельный JS-пакет для компонента <code>Counter</code>.</li>
<li>Автоматически загрузить этот JS-пакет при загрузке страницы и «активировать» (hydrate) компонент, сделав его интерактивным.</li>
</ol>
<p>Astro также предлагает более точные директивы загрузки, например <code>client:idle</code> (загрузка во время простоя браузера) и <code>client:visible</code> (загрузка, когда компонент появляется в области просмотра), позволяя еще лучше оптимизировать производительность.</p>
<h2>4. Независимость от фреймворка</h2>
<p>Еще одно важное достоинство Astro — его открытость к UI-фреймворкам. В одном проекте Astro можно одновременно использовать компоненты из разных фреймворков, таких как React, Vue, Svelte, SolidJS и Lit. Это дает командам огромную гибкость в совместной работе и выборе технологий.</p>
<h2>Заключение</h2>
<p>Astro не стремится заменить React или Vue, а предлагает более подходящее решение для определенных сценариев. Если ваш проект — сайт, сосредоточенный на контенте и предъявляющий очень высокие требования к производительности и SEO (например, сам этот блог построен на Astro), то Astro, несомненно, является отличным выбором, который стоит изучить глубже. Он прекрасно сочетает преимущества производительности статических сайтов с удобством разработки на современных фреймворках.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Официальный выпуск SvelteKit 1.0: новый способ создания Web-приложений]]></title>
            <link>https://blog.m1ng.space/ru/posts/svelte/sveltekit-1-0-release/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/svelte/sveltekit-1-0-release/</guid>
            <pubDate>Thu, 01 Dec 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[После более чем двух лет разработки SvelteKit 1.0 наконец вышел в декабре 2022 года. Его называют будущим создания Web-приложений любого масштаба; познакомимся с ключевыми возможностями и философией проектирования.]]></description>
            <content:encoded><![CDATA[<h2>SvelteKit 1.0 уже здесь!</h2>
<p>В декабре 2022 года команда Svelte официально объявила о выпуске SvelteKit 1.0. Это стало важной вехой не только для сообщества Svelte, но и для всей фронтенд-разработки. SvelteKit больше нельзя считать всего лишь «Next.js или Nuxt.js для Svelte». Он обладает собственной уникальной философией и стремится предложить более простой, гибкий и производительный способ создания Web-приложений.</p>
<h2>Что такое SvelteKit?</h2>
<p>Если вы знакомы со Svelte, то, вероятно, знаете, что сам Svelte — это <strong>компонентный фреймворк</strong>. Его радикальный компилятор во время сборки преобразует файлы <code>.svelte</code> в эффективный императивный JavaScript-код, обеспечивая впечатляющую производительность и чрезвычайно малые накладные расходы во время выполнения.</p>
<p>SvelteKit же представляет собой <strong>фреймворк приложений</strong>, построенный поверх Svelte. Он берёт на себя всё необходимое для создания полноценного приложения: маршрутизацию, серверный рендеринг (SSR), загрузку данных, адаптацию к среде развёртывания и многое другое, позволяя сосредоточиться на бизнес-логике.</p>
<h2>Обзор основных возможностей</h2>
<h4>1. Маршрутизация на основе файловой системы</h4>
<p>Подобно Next.js, SvelteKit автоматически создаёт маршруты на основе структуры файловой системы. Структура файлов в каталоге <code>src/routes</code> напрямую соответствует URL приложения.</p>
<ul>
<li><code>src/routes/+page.svelte</code> -&gt; <code>/</code></li>
<li><code>src/routes/about/+page.svelte</code> -&gt; <code>/about</code></li>
<li><code>src/routes/blog/[slug]/+page.svelte</code> -&gt; <code>/blog/some-post</code></li>
</ul>
<p>Такой подход «соглашения важнее конфигурации» значительно упрощает управление маршрутами.</p>
<h4>2. Гибкие режимы рендеринга</h4>
<p>SvelteKit позволяет точно управлять способом рендеринга на уровне отдельных страниц. В одном приложении можно реализовать серверный рендеринг (SSR), генерацию статического сайта (SSG) или сочетание обоих способов. Можно даже отключить SSR для отдельной страницы, превратив её в полностью клиентское одностраничное приложение (SPA).</p>
<h4>3. Универсальная функция <code>load</code></h4>
<p>SvelteKit унифицирует модель загрузки данных. Рядом с каждой страницей или макетом можно создать файл <code>+page.js</code> либо <code>+page.server.js</code> и экспортировать из него функцию <code>load</code>.</p>
<ul>
<li>В <code>+page.js</code> функция <code>load</code> выполняется и на сервере, и на клиенте.</li>
<li>В <code>+page.server.js</code> функция <code>load</code> выполняется <strong>только на сервере</strong>, поэтому там можно безопасно обращаться к базе данных или закрытому API.</li>
</ul>
<p>Данные, возвращённые функцией <code>load</code>, автоматически передаются соответствующему компоненту <code>+page.svelte</code>.</p>
<pre><code>// src/routes/blog/[slug]/+page.server.js
import * as db from '$lib/server/database';

export async function load({ params }) {
  const post = await db.getPost(params.slug);
  return { post };
}
</code></pre>
<h4>4. Адаптеры</h4>
<p>«Написать один раз, развернуть где угодно» — одно из главных обещаний SvelteKit. С помощью <strong>адаптеров</strong> SvelteKit может упаковать приложение в формате, подходящем для любой целевой платформы.</p>
<ul>
<li><code>@sveltejs/adapter-node</code>: Для развёртывания на традиционном сервере Node.js.</li>
<li><code>@sveltejs/adapter-vercel</code>: Адаптация к платформе Vercel.</li>
<li><code>@sveltejs/adapter-static</code>: Предварительный рендеринг всего приложения в статические файлы, подходящие для любого статического хостинга.</li>
<li>Существуют и другие официальные и созданные сообществом адаптеры для Netlify, Cloudflare Workers и прочих платформ.</li>
</ul>
<h2>Заключение</h2>
<p>Выпуск SvelteKit 1.0 ознаменовал зрелость экосистемы Svelte. Он объединяет исключительную производительность компилятора Svelte с тщательно продуманным фреймворком приложений и даёт разработчикам мощный и приятный опыт работы. Если вам нужен универсальный фреймворк, способный создавать как простые статические страницы, так и сложные динамические приложения, SvelteKit определённо стоит попробовать.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Эволюция управления состоянием в React: от Props Drilling до Zustand]]></title>
            <link>https://blog.m1ng.space/ru/posts/react/state-management-evolution/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/react/state-management-evolution/</guid>
            <pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Управление состоянием в React прошло путь от раннего «протаскивания props» через единый подход Redux к современному многообразию лёгких библиотек вроде Zustand. В статье рассматривается эта увлекательная история.]]></description>
            <content:encoded><![CDATA[<h2>Введение</h2>
<p>«Как элегантно управлять состоянием?» Это ключевой вопрос, с которым по мере роста проекта сталкивается каждый React-разработчик. От простого состояния внутри компонента до сложного глобального состояния, общего для многих компонентов, — сообщество React изучило множество решений. Их эволюция также отражает то, как углублялось наше понимание компонентной разработки.</p>
<h2>Первый этап: простая эпоха — <code>useState</code> и Props Drilling</h2>
<p>Вначале в нашем распоряжении был только <code>useState</code> (или <code>this.state</code> в классовых компонентах). Когда одно состояние требовалось нескольким компонентам, официальной рекомендацией React было <strong>«поднятие состояния» (Lifting State Up)</strong>. Состояние переносили к ближайшему общему родительскому компоненту, а затем передавали его и функцию обновления вниз через props.</p>
<p>При глубокой иерархии компонентов этот подход приводил к <strong>«протаскиванию props» (Props Drilling)</strong>: некоторые промежуточные компоненты получали props лишь для передачи их более глубоким потомкам, не используя эти данные сами. Это усиливало связанность компонентов и усложняло рефакторинг и сопровождение.</p>
<h2>Второй этап: официальный ответ — Context API</h2>
<p>Для решения проблемы Props Drilling React официально предоставил Context API. Он позволяет создать «контекст», предоставить (Provide) значение на верхнем уровне дерева компонентов, а затем напрямую использовать (Consume) это значение в дочернем компоненте на любой глубине без ручной передачи через каждый уровень.</p>
<pre><code>// 1. 创建 Context
const ThemeContext = React.createContext('light');

// 2. 在顶层提供值
&lt;ThemeContext.Provider value="dark"&gt;
  &lt;App /&gt;
&lt;/ThemeContext.Provider&gt;

// 3. 在子组件中消费
const theme = useContext(ThemeContext); // 'dark'
</code></pre>
<p>Однако у Context API есть «ловушка»: <strong>проблемы с производительностью</strong>. При любом изменении <code>Provider</code>, а именно его <code>value</code>, повторно рендерятся <strong>все</strong> компоненты, использующие этот Context, даже если им нужна лишь небольшая часть объекта <code>value</code>. Поэтому Context API не подходит для управления сложным глобальным состоянием, которое часто меняется.</p>
<h2>Третий этап: эпоха объединения — Redux</h2>
<p>Когда Context API ещё не достиг зрелости, стремительно появился Redux и быстро стал фактическим стандартом управления состоянием в больших и сложных приложениях. Заимствуя идеи архитектуры Flux и функционального программирования, он принёс следующие принципы:</p>
<ul>
<li><strong>Единственный источник истины (Single Source of Truth)</strong>: State всего приложения хранится в едином store.</li>
<li><strong>State доступен только для чтения</strong>: Единственный способ изменить state — выполнить dispatch для action.</li>
<li><strong>Изменения выполняются чистыми функциями</strong>: Reducer получает предыдущий state и action, а возвращает новый state.</li>
</ul>
<p>Предсказуемость, мощные средства отладки (путешествия во времени) и богатая экосистема промежуточного ПО позволили Redux решить задачи управления состоянием в больших приложениях. Но его недостаток был столь же очевиден: громоздкий <strong>шаблонный код (Boilerplate)</strong>. Для реализации простой функции разработчикам приходилось писать Actions, Reducers и Dispatchers, что создавало немалую когнитивную нагрузку.</p>
<h2>Четвёртый этап: возрождение — лёгкие решения в стиле Hooks-First</h2>
<p>С распространением React Hooks и переосмыслением сложности Redux в сообществе стало появляться новое поколение более лёгких библиотек управления состоянием. Их объединяли лаконичные API, простые ментальные модели и полноценное использование возможностей Hooks.</p>
<p><strong>Zustand</strong> — один из ярких представителей. Он предоставляет чрезвычайно простую функцию <code>create</code> для создания store, а также:</p>
<ul>
<li><strong>Не требует Provider</strong>: Store существует вне дерева компонентов React, поэтому его можно импортировать и использовать где угодно.</li>
<li><strong>Минимальный API</strong>: Для доступа к состоянию и его обновления достаточно одного Hook.</li>
<li><strong>Избирательные подписки и лучшая производительность</strong>: Компонент может подписаться только на нужную ему часть state, избегая проблем производительности Context API.</li>
</ul>
<pre><code>const useStore = create(set =&gt; ({
  count: 0,
  inc: () =&gt; set(state =&gt; ({ count: state.count + 1 })),
}));

function Counter() {
  // 只订阅 count 的变化
  const count = useStore(state =&gt; state.count);
  return &lt;h1&gt;{count}&lt;/h1&gt;;
}
</code></pre>
<h2>Заключение: серебряной пули нет — выбирайте по потребностям</h2>
<p>История развития управления состоянием в React показывает, что универсального «лучшего решения» не существует: есть лишь решение, которое лучше всего подходит для текущей ситуации.</p>
<ul>
<li><strong><code>useState</code></strong>: Всегда первый выбор для локального состояния компонента.</li>
<li><strong>Context API</strong>: Подходит для глобальных данных, которые меняются нечасто, например темы или сведений об аутентификации пользователя.</li>
<li><strong>Zustand / Jotai</strong>: Для большинства приложений, которым требуется глобальное клиентское состояние, они обеспечивают превосходный баланс между производительностью и удобством разработки.</li>
<li><strong>Redux (Redux Toolkit)</strong>: По-прежнему надёжный выбор для сверхкрупных приложений, которым нужны строгие правила потока данных, сложное промежуточное ПО и мощные возможности отладки.</li>
</ul>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[От Webpack к Vite: путь плавной миграции]]></title>
            <link>https://blog.m1ng.space/ru/posts/build-tools/migrating-from-webpack-to-vite/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/build-tools/migrating-from-webpack-to-vite/</guid>
            <pubDate>Mon, 05 Sep 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Ваш проект на Webpack запускается минуту, а горячее обновление занимает больше десяти секунд? Пора переходить на новое поколение инструментов фронтенд-сборки. В статье описаны процесс и выводы миграции с Webpack на Vite.]]></description>
            <content:encoded><![CDATA[<h2>1. Зачем мигрировать? Проблемы Webpack</h2>
<p>Webpack — чрезвычайно мощный и гибко настраиваемый сборщик модулей, который много лет оставался основой фронтенд-проектов. Но по мере роста проектов его основная идея, основанная на сборке, создает узкие места производительности:</p>
<ul>
<li><strong>Медленный холодный запуск</strong>: При каждом запуске сервера разработки (<code>dev-server</code>) Webpack должен обойти весь граф зависимостей и собрать все модули в памяти. В крупных проектах этот процесс может занимать несколько минут.</li>
<li><strong>Медленное горячее обновление (HMR)</strong>: При изменении файла Webpack должен заново вычислить и заменить связанные модули. Это быстрее полной перезагрузки страницы, но в крупных проектах задержка может достигать нескольких или даже более десяти секунд, прерывая рабочий ритм.</li>
</ul>
<h2>2. Как Vite решает эти проблемы?</h2>
<p>Vite, название которого по-французски означает «быстро», идет другим путем. Он использует поддержку нативных модулей ES (ESM) современными браузерами и делит процесс сборки на две части:</p>
<ul>
<li><strong>Во время разработки</strong>: Vite запускает сервер, но не собирает заранее все модули. Вместо этого он перехватывает запросы модулей от браузера, преобразуя и отдавая исходный код по требованию. Например, браузер запрашивает <code>main.js</code>, и Vite отдает его; <code>main.js</code> импортирует <code>Button.vue</code>, браузер отправляет новый запрос, и Vite отдает преобразованный <code>Button.vue</code>. Благодаря этому сервер разработки запускается почти мгновенно.</li>
<li><strong>В производственной среде</strong>: Vite использует Rollup, еще один эффективный сборщик, чтобы создавать хорошо оптимизированные статические ресурсы.</li>
</ul>
<p>Эта модель полностью меняет процесс разработки, обеспечивая молниеносный запуск и горячее обновление за миллисекунды.</p>
<h2>3. Практические шаги миграции</h2>
<p>Миграция существующего проекта с Webpack на Vite обычно включает следующие шаги:</p>
<h4>a. Установить зависимости и создать файл конфигурации</h4>
<p>Сначала установите Vite:</p>
<pre><code>pnpm add -D vite @vitejs/plugin-react # 或 @vitejs/plugin-vue
</code></pre>
<p>Затем создайте <code>vite.config.js</code> в корне проекта:</p>
<pre><code>import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [react()],
})
</code></pre>
<h4>b. Переместить <code>index.html</code></h4>
<p>Webpack обычно размещает <code>index.html</code> в каталоге <code>public</code> и автоматически добавляет собранный JS. Vite считает <code>index.html</code> точкой входа приложения. Нужно переместить его в корень проекта и вручную добавить ссылку на скрипт:</p>
<pre><code>
&lt;body&gt;
  &lt;div id="root"&gt;&lt;/div&gt;
  &lt;script type="module" src="/src/main.jsx"&gt;&lt;/script&gt;
&lt;/body&gt;
</code></pre>
<h4>c. Заменить плагины Webpack</h4>
<p>Нужно найти плагины Vite, соответствующие Loaders и Plugins из Webpack. Экосистема сообщества очень богата, и решение найдется для большинства задач.</p>
<table>
<thead>
<tr>
<th>Webpack</th>
<th>Vite</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>babel-loader</code></td>
<td><code>@vitejs/plugin-react</code> (Babel встроен)</td>
</tr>
<tr>
<td><code>vue-loader</code></td>
<td><code>@vitejs/plugin-vue</code></td>
</tr>
<tr>
<td><code>file-loader</code></td>
<td>Встроено в Vite</td>
</tr>
<tr>
<td><code>webpack-dev-server</code></td>
<td>Сервер разработки встроен в Vite</td>
</tr>
</tbody>
</table>
<h4>d. Обработать переменные окружения</h4>
<p>В Webpack мы привыкли использовать <code>process.env.NODE_ENV</code>. В Vite доступ к переменным окружения осуществляется через <code>import.meta.env</code>, например <code>import.meta.env.VITE_API_URL</code>.</p>
<h4>e. Настроить псевдонимы путей</h4>
<p>Псевдонимы путей в <code>vite.config.js</code> настраиваются очень просто:</p>
<pre><code>import path from 'node:path'
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  // ...
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})
</code></pre>
<h2>4. Результаты после миграции</h2>
<p>После миграции на Vite самое заметное впечатление — <strong>скорость</strong>.</p>
<ul>
<li>Время запуска сервера разработки сократилось с <strong>50 секунд</strong> до <strong>2 секунд</strong>.</li>
<li>Время горячего обновления уменьшилось со <strong>средних 3–5 секунд</strong> до <strong>почти незаметных десятков миллисекунд</strong>.</li>
<li>Файл конфигурации (<code>vite.config.js</code>) стал значительно проще, чем <code>webpack.config.js</code>.</li>
</ul>
<h2>Заключение</h2>
<p>Хотя во время миграции может потребоваться решить некоторые проблемы конфигурации, характерные для конкретного проекта, переход с Webpack на Vite значительно улучшает процесс разработки. Если вы все еще терпите медленную сборку, сейчас самое подходящее время перейти на Vite.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[CSS-in-JS без runtime: удобство разработки и максимальная производительность]]></title>
            <link>https://blog.m1ng.space/ru/posts/css/zero-runtime-css-in-js/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/css/zero-runtime-css-in-js/</guid>
            <pubDate>Fri, 05 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[CSS-in-JS обеспечивает отличный опыт разработки, но его расходы во время выполнения давно вызывают споры. CSS-in-JS без runtime извлекает стили в статические CSS-файлы при сборке, объединяя преимущества обоих подходов.]]></description>
            <content:encoded><![CDATA[<h2>1. «Обоюдоострый меч» CSS-in-JS</h2>
<p>Решения CSS-in-JS, такие как <code>styled-components</code> и <code>Emotion</code>, радикально изменили управление стилями в компонентной разработке, позволив писать CSS в JavaScript- или TypeScript-файлах. Они дают множество преимуществ:</p>
<ul>
<li><strong>Область видимости компонента</strong>: Стили по умолчанию привязаны к компоненту, что решает проблему глобальных конфликтов имён CSS.</li>
<li><strong>Динамические стили</strong>: Динамические стили можно легко создавать на основе props или state компонента.</li>
<li><strong>Совместное размещение кода</strong>: Стили и логика компонента находятся в одном файле, что упрощает сопровождение и организацию.</li>
</ul>
<p>Однако за эти удобства приходится платить. Основа традиционных библиотек CSS-in-JS — <strong>runtime</strong>. Когда компонент монтируется в браузере, JavaScript runtime библиотеки CSS-in-JS:</p>
<ol>
<li>Разбирает CSS в шаблонных строках или объектах.</li>
<li>Генерирует уникальные имена классов.</li>
<li>Динамически внедряет стили в <code>&lt;head&gt;</code> документа, в теги <code>&lt;style&gt;</code>.</li>
</ol>
<p>Этот процесс увеличивает размер JavaScript-бандла и создаёт дополнительные вычислительные расходы при запуске и рендеринге приложения, что влияет на производительность.</p>
<h2>2. Выход с помощью «нулевого runtime»</h2>
<p>Чтобы решить проблему производительности во время выполнения, сообщество предложило новый подход: <strong>CSS-in-JS без runtime (Zero-Runtime)</strong>.</p>
<p>Основная идея — сохранить удобство разработки CSS-in-JS, но выполнить всю работу <strong>во время сборки (build time)</strong>. Инструмент сборки, обычно плагин Babel или Vite, сканирует код, находит все случаи использования CSS-in-JS, а затем:</p>
<ol>
<li>Извлекает текст CSS.</li>
<li>Создаёт статические файлы <code>.css</code>.</li>
<li>Заменяет исходный код CSS-in-JS сгенерированными уникальными именами классов.</li>
</ol>
<p>В результате JavaScript-бандл, доставляемый браузеру, <strong>не содержит runtime-библиотеки CSS-in-JS</strong>, а итог эквивалентен написанным вручную и хорошо оптимизированным статическим CSS-файлам.</p>
<h2>3. Основные решения без runtime</h2>
<h4>a. Linaria</h4>
<p>Linaria — один из пионеров CSS-in-JS без runtime. Она позволяет использовать знакомый синтаксис тегированных шаблонных строк <code>styled</code>.</p>
<pre><code>// YourComponent.js
import { styled } from '@linaria/react';

const Title = styled.h1`
  font-size: 2rem;
  color: tomato;
`;

// 构建时，这会被转换为：
// &lt;h1 class="Title_a1b2c3d"&gt;...&lt;/h1&gt;
//
// 并在一个静态 .css 文件中生成：
// .Title_a1b2c3d {
//   font-size: 2rem;
//   color: tomato;
// }
</code></pre>
<h4>b. vanilla-extract</h4>
<p>Разработанный командой SEEK vanilla-extract идёт дальше и использует TypeScript для создания полностью типобезопасных стилей. Все определения стилей представлены экспортируемыми переменными в файлах <code>.css.ts</code>, что обеспечивает мощный вывод типов и автодополнение.</p>
<pre><code>// styles.css.ts
import { style } from '@vanilla-extract/css';

export const title = style({
  fontSize: '2rem',
  color: 'tomato',
});
</code></pre>
<h4>c. Panda CSS</h4>
<p>Panda CSS — более новый конкурент. Он заимствует идеи Tailwind CSS, предлагает опыт разработки со «Style Props» и при этом гарантирует вывод без runtime.</p>
<pre><code>import { css } from '../styled-system/css';

function MyComponent() {
  return &lt;div className={css({ fontSize: '2rem', color: 'tomato' })} /&gt;;
}
</code></pre>
<h2>4. Преимущества и компромиссы</h2>
<p><strong>Преимущества</strong>:</p>
<ul>
<li><strong>Максимальная производительность</strong>: Конечный результат представляет собой статический CSS без расходов JavaScript runtime.</li>
<li><strong>Меньший размер бандла</strong>: На клиенте не нужно загружать библиотеку CSS-in-JS.</li>
<li><strong>Мощный опыт разработки</strong>: Сохраняются область видимости компонента, типобезопасность TypeScript и совместное размещение кода.</li>
</ul>
<p><strong>Компромиссы</strong>:</p>
<ul>
<li><strong>Зависимость от сборки</strong>: Решение необходимо интегрировать в процесс сборки, поэтому конфигурация несколько усложняется.</li>
<li><strong>Ограниченная динамичность</strong>: Нельзя генерировать совершенно новые стили из значений, доступных только во время выполнения, например из данных API. Однако CSS-переменные и другие техники покрывают большинство динамических сценариев.</li>
</ul>
<h2>Заключение</h2>
<p>CSS-in-JS без runtime — это корректировка курса традиционной парадигмы CSS-in-JS. Он грамотно разделяет «опыт во время разработки» и «производительность во время выполнения», избавляя от необходимости выбирать между ними. Для современных веб-приложений, стремящихся к максимальной производительности и меньшему размеру бандла, это почти идеальное решение.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Обзор возможностей ES2022: Top-Level Await, .at() и не только]]></title>
            <link>https://blog.m1ng.space/ru/posts/es/es2022-features-overview/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/es/es2022-features-overview/</guid>
            <pubDate>Fri, 15 Jul 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[ECMAScript 2022 (ES2022) официально выпущен и включает ряд практичных новых возможностей. В статье кратко рассматриваются самые заметные обновления, включая Top-Level await и метод .at().]]></description>
            <content:encoded><![CDATA[<h2>Введение</h2>
<p>Благодаря ежегодному циклу релизов комитета TC39 язык JavaScript продолжает развиваться каждый год. ECMAScript 2022 (ES2022) принёс ряд новых возможностей, призванных улучшить опыт разработчиков и читаемость кода. Рассмотрим самые яркие из них.</p>
<h2>1. <code>await</code> на верхнем уровне</h2>
<p>Это одна из самых ожидаемых возможностей ES2022. Раньше ключевое слово <code>await</code> можно было использовать только внутри функции <code>async</code>. Из-за этого асинхронные операции в области видимости верхнего уровня модуля было неудобно обрабатывать: обычно асинхронный код приходилось оборачивать в IIFE (немедленно вызываемое функциональное выражение).</p>
<p><strong>Раньше 👎:</strong></p>
<pre><code>// data.js
import { fetchData } from './api.js';

let data;
(async () =&gt; {
  data = await fetchData();
  // ... 只能在这里使用 data
})();

export { data }; // 导出时 data 还是 undefined
</code></pre>
<p><strong>Теперь (ES2022) 👍:</strong></p>
<pre><code>// data.js
import { fetchData } from './api.js';

const data = await fetchData();

export { data }; // 模块会等待 await 完成后再被其他模块评估
</code></pre>
<p>Top-level <code>await</code> значительно упрощает динамическую загрузку модулей, инициализацию зависимостей и другие подобные сценарии, делая асинхронный код более интуитивным.</p>
<h2>2. Метод индексирования массивов и строк <code>.at()</code></h2>
<p>Чтобы получить последний элемент массива в JavaScript, обычно приходится писать <code>arr[arr.length - 1]</code>, что многословно и чревато ошибками. ES2022 вводит метод <code>.at()</code>, унифицирующий работу с прямыми и обратными индексами.</p>
<p>Метод <code>.at()</code> принимает целое число. Положительное число возвращает элемент с соответствующим индексом, а отрицательное отсчитывает элементы с конца.</p>
<p><strong>Раньше 👎:</strong></p>
<pre><code>const arr = [1, 2, 3, 4, 5];
const lastElement = arr[arr.length - 1]; // 5
const secondToLast = arr[arr.length - 2]; // 4
</code></pre>
<p><strong>Теперь (ES2022) 👍:</strong></p>
<pre><code>const arr = [1, 2, 3, 4, 5];
const lastElement = arr.at(-1); // 5
const secondToLast = arr.at(-2); // 4

// 同样适用于字符串
const str = 'hello';
console.log(str.at(-1)); // 'o'
</code></pre>
<h2>3. <code>Object.hasOwn(obj, prop)</code></h2>
<p>Чтобы проверить, есть ли у объекта собственное, а не унаследованное свойство, обычно используют <code>Object.prototype.hasOwnProperty.call(obj, prop)</code>. Такая запись очень громоздка и в некоторых случаях, например с объектами, созданными через <code>Object.create(null)</code>, может привести к ошибке.</p>
<p><code>Object.hasOwn()</code> предлагает более короткий и надёжный статический метод.</p>
<p><strong>Раньше 👎:</strong></p>
<pre><code>const obj = { a: 1 };
console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'toString')); // false
</code></pre>
<p><strong>Теперь (ES2022) 👍:</strong></p>
<pre><code>const obj = { a: 1 };
console.log(Object.hasOwn(obj, 'a')); // true
console.log(Object.hasOwn(obj, 'toString')); // false
</code></pre>
<h2>4. Другие примечательные возможности</h2>
<ul>
<li><strong>Error Cause</strong>: Конструктор <code>Error</code> теперь может принимать второй аргумент, указывающий «причину» ошибки, что облегчает создание более понятных цепочек ошибок.<pre><code>try {
  // ...
} catch (err) {
  throw new Error('New error message', { cause: err });
}
</code></pre>
</li>
<li><strong>Индексы совпадений RegExp (флаг <code>/d</code>)</strong>: При использовании флага <code>/d</code> результат регулярного выражения дополнительно содержит начальный и конечный индексы каждой группы захвата.</li>
</ul>
<h2>Заключение</h2>
<p>Хотя новые возможности ES2022 не принесли революционных изменений, они дорабатывают JavaScript в деталях, решают многие давние проблемы разработчиков и делают код более кратким и надёжным.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Прощай, шаблонный код Redux? Подробно о лёгком менеджере состояния Zustand]]></title>
            <link>https://blog.m1ng.space/ru/posts/react/zustand-vs-redux/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/react/zustand-vs-redux/</guid>
            <pubDate>Sun, 10 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Вас отпугивает громоздкость Redux? Zustand предлагает минималистичное управление состоянием React на основе Hooks. В статье рассматриваются его основные приёмы и философия проектирования.]]></description>
            <content:encoded><![CDATA[<h2>1. «Проблемы» Redux</h2>
<p>Redux, без сомнения, является самой известной и мощной библиотекой управления состоянием в экосистеме React. Однонаправленный поток данных, отладка с путешествием во времени и другие возможности делают его надёжным выбором для больших и сложных приложений. Однако во многих небольших и средних проектах «шаблонный код» (Boilerplate) Redux часто доставляет немало хлопот:</p>
<ul>
<li><strong>Actions &amp; Action Creators</strong>: Необходимо определять множество типов action и функций их создания.</li>
<li><strong>Reducers</strong>: Приходится писать большие конструкции <code>switch</code> для обработки разных action.</li>
<li><strong>Dispatch &amp; Selectors</strong>: В компонентах action отправляются через <code>dispatch</code>, а подписка на изменения состояния выполняется через <code>useSelector</code>.</li>
<li><strong>Context Provider</strong>: Корень приложения необходимо обернуть в <code>&lt;Provider&gt;</code>.</li>
</ul>
<p>В результате даже добавление простого состояния требует изменений в нескольких файлах, что делает процесс громоздким.</p>
<h2>2. Zustand — глоток свежего воздуха</h2>
<p>Zustand («состояние» по-немецки) — это лёгкая библиотека управления состоянием, разработанная командой Poimandres, создавшей <code>react-three-fiber</code>. Её философия — <strong>минимализм</strong> и <strong>ненавязчивость</strong>.</p>
<p><strong>Основные особенности:</strong></p>
<ul>
<li><strong>Очень мало кода</strong>: Для реализации одной функции Zustand обычно требует лишь малую долю кода, необходимого Redux.</li>
<li><strong>Основан на Hooks</strong>: Всё строится вокруг одного пользовательского Hook, что соответствует привычкам современной React-разработки.</li>
<li><strong>Не требует Context Provider</strong>: Верхний уровень приложения не нужно оборачивать в Provider. Store не зависит от дерева компонентов, поэтому его можно импортировать и использовать где угодно.</li>
<li><strong>Легко освоить</strong>: API предельно прост, основные приёмы можно изучить за несколько минут.</li>
</ul>
<h2>3. Основы использования</h2>
<p>Работа с Zustand делится на два шага: <strong>создание Store</strong> и <strong>его использование в компоненте</strong>.</p>
<h4>a. Создание Store</h4>
<p>Store можно определить в любом файле <code>.js</code> или <code>.ts</code>.</p>
<pre><code>// src/store.js
import { create } from 'zustand';

const useBearStore = create((set) =&gt; ({
  bears: 0,
  increasePopulation: () =&gt; set((state) =&gt; ({ bears: state.bears + 1 })),
  removeAllBears: () =&gt; set({ bears: 0 }),
}));

export default useBearStore;
</code></pre>
<p>Функция <code>create</code> принимает callback, аргументом которого является функция <code>set</code>, похожая на <code>setState</code> в React. Здесь определяются состояние и методы его обновления.</p>
<h4>b. Использование в компоненте</h4>
<p>Используйте его в любом компоненте как обычный Hook.</p>
<pre><code>// src/components/BearCounter.jsx
import useBearStore from '../store';

function BearCounter() {
  const bears = useBearStore((state) =&gt; state.bears);
  return &lt;h1&gt;{bears} around here ...&lt;/h1&gt;;
}
</code></pre>
<p>Обратите внимание: через функцию-селектор <code>(state) =&gt; state.bears</code> мы подписываемся на состояние <code>bears</code>. Это важно, поскольку компонент будет повторно рендериться только при изменении <code>bears</code>, что предотвращает ненужные затраты производительности.</p>
<pre><code>// src/components/Controls.jsx
import useBearStore from '../store';

function Controls() {
  const increasePopulation = useBearStore((state) =&gt; state.increasePopulation);
  return &lt;button onClick={increasePopulation}&gt;one up&lt;/button&gt;;
}
</code></pre>
<p>Получить action так же просто.</p>
<h2>4. Асинхронные Action</h2>
<p>Zustand также естественно работает с асинхронными операциями. Никакое промежуточное ПО вроде <code>redux-thunk</code> или <code>redux-saga</code> не требуется.</p>
<pre><code>const useAsyncStore = create((set) =&gt; ({
  data: null,
  fetchData: async (url) =&gt; {
    const response = await fetch(url);
    const data = await response.json();
    set({ data });
  },
}));
</code></pre>
<h2>Заключение</h2>
<p>Zustand не стремится полностью заменить Redux. У Redux по-прежнему есть преимущества в строгих правилах, отслеживаемости и огромной экосистеме.</p>
<p>Однако для подавляющего большинства React-приложений Zustand предлагает более простой и быстрый вариант с меньшей когнитивной нагрузкой. Если вы устали от формальностей Redux или следующему проекту нужен лёгкий и гибкий менеджер состояния, Zustand определённо заслуживает внимания.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Управление проектом Monorepo с помощью Turborepo]]></title>
            <link>https://blog.m1ng.space/ru/posts/build-tools/intro-to-turborepo/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/build-tools/intro-to-turborepo/</guid>
            <pubDate>Sun, 20 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[По мере усложнения проектов Monorepo становится популярным способом организации кода. Но как решить связанные с ним проблемы производительности сборки? Turborepo — высокопроизводительная система сборки, созданная именно для этого.]]></description>
            <content:encoded><![CDATA[<h2>1. Почему стоит выбрать Monorepo?</h2>
<p>Monorepo, или единый репозиторий кода, — это стратегия хранения нескольких независимых проектов или пакетов в одном репозитории. По сравнению с моделью Polyrepo, где у каждого проекта отдельный репозиторий, Monorepo дает несколько заметных преимуществ:</p>
<ul>
<li><strong>Совместное использование кода</strong>: Обмен компонентами, вспомогательными функциями и определениями типов между разными проектами становится очень простым.</li>
<li><strong>Атомарные коммиты</strong>: Если изменение одной функции затрагивает несколько пакетов, его можно выполнить одним коммитом, обеспечив согласованность версий.</li>
<li><strong>Упрощенное управление зависимостями</strong>: Все проекты используют общий <code>node_modules</code> или оптимизируют его через workspace pnpm/yarn, что уменьшает конфликты зависимостей и расхождения версий.</li>
</ul>
<p>Однако по мере роста репозитория Monorepo приносит и новую проблему: <strong>производительность сборки</strong>.</p>
<h2>2. Проблемы Monorepo</h2>
<p>Представьте, что в репозитории есть <code>docs</code>, <code>webapp</code> и общая библиотека UI-компонентов <code>ui</code>. Если вы исправили только одну опечатку в <code>docs</code>, меньше всего хочется, чтобы все проекты репозитория, включая <code>webapp</code> и <code>ui</code>, заново собирались и тестировались.</p>
<p>Традиционным инструментам управления Monorepo, таким как Lerna, не хватает возможностей оркестрации задач и кеширования сборки, что приводит к следующим проблемам:</p>
<ul>
<li><strong>Повторная работа</strong>: При каждом запуске CI/CD все собирается и тестируется с нуля, даже если большая часть кода не изменилась.</li>
<li><strong>Долгая сборка</strong>: Невозможно эффективно использовать несколько ядер CPU для параллельного выполнения задач.</li>
<li><strong>Сложные скрипты</strong>: Для ручного управления порядком выполнения задач приходится писать сложные скрипты <code>package.json</code>.</li>
</ul>
<h2>3. Turborepo: создан для скорости</h2>
<p>Turborepo — высокопроизводительная система сборки для Monorepo на JavaScript/TypeScript, позднее приобретенная Vercel. Она решает описанные проблемы с помощью двух ключевых технологий: <strong>инкрементальной сборки</strong> и <strong>удаленного кеширования</strong>.</p>
<h4>a. Инкрементальная сборка и конвейеры задач</h4>
<p>Turborepo позволяет определять зависимости между задачами в файле <code>turbo.json</code> в корне проекта.</p>
<pre><code>// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": []
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false
    }
  }
}
</code></pre>
<ul>
<li><strong><code>dependsOn: ["^build"]</code></strong>: Символ <code>^</code> означает, что задача <code>build</code> пакета зависит от задач <code>build</code> всех пакетов, от которых он зависит. Turborepo выполняет задачи в наиболее эффективном параллельном порядке на основе этого графа.</li>
<li><strong><code>outputs</code></strong>: Сообщает Turborepo, какие выходные файлы создает задача.</li>
</ul>
<p>При запуске <code>pnpm turbo build</code> Turborepo вычисляет, какие файлы изменились, и заново собирает только затронутые пакеты. Если исходный код и зависимости пакета не изменились, Turborepo напрямую использует результат предыдущей сборки, что происходит мгновенно.</p>
<h4>b. Удаленное кеширование</h4>
<p>Это «главная особенность» Turborepo. Система не только кеширует результаты сборки на локальном компьютере, но и может загружать кеш на общий удаленный сервер, например Vercel или в собственный бакет S3.</p>
<p>Это означает:</p>
<ul>
<li><strong>Ваш коллега</strong>, получив свежий код, может напрямую скачать кеш, если вы уже собрали нужную ему часть, и не выполнять локальную сборку заново.</li>
<li><strong>Сервер CI/CD</strong> также может подключаться к этому удаленному кешу. После сборки и кеширования PR в CI другие разработчики и последующие задачи CI смогут немедленно использовать результат.</li>
</ul>
<p>Это способно сократить время сборки для всей команды с десятков минут до нескольких минут или даже десятков секунд.</p>
<h2>Заключение</h2>
<p>Turborepo не изобрел Monorepo, но благодаря передовым системам кеширования и планирования задач значительно улучшил разработку в Monorepo и устранил его главный узкий момент производительности. Если вы используете или планируете внедрить архитектуру Monorepo, Turborepo — мощный инструмент, способный сэкономить вам и вашей команде много времени.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[SolidJS: по-настоящему реактивный JavaScript-фреймворк]]></title>
            <link>https://blog.m1ng.space/ru/posts/frameworks/introduction-to-solidjs/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/frameworks/introduction-to-solidjs/</guid>
            <pubDate>Fri, 05 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Он похож на React, но в нём нет виртуального DOM. Благодаря инновационной системе мелкозернистой реактивности SolidJS достигает нового уровня производительности. Разберёмся, как он работает.]]></description>
            <content:encoded><![CDATA[<h2>1. «Святой Грааль» фронтенд-фреймворков: производительность и удобство</h2>
<p>С тех пор как React популяризировал виртуальный DOM (Virtual DOM), VDOM словно стал обязательной частью современных фронтенд-фреймворков. Он хранит в памяти виртуальное представление UI и с помощью алгоритма diff вычисляет минимально необходимые обновления DOM, улучшая опыт разработки и производительность в большинстве сценариев.</p>
<p>Но VDOM не обходится бесплатно. Он занимает память, а процесс diff требует вычислительных ресурсов. Отсюда возникает вопрос: можно ли обойти VDOM и точно применять изменения состояния непосредственно к DOM, сохранив при этом декларативный, похожий на React опыт разработки?</p>
<p>SolidJS даёт свой ответ.</p>
<h2>2. Основа SolidJS: мелкозернистая реактивность (Fine-Grained Reactivity)</h2>
<p>SolidJS — декларативный реактивный JavaScript-фреймворк. Он использует JSX и внешне очень похож на React, однако его внутренние принципы совершенно иные.</p>
<p>Компилятор SolidJS преобразует JSX-код в оптимальные нативные операции с DOM. Вместо VDOM он создаёт граф зависимостей из реактивных «сигналов» (Signals). Когда значение сигнала меняется, повторно выполняются только подписанные на него «эффекты» (Effects) или вычисления (Memos).</p>
<p><strong>Модель, меняющая привычное мышление: компонент выполняется только один раз!</strong></p>
<p>В React при изменении state или props вся функция компонента выполняется повторно. В SolidJS функция компонента <strong>выполняется от начала до конца только один раз</strong>.</p>
<pre><code>import { createSignal } from 'solid-js';

function Counter() {
  console.log('Component function runs!'); // 这句话只会在组件挂载时打印一次

  const [count, setCount] = createSignal(0);

  const increment = () =&gt; setCount(count() + 1);

  return (
    &lt;button type="button" onClick={increment}&gt;
      Count: {count()}
    &lt;/button&gt;
  );
}
</code></pre>
<p>При нажатии на кнопку обновляются только считыватель сигнала <code>count()</code> и зависящий от него текстовый DOM-узел. Сама функция <code>Counter</code> повторно не запускается. Такие «хирургически точные» обновления и обеспечивают исключительную производительность SolidJS.</p>
<h2>3. Основные API</h2>
<p>Реактивная система SolidJS в основном состоит из трёх базовых примитивов:</p>
<ul>
<li><strong><code>createSignal(initialValue)</code></strong>: Создаёт реактивный сигнал и возвращает getter и setter: <code>[count, setCount]</code>.</li>
<li><strong><code>createEffect(() =&gt; {})</code></strong>: Создаёт «эффект», который автоматически отслеживает все прочитанные внутри него сигналы и выполняется снова при изменении любого из них. Отлично подходит для побочных эффектов, например ручной работы с DOM.</li>
<li><strong><code>createMemo(() =&gt; {})</code></strong>: Создаёт кэшируемое производное вычисляемое значение. Оно пересчитывается только при изменении внутренних зависимостей-сигналов.</li>
</ul>
<pre><code>import { createSignal, createEffect } from 'solid-js';

function App() {
  const [firstName, setFirstName] = createSignal('John');
  const [lastName, setLastName] = createSignal('Smith');

  // createEffect 会在 firstName 或 lastName 变化时自动运行
  createEffect(() =&gt; {
    console.log(`Full name: ${firstName()} ${lastName()}`);
  });

  // ...
}
</code></pre>
<h2>4. Почему стоит выбрать SolidJS?</h2>
<ul>
<li><strong>Исключительная производительность</strong>: SolidJS часто занимает верхние позиции в независимых тестах, а его производительность очень близка к нативному JavaScript-коду.</li>
<li><strong>Очень маленький размер пакета</strong>: Благодаря отсутствию VDOM runtime основной пакет имеет небольшой размер.</li>
<li><strong>Знакомый опыт разработки</strong>: Если вы знакомы с React Hooks, то сможете быстро освоить SolidJS. Сочетание JSX и реактивных примитивов одновременно мощное и понятное.</li>
<li><strong>Настоящая реактивность</strong>: Его модель ближе к MobX или Composition API во Vue, но компилятор позволяет реализовать её без затрат во время выполнения.</li>
</ul>
<h2>Заключение</h2>
<p>SolidJS представляет важное направление развития фронтенд-фреймворков: компилятор выполняет больше работы во время сборки, чтобы сократить объём кода во время выполнения и повысить производительность. Он доказывает, что от ограничений виртуального DOM можно полностью отказаться, не жертвуя декларативным опытом разработки. Для приложений, стремящихся к максимальной производительности, SolidJS предлагает крайне привлекательный вариант.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Вы действительно умеете пользоваться Fetch? Отмена запросов с AbortController]]></title>
            <link>https://blog.m1ng.space/ru/posts/javascript/fetch-with-abortcontroller/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/ru/posts/javascript/fetch-with-abortcontroller/</guid>
            <pubDate>Sat, 25 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[В современной Web-разработке Fetch API стал стандартом для выполнения HTTP-запросов. Но знаете ли вы, как корректно отменить запрос, который больше не нужен? Ответ — AbortController.]]></description>
            <content:encoded><![CDATA[<h2>1. Fetch API — основа современных Web-запросов</h2>
<p><code>Fetch API</code> пришёл на смену устаревшему <code>XMLHttpRequest</code> и стал стандартным способом выполнения HTTP-запросов в браузере. Он основан на Promise и предоставляет более простой и мощный механизм запросов.</p>
<p>Однако по умолчанию <code>Fetch API</code> не предоставляет прямого механизма отмены запроса. Во многих ситуациях отмена запросов очень важна:</p>
<ul>
<li><strong>Оптимизация производительности</strong>: Когда пользователь быстро переключает страницы или вводит поисковый запрос, отмена старых, уже ненужных запросов экономит пропускную способность и ресурсы сервера.</li>
<li><strong>Предотвращение состояний гонки</strong>: При поиске по мере ввода старый запрос может вернуться позже нового, из-за чего интерфейс покажет устаревшие данные.</li>
<li><strong>Предотвращение утечек памяти</strong>: Если компонент размонтирован до завершения запроса, запущенного в течение его жизненного цикла, попытка обновить уже не существующий компонент может вызвать ошибку или утечку памяти.</li>
</ul>
<h2>2. <code>AbortController</code> — универсальный сигнал отмены</h2>
<p><code>AbortController</code> — это универсальный Web API, предоставляющий механизм для прерывания одного или нескольких Web-запросов. Он не зависит от <code>Fetch API</code>, но прекрасно работает вместе с <code>Fetch</code>.</p>
<p>Основная идея <code>AbortController</code> такова:</p>
<ol>
<li>Создать экземпляр <code>AbortController</code>.</li>
<li>Получить из него объект <code>AbortSignal</code>.</li>
<li>Передать <code>AbortSignal</code> Web API, поддерживающему прерывание, например <code>fetch</code>.</li>
<li>Когда потребуется отмена, вызвать у экземпляра <code>AbortController</code> метод <code>abort()</code>.</li>
</ol>
<h2>3. Совместное использование <code>AbortController</code> и <code>Fetch</code></h2>
<p>Рассмотрим на примере, как с помощью <code>AbortController</code> отменить запрос <code>Fetch</code>.</p>
<pre><code>const controller = new AbortController();
const signal = controller.signal; // 获取信号对象

async function fetchDataWithCancellation(url) {
  try {
    console.log('Fetching data...');
    const response = await fetch(url, { signal }); // 将信号传递给 fetch
    const data = await response.json();
    console.log('Data received:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch request was aborted.');
    } else {
      console.error('Fetch error:', error);
    }
  }
}

// 示例用法
const promise = fetchDataWithCancellation('https://jsonplaceholder.typicode.com/posts/1');

// 假设在 500 毫秒后，我们决定取消这个请求
setTimeout(() =&gt; {
  controller.abort(); // 调用 abort() 方法取消请求
  console.log('Request aborted by timeout.');
}, 500);
</code></pre>
<p>При вызове <code>controller.abort()</code> запрос <code>fetch</code> немедленно прерывается и выбрасывает <code>AbortError</code>. Эту ошибку нужно перехватить в блоке <code>catch</code> и проверить <code>error.name === 'AbortError'</code>, чтобы отличить отмену от других сетевых ошибок.</p>
<h2>4. Практические сценарии</h2>
<h4>a. Поиск по мере ввода (Search-as-you-type)</h4>
<p>Когда пользователь быстро печатает в строке поиска, каждый ввод может запускать новый запрос. Можно отменить предыдущий незавершённый запрос и оставить только самый новый.</p>
<pre><code>let currentController = null;

document.getElementById('searchInput').addEventListener('input', (event) =&gt; {
  if (currentController) {
    currentController.abort(); // 取消上一个请求
  }
  currentController = new AbortController();
  const signal = currentController.signal;

  const query = event.target.value;
  if (query.length &gt; 2) {
    fetchDataWithCancellation(`/api/search?q=${query}`, signal);
  }
});
</code></pre>
<h4>b. Отмена запроса при размонтировании компонента</h4>
<p>Во фреймворках вроде React или Vue отмена незавершённых запросов, запущенных внутри компонента, при его размонтировании помогает эффективно избежать утечек памяти и ненужных обновлений интерфейса.</p>
<pre><code>// React 示例
useEffect(() =&gt; {
  const controller = new AbortController();
  fetchDataWithCancellation('/api/data', controller.signal);

  return () =&gt; {
    controller.abort(); // 组件卸载时取消请求
  };
}, []);
</code></pre>
<h2>Заключение</h2>
<p><code>AbortController</code> — незаменимый инструмент современной Web-разработки. Он предоставляет нативный механизм отмены для <code>Fetch API</code> и других асинхронных операций, помогая создавать более надёжные и эффективные приложения с лучшим пользовательским опытом. Умение пользоваться <code>AbortController</code> — один из необходимых навыков хорошего фронтенд-разработчика.</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
    </channel>
</rss>