Utilities

Export Icons

SXL Export Icons: installable CLI-утилита для экспорта COMPONENT/COMPONENT_SET из Figma с diff-sync, multi-config, шрифтами и SF Symbols.

Overview

SXL Export Icons (@sxl-studio/export-icons) — это CLI-утилита для массовой выгрузки иконок и ассетов из Figma в проект.

Утилита:

  • экспортирует только COMPONENT и варианты из COMPONENT_SET;
  • поддерживает статические форматы svg, png, jpg, webp, gif, pdf, а также Motion svg, gif, mp4, webm;
  • делает инкрементальный sync (new/updated/deleted/renamed);
  • умеет генерировать иконочный шрифт (woff2/woff/ttf/svg/eot);
  • умеет собирать iOS Assets.xcassets (.symbolset);
  • поддерживает сразу несколько конфигов и несколько source/target в одном конфиге;
  • поддерживает 2 режима источника: Figma REST и Bridge/MCP.

Когда использовать

  • Вы хотите автоматически синхронизировать иконки из Figma в assets/.
  • Нужны стабильные имена файлов и управляемый нейминг (kebab/snake/camel/pascal).
  • Нужен быстрый повторный запуск без полной перезагрузки.
  • Нужны дополнительные артефакты: webfont, SF Symbols.

Установка

Требуется Node.js 20.9 или новее.

BASH
# локально в проект
npm install --save-dev @sxl-studio/export-icons

# запуск локально установленного бинарника
pnpm exec sxl-export-icons help

# one-shot без установки
pnpm dlx @sxl-studio/export-icons help
npx @sxl-studio/export-icons help

Не используйте pnpx sxl-export-icons ... в корпоративных/proxy registry.
Такой вызов может пытаться резолвить unscoped пакет sxl-export-icons и давать 404.


Подготовка доступа к Figma

1) Получите Personal Access Token

  1. Откройте Figma account settings.
  2. Создайте Personal Access Token.
  3. В scopes включите минимум:
    • file_content:read
    • file_metadata:read
  4. Остальные scopes для export-icons не требуются (write-права не нужны).
  5. Укажите срок действия токена согласно вашей политике (PAT в Figma ограничены по времени).
  6. Сохраните его в .env:
BASH
FIGMA_TOKEN=figd_xxxxxxxxxxxxxxxxx

2) Получите fileKey

Из URL файла:

https://www.figma.com/design/<fileKey>/...

Сохраните ключ в .env:

BASH
SXL_ICONS_FILE_KEY=abc123xyz456

3) Зафиксируйте безопасность .env

Добавьте .env в .gitignore и не пушьте токены в git:

GITIGNORE
.env
.env.local

Минимальный набор переменных:

BASH
FIGMA_TOKEN=figd_xxxxxxxxxxxxxxxxx
SXL_ICONS_FILE_KEY=xxxxxxxxxxxx
# optional, only for mode: mcp
BRIDGE_AUTH_TOKEN=xxxxxxxxxxxx

CLI автоматически подхватывает .env.local и .env из текущей и родительских директорий. Во время REST-загрузки теперь показывается живой прогресс по этапам/страницам (REST 1/3, REST 2/3, REST 3/3).


Быстрый старт

BASH
# 1. Создать шаблон конфига
pnpm exec sxl-export-icons init

# либо мастер настройки (интерактивный)
pnpm exec sxl-export-icons init --wizard

# 2. Проверить конфиг
pnpm exec sxl-export-icons validate-config --config sxl-export-icons.config.yaml

# 3. Dry-run
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml --dry-run

# 4. Первый реальный запуск (принять уже скачанные файлы как baseline)
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml --adopt-existing

# 5. Обычный sync
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml

# 6. Запуск только одного target из смешанного конфига
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml --target font-subset-local

Пример запуска из project terminal

BASH
cd ./design-system/tokens
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml --adopt-existing
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml

Рекомендуемые package scripts

Если вы запускаете утилиту из token package, добавьте похожие scripts в его package.json:

BASH
pnpm run icons:sync:init        # первый запуск: baseline из существующих файлов
pnpm run icons:sync             # повседневная синхронизация с Figma
pnpm run icons:sync:dry-run     # предварительная проверка изменений
pnpm run icons:sync:full        # аварийный полный пересбор
pnpm run icons:sync:font        # локальная конвертация subset-иконок в шрифт
pnpm run icons:sync:font:dry-run
pnpm run icons:clean

