npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

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

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

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

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