@feugene/granularity-devtools
v0.3.2
Published
Vue DevTools panel for @feugene/granularity — where a prop value came from, the overlay layer stack and design-system warnings.
Maintainers
Readme
@feugene/granularity-devtools
Панель Vue DevTools для дизайн-системы
@feugene/granularity.
Показывает то, чего нельзя узнать до запуска приложения.
Что в панели
Overlay layers — живой стек слоёв: кому адресован Esc, какие модалки ушли
в inert, на какой глубине каждая. Плюс лента открытий, закрытий и нажатий
Esc. Нажатие, которое слой съел и остался открытым (closeOnEsc выключен),
помечено предупреждением: снаружи это выглядит ровно как «Esc не работает».
Секция на компоненте — в штатном инспекторе компонентов, рядом с пропами.
Каждый проп разложен по источнику: prop (написан в разметке),
GrConfigProvider · componentDefaults, GrConfigProvider · size или
component default. Отвечает на вопрос «почему у кнопки size=sm, я его не
задавал».
Классы без правил — там же: классы корня и потомков, которым в документе не соответствует ни одного CSS-правила. Это симптом промаха safelist: размеры работают, цвета прозрачные, фокус-колец нет. Кросс-доменные таблицы стилей браузер читать не даёт — если такие есть, раздел говорит, что список неполон.
Токены компонента — там же: применившиеся значения из вычисленного стиля,
объявленные, но не применившиеся, и --gr-*, выставленные на элементе, которых
нет ни в одном реестре, — почти всегда опечатка.
Токены, которые компонент читает — четырьмя секциями по владельцу: own
(объявлены им самим), from other components (с именем владельца — правка
заденет и его), foundation (палитра, шкалы, тени, длительности) и
unregistered (не объявлен ни одним реестром — обычно опечатка). У каждого
токена фактическое значение и пометка has fallback, если он читается с
запасом.
Это обратная сторона предыдущей секции, и включения между ними нет ни в одну
сторону: объявленный токен может не потребляться, а потребляет компонент в
основном чужое. Живой GrButton на стенде читает двенадцать токенов, из них
свои — два. Отсюда виден и путь значения: --gr-button-primary-bg (точка
кастомизации, читается с запасом) отдаёт #e546bd, потому что его запас —
--gr-primary, перекрашенный приложением.
Токены, разрешающиеся в пустоту — отдельной секцией: --gr-*, который
правило компонента читает без запасного значения, а браузер отдаёт пустым.
Такое объявление отбраковывается на этапе вычисления, и компонент выходит без
фона, без рамки, с прямыми углами — при зелёной сборке и валидном CSS.
Смежную проверку делает granular doctor (диагностика token-undefined), и с
пресета 0.15.0 она этот класс ловит: подмена tokens.css через
themes.tokensFile поднимает на стенде apps/playground число находок с 8 до
52. Панель отвечает на другой вопрос — не «задаёт ли токен хоть один слой в этой
конфигурации», а «пуст ли он сейчас, на этом элементе». Замеры на том же
стенде:
| | doctor | панель |
| --- | --- | --- |
| штатный конфиг | 8 находок | 0 |
| подмена tokensFile | 52 | 11 |
Расхождения не противоречие. Восемь находок на здоровом стенде — токены,
которые GrAlert выставляет себе сам инлайновым стилем: статикой такое не
отличить от незаданного, а браузер их видит заданными. Пятьдесят две против
одиннадцати — это весь замкнутый набор выбранных компонентов против того, что
реально на экране, с именем класса, который токен читает.
Issues — все предупреждения пакета одним списком со счётчиком повторов,
вместо тонущих в консоли строк. Сюда же попадают недостающие обязательные
пропы: production-сборка SFC стирает required, поэтому «Missing required
prop» Vue не печатает никогда — панель восстанавливает эту проверку по типам.
Announcements — лента живого региона: что услышал бы экранный диктор. Единственный способ проверить это в браузере, не запуская его.
Плюс проверка i18n при старте: если адаптер не подключён, компоненты молча берут встроенные английские строки — панель говорит об этом вслух.
Статические вопросы — «кто затащил класс в CSS», «кто тянет компонент в сборку»,
«есть ли конфликт токенов» — решает CLI granular why-css / explain / doctor
из @feugene/unocss-preset-granular.
Опции
app.use(installGranularityDevtools({
// Выключить поиск недостающих обязательных пропов.
checks: 'off',
// Глубина буфера событий в ядре; по умолчанию 50.
eventLimit: 200,
}))checks есть потому, что проверка идёт на обходе дерева — по каждому узлу.
Пока она дёшева, но выключатель дешевле завести до того, как подорожает.
Отчёт для issue
Кнопка в разделе «Granularity overlays» кладёт JSON со стеком слоёв, живыми
виртуализаторами, предупреждениями и версиями — в консоль и, если получится, в
буфер обмена. Только буфера мало: в панели DevTools нет пользовательского жеста,
и navigator.clipboard там отказывает молча.
Мост для тестов
Состояние, которое видит панель, доступно и коду — через window.__GR_DEVTOOLS__.
Он ставится вместе с плагином и работает без открытой панели: тесту не нужно
ничего открывать.
await page.getByRole('button', { name: 'Открыть' }).click()
// Ждём событие стека, а не анимацию.
await page.evaluate(() =>
window.__GR_DEVTOOLS__.waitFor(state => state.layers.length === 1, { timeout: 3000 }),
)| Что | Зачем |
| --- | --- |
| snapshot() | стек слоёв, журнал предупреждений и версия панели одним объектом |
| waitFor(predicate, { timeout }) | ждёт, пока снимок удовлетворит условию; проверяет сразу, поэтому выполненное условие не ждёт таймаута |
| version | версия панели — чтобы тест понимал, с чем говорит |
Мост появляется после монтирования приложения, а не на load: если оно
стартует асинхронно, дождитесь его — page.waitForFunction(() => Boolean(window.__GR_DEVTOOLS__)).
Что мост не решает: ожидание кадров отрисовки. Анимация панели, применённый
:hover, завершённый CSS-переход — это по-прежнему waitForTimeout или проверка
пикселей. Мост отвечает про состояние рантайма, а не про то, что успел нарисовать
браузер.
Чего панель не делает
Статические вопросы закрывает CLI пресета, и дублировать его незачем: где
статики хватает, панель молчит — она добавляет только то, что видно
исключительно в браузере.
Промахи ключей i18n не считаются: для этого пришлось бы подменить t у чужого
адаптера, то есть писать в состояние приложения.
Установка
yarn add -D @feugene/granularity-devtoolsПодключение
import { installGranularityDevtools } from '@feugene/granularity-devtools'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
if (import.meta.env.DEV) {
app.use(installGranularityDevtools())
}
app.mount('#app')Плагин подключается явным вызовом, а не через createGranularity: плагин
ядра ничего не импортирует сам, чтобы не ломать гранулярность бандла, и панель
это правило не нарушает.
Панель работает только в разработке
installGranularityDevtools() — no-op на сервере и при
process.env.NODE_ENV === 'production', то есть у сборщиков, которые определяют
process (webpack, rspack), панель выключается сама.
Гард import.meta.env.DEV у вызывающего обязателен, и вот почему: в
production-сборке Vite process в браузере не определён, поэтому внутренняя
проверка признать сборку продовой не может — а сделать её строже нельзя, иначе
панель перестанет включаться в dev-сервере Vite, где process не определён
ровно так же. Гард снимает вопрос целиком: из бандла уходит и вызов, и импорт
@vue/devtools-api.
Замер на apps/playground: с гардом — ноль вхождений имени пакета, символа
installGranularityDevtools и devtools-api во всех чанках.
Аналог для сборщиков без import.meta.env — process.env.NODE_ENV !== 'production'.
Лента событий не пишется, пока не нажата запись
Таймлайн Vue DevTools по умолчанию выключен: пустые «Granularity overlays» и «Granularity announcements» чаще всего значат, что запись не включена, а не что событий не было. Кнопка записи — в правом верхнем углу вкладки Timeline.
Требуются Vue DevTools — расширение браузера, standalone-приложение или встроенная панель.