Команды CLI

КомандаЧто делает
sync (по умолчанию)Экспорт/синхронизация иконок
cleanУдаляет артефакты, зарегистрированные в state для выбранных scope
initГенерирует стартовый sxl-export-icons.config.yaml
validate-configПроверяет YAML и резолв переменных/путей
reportПоказывает покрытие state по source/target scope
probe-motionДиагностирует Figma Motion metadata и поддержку runtime export через Bridge/Remote Connect

Алиасы команд:

  • create-config -> init
  • init-wizard -> init --wizard

Полезные флаги:

  • --config <path> (можно несколько раз)
  • --config-glob "<glob>"
  • --target <target-id> (можно несколько раз)
  • --dry-run
  • --full
  • --adopt-existing
  • --allow-fallback-rest
  • --report <path-to-json>

Для clean есть дополнительные опции:

  • --skip-aux -> не удалять папки из font.path и sfSymbols.path, очищать только трекаемые ассет-файлы/state
  • --dry-run -> показать, что будет удалено, без изменений на диске

Экспорт анимированных Motion-иконок

Figma Motion иконки можно экспортировать обычным sync, если в target явно задано motion.enabled: true.

Поддерживаемые Motion-форматы:

  • svg -> animated SVG, собранный из Motion keyframes;
  • gif -> нативный Figma Motion GIF export через plugin runtime;
  • mp4 -> нативный Figma Motion MP4 export через plugin runtime;
  • webm -> нативный Figma Motion WebM export через plugin runtime.

Для Motion export нужны SXL Studio Bridge, активная Remote Connect-сессия в том же Figma-файле и rest source с fileKey/fileKeyVar для обычной индексации файла или mcp source, когда файл уже открыт в Figma.

YAML
targets:
  - id: motion-webm
    sourceIds: [icons]
    output:
      path: assets/motion
      format: webm
      scale: 1
      quality: 90
    motion:
      enabled: true
      fps: 30
      loop: true
      ignoreOverlappingLayers: true
      includeIdAttribute: true

Motion targets переэкспортируются при каждом sync, потому что Figma Motion keyframes читаются через plugin runtime и отсутствуют в REST file JSON fingerprints. Статические targets остаются на прежнем REST/SVG/raster export path.

Публичные Plugin typings/docs Figma не открывают MP4/WebM/GIF Motion formats, но текущий Figma Motion runtime принимает нативные exportAsync formats MP4, WEBM и GIF. Export Icons использует этот native path для media formats и plugin SVG_STRING + Motion keyframes для animated SVG. ignoreOverlappingLayers мапится в contentsOnly для SVG/SVG_STRING; текущий native Motion media runtime отвергает этот ключ, поэтому GIF/MP4/WebM используют runtime defaults Figma для overlap handling.

Motion runtime probe

probe-motion проверяет, что текущий Figma plugin runtime отдаёт для Motion nodes.

Нужны SXL Studio Bridge и активная Remote Connect-сессия в Figma. Без --node-id команда использует текущий selection в Figma.

BASH
pnpm exec sxl-export-icons probe-motion \
  --format MP4,WEBM,GIF,SVG,SVG_STRING \
  --ignore-overlapping-layers \
  --include-id-attribute

Полезные опции:

  • --inspect-only читает metadata без вызова exportAsync.
  • --format <list> принимает список через запятую или повторяющиеся флаги.
  • --include-descendants включает descendant Motion keyframes, которые использует animated icon export.
  • --ignore-overlapping-layers использует contentsOnly: true для SVG / SVG_STRING; native Motion media formats используют runtime defaults Figma.
  • --include-id-attribute передаёт svgIdAttribute: true для SVG / SVG_STRING.

Конфиг v3: структура

YAML
version: 3
env:
  figmaTokenVar: FIGMA_TOKEN
bridge:
  baseUrl: http://127.0.0.1:37830
  authTokenVar: BRIDGE_AUTH_TOKEN
state:
  file: .sxl/cache/icons-state.json
safety:
  allowOutsideWorkspace: false
sources: []
targets: []

Важно про state.file:

  • дефолт схемы — assets/.cache/sxl-export-icons-state.json;
  • шаблон init/wizard предлагает .sxl/cache/icons-state.json.

Оба варианта валидны. Выберите один стабильный путь для репозитория.

