Справочник
Архитектура
responsive-media
│
├── BaseResponsiveState (абстрактный)
│ Прокси над обычным Record<string, boolean>
│ Пакетные обновления — все колбэки matchMedia/ResizeObserver собираются
│ перед тем, как срабатывает единый notify()
│ Реестр подписок: глобальные слушатели + слушатели по конкретному ключу
│ Debounce для subscribe(); слушатели по ключу никогда не дебаунсятся
│ Упорядоченные хелперы брейкпоинтов: current / isAbove / isBelow / between
│ Утилиты: syncCSSVars / emitDOMEvents / toSignal / hydrate / destroy
│
├── ReactiveResponsiveState extends BaseResponsiveState
│ setupSources() → window.matchMedia на каждый ключ конфига
│ Сгенерированная строка media query хранится в карте mediaQueries
│ Пакетирует все события 'change' MediaQueryList, затем сбрасывает
│ setConfig() / destroy() cleanupSources() удаляет обработчики matchMedia
│
├── ContainerState extends BaseResponsiveState
│ setupSources() → единственный ResizeObserver на целевом элементе
│ Вычисляет условия width/height/orientation/aspect-ratio в JS
│ Переподключает observer при изменении конфига; teardown в destroy()
│
├── createResponsiveState(config, options)
│ Фабрика — возвращает новый экземпляр ReactiveResponsiveState
│
├── responsiveState (глобальный синглтон)
│ Предварительно настроен с ResponsiveConfig по умолчанию (mobile/tablet/desktop)
│ setResponsiveConfig() / getResponsiveState() / getResponsiveMediaQueries()
│ делегируют этому экземпляру
│
├── toMediaQueryString(conditions)
│ Конвертирует MediaQueryConfig → CSS-строку
│ AND: плоский массив → объединяется через ' and '
│ OR: вложенный массив → группы объединяются через ', '
│ Числовые значения получают суффикс 'px' (кроме raw / orientation / …)
│
├── match(state, map, fallback?)
│ Поиск первого совпадения — возвращает сопоставленное значение для первого истинного ключа в map
│
├── subscribeMediaQuery(query, callback)
│ Тонкая обёртка вокруг window.matchMedia + addEventListener('change')
│ Возвращает функцию очистки
│
├── Адаптер Vue (vue-responsive.ts)
│ RESPONSIVE_KEY — ключ инъекции; provide() его, чтобы передать useResponsive()/
│ useBreakpoints() кастомное состояние вместо глобального
│ ResponsivePlugin — app.use(); вызывает setResponsiveConfig, делает provide() RESPONSIVE_KEY
│ useResponsive() — сначала inject(RESPONSIVE_KEY), иначе shallowReactive-зеркало; subscribe() синхронизирует его
│ useBreakpoints() — возвращает { current, isAbove, isBelow, between }
│ тоже сначала проверяет inject(RESPONSIVE_KEY), как и useResponsive()
│ useMediaQuery() — оборачивает subscribeMediaQuery в ref + onUnmounted; возвращаемая ссылка
│ также всегда несёт .stop() для очистки вне экземпляра компонента
│ useContainerState() — watchEffect над templateRef; создаёт/уничтожает
│ ContainerState; возвращает shallowReactive-зеркало
│
├── Адаптер React (react-responsive.ts)
│ useResponsive() — useSyncExternalStore(subscribe, getState, getState)
│ useBreakpoints() — useSyncExternalStore + оборачивает упорядоченные хелперы
│ useMediaQuery() — useSyncExternalStore над subscribeMediaQuery
│ useContainerState() — useEffect создаёт ContainerState; useState-зеркало;
│ повторяет попытку через requestAnimationFrame, если ref ещё не подключён
│
└── Пресеты (presets.ts)
ResponsiveConfig — mobile/tablet/desktop по умолчанию
TailwindPreset / TailwindOrder
BootstrapPreset / BootstrapOrder
AccessibilityPresetРазмер бандла и peer-зависимости
| Точка входа | Peer-зависимости | Размер (мин. + gzip) | Заметки |
|---|---|---|---|
responsive-media | (нет) | 12,5 кБ + 4,3 кБ | Только framework-agnostic ядро — без кода Vue или React |
responsive-media/vue | vue ^3.5.27 | 12,0 кБ + 4,2 кБ | Только Vue composables |
responsive-media/react | react ^19 | 10,3 кБ + 3,5 кБ | Только React-хуки |
responsive-media/presets | (нет) | 1,3 кБ + 0,4 кБ | Только конфиги пресетов — добавляются в любой экземпляр |
responsive-media/container | (нет) | 6,8 кБ + 2,4 кБ | Только класс ContainerState + фабрика |
responsive-media/nuxt | @nuxt/kit | 0 в браузере | Модуль Nuxt — только на этапе сборки, без кода для браузера |
responsive-media/testing | (нет) | 3,8 кБ + 1,7 кБ | Помощники для тестов — управляемый matchMedia |
Размеры даны для всей точки входа, минифицированной и сжатой gzip, без Vue, React и @nuxt/kit; измерены на 2.2.2 (npm run size в репозитории пакета повторяет измерение). Каждая точка входа несёт в себе ядро, которым пользуется, поэтому размеры не складываются: приложение, использующее точку входа Vue, платит за неё один раз, а не ещё и за корневую.
Типичные импорты
| Импорт | Размер (мин. + gzip) |
|---|---|
createResponsiveState из корня | 6,2 кБ + 2,1 кБ |
useResponsive из responsive-media/vue | 6,8 кБ + 2,4 кБ |
useMediaQuery из responsive-media/vue | 0,6 кБ + 0,4 кБ |
defineResponsive из responsive-media/vue | 8,5 кБ + 3,1 кБ |
useResponsive из responsive-media/react | 6,4 кБ + 2,2 кБ |
Пакет помечен sideEffects: false, а общее responsiveState живёт в отдельном модуле, поэтому сборщик отбрасывает его, если его никто не импортирует. Поэтому useMediaQuery, которому общее состояние не нужно, весит малую долю полной точки входа; функции, построенные на состоянии, например useResponsive, по-прежнему несут его с собой.
Пакет поставляется как tree-shakeable ESM и CommonJS. Начиная с 2.0, адаптер Vue (ResponsivePlugin, useResponsive и т. д.) имеет собственную точку входа responsive-media/vue — точно так же, как и React-адаптер. Основная точка входа больше не импортирует ничего специфичного для Vue или React, так что её вес остаётся минимальным для любого потребителя, с фреймворком или без. (До 2.0 адаптер Vue был встроен в основную точку входа, из-за чего сам факт импорта корня пакета тянул статический импорт @vue/runtime-core — это была реальная ошибка, а не задокументированное поведение tree-shaking; если вам встретится старый пример кода, импортирующий Vue composables из голого 'responsive-media', поправьте его на 'responsive-media/vue'.)
Лицензия
MIT