Skip to content

Latest commit

 

History

History
192 lines (144 loc) · 12.6 KB

File metadata and controls

192 lines (144 loc) · 12.6 KB

@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.envprocess.env.NODE_ENV !== 'production'.

Лента событий не пишется, пока не нажата запись

Таймлайн Vue DevTools по умолчанию выключен: пустые «Granularity overlays» и «Granularity announcements» чаще всего значат, что запись не включена, а не что событий не было. Кнопка записи — в правом верхнем углу вкладки Timeline.

Требуются Vue DevTools — расширение браузера, standalone-приложение или встроенная панель.