Зачем нужен bridge в конфиге

bridge относится к Utils/bridge и нужен для sources[].mode: mcp, а также для targets с motion.enabled. Если вы работаете только со статическими targets через mode: rest, блок bridge игнорируется.

Мастер настройки (init --wizard)

Интерактивный мастер задаёт базовые вопросы и собирает валидный конфиг:

  • язык (en/ru, по умолчанию en);
  • создавать ли .env;
  • путь к state.file;
  • safety.allowOutsideWorkspace;
  • базовые параметры source и target;
  • включать ли font pipeline / SF Symbols;
  • включать ли фильтр по marker в description.

Команда:

BASH
pnpm exec sxl-export-icons init --wizard

Поддерживается и legacy-конфиг version: 2 (icons.config.yaml) через авто-миграцию.
Для version: 2 относительные пути резолвятся от корня workspace (поведение старого скрипта сохраняется).

sources[] — откуда читать

YAML
sources:
  - id: mono
    mode: rest # rest | mcp
    fileKeyVar: SXL_ICONS_FILE_KEY
    pageName: Monochrome
    sectionName: Actions
    downloadSpeed: 20
    selectors:
      includeNodeIds: []
      excludeNodeIds: []
      includeComponentSetNames: []
      excludeComponentSetNames: []
      includeComponentNames: []
      excludeComponentNames: []
      variantMatch:
        styleMode: outlined

Ключевые поля:

  • mode: rest — прямой Figma REST API.
  • mode: mcp — чтение через SXL Bridge Remote Connect.
  • fileKey / fileKeyVar — обязателен для REST.
  • pageName|pageId, sectionName|sectionId — фильтр области.
  • selectors.variantMatch — матч по любым variant props (не только style).
  • При sectionName|sectionId в экспорт попадают только узлы внутри выбранной секции.

Справочник selectors:

  • includeNodeIds / excludeNodeIds -> allow/deny по конкретным node ID
  • includeComponentNames / excludeComponentNames -> allow/deny по именам COMPONENT
  • includeComponentSetNames / excludeComponentSetNames -> allow/deny по именам COMPONENT_SET
  • variantMatch -> точный матч по variant props; значение может быть строкой или массивом строк

Пример variantMatch с несколькими значениями:

YAML
selectors:
  variantMatch:
    state: [default, hover]
    styleMode: outlined

Фильтр по marker в description

Можно включить фильтр экспорта по описанию COMPONENT / COMPONENT_SET:

YAML
sources:
  - id: mono
    mode: rest
    fileKeyVar: SXL_ICONS_FILE_KEY
    descriptionExportMarker:
      key: sxl-studio-export-icon
      includeWhenMissing: true

Правила:

  • если в description есть sxl-studio-export-icon: true -> экспортируется;
  • если есть sxl-studio-export-icon: false -> пропускается;
  • если marker отсутствует -> поведение берётся из includeWhenMissing.

targets[] — куда и как писать

YAML
targets:
  - id: web-icons
    mode: sync # sync | convert-local
    sourceIds: [mono]
    selectors:
      excludeComponentNames: [deprecated-icon]
    download:
      enabled: true
      pruneUntracked: false
    output:
      path: assets/icons
      format: svg
      scale: 1
      quality: 100
      qualitySize: null
      width: null
      height: null
      modeWH: null
    naming:
      case: kebab
      separator: "-"
      pattern: "{prefix}{variant.styleMode}{sectionName}{baseName}{suffix}"
      prefix: null
      suffix: null
      includeSectionName: false
      collisionStrategy: add-nodeid

Фильтрация в target:

  • targets[].selectors фильтрует элементы source перед расчетом output/naming.
  • targets[].overrides[].patch.selectors добавляет фильтр только для конкретного override-правила.

Один или несколько форматов

target.output.format задаёт один формат на один target.

Если нужно выгружать одни и те же иконки сразу в несколько форматов, создайте несколько target с одним и тем же sourceIds:

YAML
targets:
  - id: mono-svg
    sourceIds: [mono]
    output:
      path: assets/icons/svg
      format: svg
      scale: 1
      quality: 100
      qualitySize: null
      width: null
      height: null
      modeWH: null
  - id: mono-webp
    sourceIds: [mono]
    output:
      path: assets/icons/webp
      format: webp
      scale: 1
      quality: 90
      qualitySize: null
      width: null
      height: null
      modeWH: null

