Sprite

Иконки групп interface/system, interface/product, interface/web, services и extensions рендерятся через SVG-спрайт: спрайт монтируется в документ один раз, а каждая иконка ссылается на его символ через <use href="#...">. Так повторяющиеся иконки не дублируют свои <path> в DOM.

Группы flags и logos спрайта не имеют — это обычные inline-SVG-компоненты. Причина: спрайт полагается на наследование currentColor через <use>, а эти группы сохраняют исходные цвета SVG.

Модель рендера — fallback-first

Каждая sprite-иконка самодостаточна и не требует, чтобы спрайт был смонтирован:

  • SSR и первый клиентский рендер — всегда инлайн-SVG-fallback (содержимое зашито в компонент). Иконка видна сразу: до гидрации, до загрузки спрайта, без мигания.
  • После маунта иконка проверяет наличие своего символа в DOM. Символ есть — переключается на <use>. Символа нет — подписывается на событие «спрайт смонтирован» и переключается по факту его вставки (актуально для SpriteFromUrl, который загружает спрайт асинхронно).
  • Спрайт так и не появился — иконка остаётся на инлайн-fallback. Это штатный режим: без ошибок и предупреждений в консоли, визуально неотличимо от спрайтового рендера.

Событие передаётся через document, а не через состояние модуля — поэтому переключение работает и при нескольких копиях @cloud-ru/ds-icons в бандле (cjs+esm, разные версии в микрофронтах).

Отличить режимы в DevTools: у fallback-иконки внутри <svg> лежит <g> с path, у спрайтовой — <use href="#snack-uikit-…">.

Sprite — контент в бандле

Sprite печатает содержимое спрайта прямо в разметку. Подходит для SPA без SSR и небольших приложений:

import { Sprite, SpriteSystemSVG } from '@cloud-ru/ds-icons/sprite'
import { SearchSVG } from '@cloud-ru/ds-icons/interface/system'

export function App() {
  return (
    <>
      {/* Подключается один раз в корне приложения */}
      <Sprite content={SpriteSystemSVG} />

      {/* Иконка сама ссылается на символ через <use href='#...'> */}
      <SearchSVG size={24} />
    </>
  )
}

Доступные спрайты (все — из @cloud-ru/ds-icons/sprite): SpriteSystemSVG (он же SpriteSVG), SpriteWebSVG, SpriteProductSVG, SpriteServicesSVG, SpriteExtensionsSVG. Подключайте только те наборы, иконки из которых реально используются.

SpriteFromUrl — кэшируемый спрайт для SSR

Sprite на SSR отдаёт содержимое спрайта в HTML на каждый запрос заново — браузер не может закэшировать его отдельно от страницы. Для приложений, которые сами хостят статику (Next.js, root-приложение), есть SpriteFromUrl: он загружает спрайт как обычный статический файл через fetch после гидрации, вставляет в текущий документ (сохраняя наследование currentColor — в отличие от <use href="external.svg#id">, которое ломает его в части браузеров) и опирается на HTTP-кэш браузера при повторных заходах. Пока спрайт грузится, иконки показывают инлайн-fallback — визуальной просадки нет.

  1. Скопировать файлы спрайтов в статическую директорию приложения на этапе сборки:

    npx @cloud-ru/ds-icons copy-sprites --out public/sprites --base-url /sprites

    Команда кладёт файлы с content-хэшем в имени (sprite.system.<hash>.symbol.svg) — их можно отдавать с Cache-Control: public, max-age=31536000, immutable — и манифест public/sprites/manifest.json с сопоставлением { system: '/sprites/sprite.system.<hash>.symbol.svg', … }.

  2. Подключить по URL из манифеста:

    import { SpriteFromUrl } from '@cloud-ru/ds-icons/sprite'
    import manifest from '../../public/sprites/manifest.json'
    
    export function App() {
      return (
        <>
          <SpriteFromUrl src={manifest.system} />
          {/* … остальной рендер */}
        </>
      )
    }

Сценарии подключения

Next.js-приложение

Sprite-иконки — client-компоненты, но в SSR-HTML попадает инлайн-fallback, поэтому иконки видны до гидрации. Спрайт монтируется один раз в корневом layout (client-часть) — обычно SpriteFromUrl по шагам выше. После гидрации иконки переключаются на <use> самостоятельно.

Root-приложение (контейнер микрофронтов)

