vue‑why‑render 0.8: граф реактивности, WASM‑ядро и VS Code расширение
- понедельник, 26 октября 2026 г. в 00:00:20
Месяц назад я написал статью о том, как сделал отладчик рендеров для Vue — инструмент, который называет точную причину каждой перерисовки. Рассказывал про renderTriggered, про ловушки с process.env.NODE_ENV, кириллицей в байтовых смещениях и вычислением чужих computed.
С тех пор вышла версия 0.8 — с тремя большими фичами, которые я не планировал делать так быстро. Расскажу, что нового и зачем.
Если не читали первую статью — вкратце. vue-why-render подключается к Vue 3 приложению двумя строками:
import { scan } from 'vue-why-render' scan(app, { panel: true })
И показывает на каждом перерисованном компоненте: имя, счётчик рендеров, длительность и причину. Не target: Proxy, а prop badge: {text} → {text} — new reference, same value. Для Pinia — store cart.items: Array(1) → Array(0) · action clear(). Для роутера — route.params.id: "1" → "2".
Плюс панель с пятью вкладками, бюджеты рендеров, тесты (expectRenders(Card).toBe(0)), отчёты в JSON/Markdown. Ноль зависимостей, MIT.
Раньше vue‑why‑render отвечал на вопрос «почему этот компонент перерисовался?». Теперь он отвечает на вопрос «кто от чего зависит?».
Новая вкладка Deps строит живую карту зависимостей: компонент ↔ реактивный источник. Здесь я как раз подключил renderTracked — в первой статье я объяснял, почему не стал его использовать (слишком много вызовов, замедляет приложение). Решение: собирать данные лениво, только для открытой вкладки Deps, и дедуплицировать на лету.
Два режима:
Components → какие ref, prop, store и computed читает этот компонент?
Sources → какие компоненты зависят от этого ref?
На каждом ребре — счётчик чтений и триггеров. Если UserAvatar читает user.name 47 раз, но это вызвало 2 рендера — остальные чтения безвредны. Если CartBadge дёргается от cart.items на каждый keystroke — вот проблема.
Это рентген для реактивности Vue.
В первой статье всё работало на чистом JS. Для небольших приложений — нормально. Но когда компонентов сотни или тысячи, три операции начинают заметно тормозить:
Построение дерева компонентов
Дедупликация причин рендера
Сравнение (дифф) двух отчётов
Я переписал их на Rust и скомпилировал в WebAssembly.
Как это устроено:
WASM‑модуль подгружается лениво, отдельным чанком. Бандл приложения не растёт — пока vue‑why‑render не нужен, ничего лишнего не грузится. Если WASM недоступен (SSR, jsdom, строгий CSP) — автоматически включается JS‑фолбэк. Конфигурировать ничего не нужно.
scan(app, { panel: true }) // Внутри: // 1. Пробуем загрузить .wasm чанк // 2. Если ok — используем Rust-реализацию // 3. Если нет — переключаемся на JS // Потребитель не замечает разницы
Для обычного приложения разница в скорости незаметна. Для крупного — заметна.
Браузерные DevTools — это хорошо, но переключаться между Chrome и VS Code надоело. Поэтому я сделал WebSocket‑мост: сканер в браузере стримит данные прямо в редактор.
Что появляется в VS Code:
Дерево компонентов в сайдбаре — отсортировано по рендерам, иконки 🔥 для горячих компонентов
Дерево зависимостей — тот же граф реактивности
CodeLens на .vue файлах — количество рендеров и средняя длительность прямо над <script setup>
Статус‑бар — живой счётчик рендеров
Команды — pause, resume, reset из палитры
Не нужно переключаться между браузером и редактором. Данные там, где пишется код.
Vue DevTools интеграция — помимо собственной панели, vue‑why‑render теперь регистрируется в Vue DevTools. Второй инспектор с деревом реактивных источников (цветные теги: ref, prop, store, computed) и второй слой на таймлайне, логирующий каждый триггер как событие.
Nuxt модуль — одна строка в конфиге:
export default defineNuxtConfig({ modules: ['vue-why-render/nuxt'] })
Модуль уже в каталоге nuxt.com/modules.
За месяц пакет набрал 668 загрузок на npm, попал в каталог модулей Nuxt, и получил три фичи, которые я честно не планировал делать в ближайшие полгода.
npm i vue-why-render
Для Nuxt: modules: ['vue-why-render/nuxt']
Ноль зависимостей. MIT. Vue 3.3+.
Если нашли баг или хотите фичу — issues открыты.