Нейминг и шаблоны

naming.pattern использует токены:

  • {prefix}, {suffix}
  • {sectionName}, {pageName}
  • {baseName}, {componentName}, {componentSetName}, {nodeName}, {nodeId}
  • {variant} (все variant props)
  • {variant.<propName>} (любой конкретный variant prop)

Правила для variant:

  • variant — зарезервированное имя пространства токенов (менять его нельзя).
  • <propName> — произвольное имя variant-свойства из Figma (например style, type, size, state).
  • Для нескольких props просто добавляйте несколько токенов в нужном порядке:
    • "{prefix}{pageName}{variant.style}{variant.type}{baseName}"
  • Если у конкретного узла такого props нет, этот сегмент пропускается. Это может создать коллизию имён, поэтому для mixed-наборов используйте collisionStrategy и/или добавляйте другие токены ({sectionName}, {nodeId}).

Пример:

YAML
pattern: "{prefix}{variant.styleMode}{baseName}{suffix}"

Политика коллизий:

  • error — завершить с ошибкой;
  • add-nodeid — добавить suffix из nodeId;
  • add-index — добавить порядковый индекс.

Интерактивный preflight дублей

Перед скачиванием, если обнаружены дубли имён, CLI выводит список дублей и предлагает действие:

  • refresh — перечитать source и перепроверить дубли
  • skip — оставить один узел, дубли пропустить
  • all — загрузить все дубли с автопереименованием по collisionStrategy
  • abort — остановить sync

В интерактивном терминале выбор делается клавиатурой (/ + Enter), без ручного ввода команды. В неинтерактивном режиме (CI) prompt пропускается, применяется collisionStrategy из конфига.


Переопределения (overrides)

Переопределения позволяют менять output / naming / font / sfSymbols по match-правилам:

YAML
overrides:
  - name: flags-webp
    match:
      sectionName: Flags
    patch:
      output:
        format: webp
        quality: 85
  - name: outlined-only
    match:
      variantMatch:
        styleMode: outlined
    patch:
      naming:
        suffix: outlined
      selectors:
        excludeComponentNames: [legacy-icon]
  - name: ios-symbolset-only
    match:
      pageName: iOS
    patch:
      sfSymbols:
        enabled: true
      font:
        enabled: false

Поддерживаемые поля match:

  • pageName, pageId
  • sectionName, sectionId
  • componentName, componentSetName
  • nodeId
  • variantMatch

Multi-config и multi-source

Несколько конфигов за один запуск

BASH
npx sxl-export-icons sync \
  --config packages/ds-a/sxl-export-icons.config.yaml \
  --config packages/ds-b/sxl-export-icons.config.yaml

Несколько источников в одном конфиге

YAML
sources:
  - id: mono
    mode: rest
    fileKeyVar: FILE_A
  - id: flags
    mode: rest
    fileKeyVar: FILE_B

targets:
  - id: web
    sourceIds: [mono, flags]
    output:
      path: assets/icons
      format: svg
      scale: 1
      quality: 100
      qualitySize: null
      width: null
      height: null
      modeWH: null
    naming:
      case: kebab
      collisionStrategy: add-nodeid

Для нескольких страниц/секций одного и того же Figma-файла создайте несколько source с одним fileKeyVar, но разными pageName/sectionName.


Выгрузка за пределы текущей папки проекта

По умолчанию заблокирована (безопасность).

Чтобы разрешить:

YAML
safety:
  allowOutsideWorkspace: true

allowOutsideWorkspace: false (по умолчанию) защищает от случайной записи файлов за пределы репозитория.
Включайте true, только если осознанно пишете в абсолютные/внешние пути.

После этого можно писать, например, из packages/ds/tokens в packages/ds/assets.


Font и SF Symbols

Генерация шрифта

YAML
font:
  enabled: true
  path: assets/icons/font
  formats: [woff2, woff, ttf, eot, svg]
  fontName: icons
  engine: webfonts-generator # webfonts-generator | advanced
  includeNames: [] # опционально: выборка по canonical именам
  excludeNames: [] # опционально: исключения

Генерация SF Symbols

YAML
sfSymbols:
  enabled: true
  path: assets/icons/ios

Ограничение: сложные SVG (градиенты, фильтры, маски, embedded image) пропускаются как несовместимые.

Режим только конвертации (локальные SVG -> font/SF)