Контейнер монтирует спрайт один раз у себя в корне. Дочерним микрофронтам этого достаточно: событие «спрайт смонтирован» идёт через общий document, поэтому иконки из микрофронтов переключаются на <use> даже при собственных копиях @cloud-ru/ds-icons в их бандлах. Владелец контейнера отвечает за монтирование и актуальность спрайта.

Микрофронт (не точка входа)

Ничего подключать не нужно — только импортировать иконки:

  • контейнер уже смонтировал спрайт — иконки микрофронта переключатся на <use> сами;
  • микрофронт запущен standalone (локальная разработка, тесты) — иконки остаются на инлайн-fallback, без ошибок и предупреждений.

Собственный Sprite/SpriteFromUrl внутри такого микрофронта не монтируется и npx @cloud-ru/ds-icons copy-sprites не запускается — иначе документ получает дубликат символов, которые контейнер уже держит в общем DOM.

Расхождение версий контейнера и микрофронта

Спрайт контейнера собран из его версии @cloud-ru/ds-icons; микрофронт может использовать более новую:

  • иконка, которой нет в спрайте контейнера, — её символ не найдётся, иконка остаётся на собственном инлайн-fallback с корректным глифом;
  • иконка перерисована (тот же symbolId, другой глиф) — <use> покажет глиф из спрайта контейнера, то есть старый. Лечится обновлением спрайта в контейнере.

Динамическая иконка по id — SpriteIcon

Для сценариев, где id иконки известен только в рантайме (например, выбран в CMS и приходит из API), есть компонент SpriteIcon — рендерит <use href="#symbolId"> на символ смонтированного спрайта, ничего не добавляя в бандл:

import { SpriteIcon } from '@cloud-ru/ds-icons/sprite'

<SpriteIcon symbolId={card.iconId} size={24} />
  • symbolId — обычный проп: значение меняется — компонент перерисовывается, фабрик и мемоизации у потребителя нет.
  • Пока символа нет в DOM (спрайт не смонтирован, id неизвестен спрайту) — рендерится fallback (ReactNode: скелетон, дефолтная иконка), по умолчанию пустой <svg> правильного размера: лейаут не прыгает.
  • В отличие от статических *SVG-компонентов, инлайн-fallback с глифом здесь невозможен — глиф известен только спрайту, поэтому для основного пути рендера спрайт обязан быть смонтирован.

Статические *SVG-компоненты пакета построены на этом же компоненте (через фабрику createSpriteIcon) — поведение единое.

Каталог id — манифест символов

Список валидных symbolId генерируется вместе со спрайтами и доступен в двух формах:

  • SPRITE_SYMBOL_IDS из @cloud-ru/ds-icons/sprite — типизированная константа { system: [...], product: [...], … } с типами SpriteGroupId / SpriteSymbolId. Для валидации и автокомплита id в коде.
  • sprite.symbols.json — JSON рядом со спрайт-файлами; npx @cloud-ru/ds-icons copy-sprites копирует его в статическую директорию вместе со спрайтами и manifest.json. Для внешнего тулинга — например, пикера иконок в CMS: каталог всегда синхронен с фактическим содержимым спрайтов.

Схема id: snack-uikit-<group>-<kebab-name> (snack-uikit-product-accept). Неизвестный/протухший id безопасен — иконка остаётся на fallback.

Props

Sprite

Types

PropsSpriteProps
PropTypeDefaultRequiredDescription
contentstringyes
data-test-idstringno

SpriteFromUrl

Types

PropsSpriteFromUrlProps
PropTypeDefaultRequiredDescription
srcstringyesURL файла спрайта (обычно из manifest.json, созданного npx @cloud-ru/ds-icons copy-sprites)
data-test-idstringno

SpriteIcon

Types

PropsSpriteIconProps
PropTypeDefaultRequiredDescription
symbolIdstringyesid символа спрайта, на который ссылается <use href="#...">; каталог валидных id — SPRITE_SYMBOL_IDS / sprite.symbols.json
fallbackReactNodenoЧто рендерить внутри <svg>, пока символа нет в DOM (спрайт не смонтирован или id неизвестен)
testIdstringnoСуффикс data-test-id (итоговый атрибут — icon${testId}); переопределяется явным data-test-id
sizenumbernoРазмер иконки в px (по умолчанию 24)
data-test-idstringno