Если SVG уже лежат в output-папке и нужна только конвертация:

YAML
targets:
  - id: icons-font-only
    mode: convert-local
    sourceIds: []
    download:
      enabled: false
    output:
      path: assets/icons
      format: svg
      scale: 1
      quality: 100
      qualitySize: null
      width: null
      height: null
      modeWH: null
    naming:
      case: kebab
      separator: "-"
      pattern: "{variant.style}{baseName}"
      includeSectionName: false
      collisionStrategy: add-nodeid
    font:
      enabled: true
      path: assets/icons/font
      formats: [woff2, woff, ttf, eot]
      fontName: icons
      includeNames: [filled-casino-menu, outline-casino-menu]

download.enabled: false отключает download/rename/delete и использует уже существующие локальные SVG.

font.includeNames / font.excludeNames можно указывать как с .svg, так и без расширения.


Clean и отчеты

Очистка трекаемых файлов по выбранным scope:

BASH
pnpm exec sxl-export-icons clean --config ./sxl-export-icons.config.yaml

Очистка без удаления папок шрифта/SF:

BASH
pnpm exec sxl-export-icons clean --config ./sxl-export-icons.config.yaml --skip-aux

Предпросмотр очистки:

BASH
pnpm exec sxl-export-icons clean --config ./sxl-export-icons.config.yaml --dry-run

Отчет по покрытию state:

BASH
pnpm exec sxl-export-icons report --config ./sxl-export-icons.config.yaml
pnpm exec sxl-export-icons sync --config ./sxl-export-icons.config.yaml --report ./.reports/icons-report.json

Diff и state

State хранится в state.file и ведется по scope sourceId::targetId.

Типы изменений:

  • NEW
  • UPDATED
  • DELETED
  • RENAMED
  • UNCHANGED

UPDATED также срабатывает при смене формата (например svg -> webp) или при изменении hash конфигурации scope (source + target), чтобы сделать корректный re-export. Для REST-режима используется fingerprint по render-данным узла (geometry=paths), поэтому изменения вектора (шейпа) детектируются и переэкспортируются даже при неизменном timestamp в metadata.

Первый запуск без state считает все найденные иконки NEW (полный re-export), даже если файлы уже есть на диске.

Если нужно принять уже существующие файлы как baseline UNCHANGED, используйте --adopt-existing на первом реальном запуске.

Опциональная строгая очистка:

  • включите download.pruneUntracked: true, чтобы удалять неотслеживаемые файлы того же формата в output.path;
  • используйте только для выделенных директорий (очистка работает по path+format).

Режимы источника: REST vs MCP

REST

Плюсы:

  • стабильный и предсказуемый;
  • не требует активной Remote Connect сессии для статических targets;
  • Motion targets всё равно требуют Bridge/Remote Connect, потому что keyframes читаются через plugin runtime.

MCP (Bridge)

Плюсы:

  • чтение через активный контекст плагина;
  • удобно для оркестрации сценариев с агентами.

Требования:

  • запущен Utils/bridge;
  • включен Remote Connect в SXL Studio plugin;
  • в конфиге задан bridge.baseUrl.

Если указать --allow-fallback-rest, при проблеме MCP источник автоматически переключится на REST (если есть fileKey).

Режимы target: sync vs convert-local

  • mode: sync (по умолчанию): читает Figma source, синхронизирует файлы, затем запускает пост-обработку.
  • mode: convert-local: не читает Figma source, использует уже существующие локальные SVG из output.path для генерации font/SF.

Требования для convert-local:

  • sourceIds: []
  • download.enabled: false
  • output.format: svg
  • включен font или sfSymbols

Типовые ошибки

  • Missing Figma token
    Проверьте переменную из env.figmaTokenVar.

  • outside workspace
    Установите safety.allowOutsideWorkspace: true, если это ожидаемо.

  • Bridge session is not connected
    Запустите Bridge и включите Remote Connect в плагине.

  • Naming collision detected
    Обновите naming.pattern или включите collisionStrategy: add-nodeid/add-index.


Лучшие практики

  • Храните секреты (FIGMA_TOKEN, file keys) только в .env.
  • Используйте --dry-run перед большим sync.
  • Для больших библиотек разделяйте источники (sources) по страницам.
  • Для публичных пакетов ассетов фиксируйте naming.pattern и не меняйте без migration-ноты.
  • В CI сохраняйте --report JSON как артефакт сборки.