Создаем React-компоненты иконок с помощью Figma API и SVGR. Часть 1.
Vyacheslav Konyshev
Posted on November 16, 2022
Исходники первой части
Исходники второй части
Не так давно я занимался обновлением API иконок в библиотеке React-компонентов (далее — ui-kit). Основной задачей обновления было избавиться от необходимости настраивать svg-лоадер на проекте, которой использует ui-kit (так как иконки просто лежали как svg-файлы в отдельной папке), и добавить иконкам некоторый общий API, например цветов и размеров.
Заодно было решено полностью автоматизировать процесс добавления иконок в ui-kit, так как до этого иконки вручную экспортировались из Figma и добавлялись в ui-kit.
В данной статье мы рассмотрим решение описанных выше задач на отдельном примере.
Статья разделена на две части. В первой части мы познакомимся с Figma API, а также напишем автоматизацию (далее — скрипт) для загрузки иконок из макетов. Во второй — займёмся преобразованием svg-иконок в готовые к использованию React-компоненты с помощью SVGR, а также создадим компонент SvgIcon, который будет наделять иконки некоторым общим API, и будем использовать его в SVGR Custom template.
Пример работы скрипта, который мы создали в первой части статьи:
О примере
В качестве иконок мы будем использовать общедоступные Material Design Icons. Они отлично подходят для примера, так как хорошо организованы: разбиты на группы, есть определенные правила именования.
Прежде всего я создал копию иконок Material Design Icons в Figma.
Нам не нужны все иконки. Будет достаточно несколько небольших групп на одной из страниц. Поэтому я оставил 3 небольшие группы иконок на странице Filled: Alert, Content и Editor.
В начале статьи указана ссылка на исходники первой части. Чтобы пример работал, после клонирования репозитория вам необходимо создать свою копию иконок Material Design и использовать ваш personal access token для Figma API. Тема аутентификации Figma API ещё будет затронута ниже.
Figma API
Figma API предоставляет доступ для чтения и взаимодействия с файлами Figma. Выполняя HTTP запросы по определённым эндпоинтам с параметрами, можно запрашивать файлы, изображения, версии файлов, пользователей, комментарии и т.д. Подробно ознакомиться с Figma API можно по ссылке.
Для работы с Figma API необходима аутентификация. В этом примере я буду использовать personal access token. При выполнении запросов будет достаточно указать его в заголовке x-figma-token. Подробнее о способах аутентификации можно узнать в документации.
Нас интересует получение информации о файле и получение информации об изображениях.
Получение информации о файле
Под файлом подразумевается копия иконок Material Design, созданная мною ранее.
Для начала нам необходимо узнать полный url, по которому мы сможем выполнить http запрос для получения информации о файле. Базовый url для Figma API — https://api.figma.com/
. Затем к нему добавляется эндпоинт нужного типа. Для получения информации о файле используется эндпоинт GET /v1/files/:key
, где key — уникальный идентификатор файла, который можно узнать из ссылки на файл, используя шаблон https://www.figma.com/file/:key/:title
.
В нашем случае это x2vqkyeGdrL0nnSbLslveL.
Таким образом, полный url для запроса информации о файле — https://api.figma.com/v1/files/x2vqkyeGdrL0nnSbLslveL
.
Если мы выполним GET-запрос по этому url, то получим информацию о всём файле. Нас же интересуют только иконки на странице Filled.
Иконки сгруппированы в отдельных фреймах, и у каждого фрейма есть идентификатор узла node-id, который можно узнать из url.
Как мы видим, идентификатор узла с иконками Alert — 6%3A8347.
%3A — это закодированный символ двоеточие ":". Следовательно, идентификатор в незакодированном виде — 6:8347. Таким же образом мы узнаём идентификаторы для иконок Content и Editor — 6:8877 и 6:9532 соответственно.
Чтобы получить информацию только об этих узлах, необходимо в query параметрах запроса указать ids — список идентификаторов узлов, о которых мы хотим получить информацию, через запятую.
Также можно добавить параметр depth=3, чтобы получить информацию об объектах не глубже третьего уровня вложенности: это сильно уменьшит размер ответа, при этом вся необходимая нам информация будет по-прежнему доступна. Таким образом, финальный url принимает следующий вид:
https://api.figma.com/v1/files/x2vqkyeGdrL0nnSbLslveL?ids=6:8347,6:8877,6:9532&depth=3
.
Теперь мы можем выполнить GET-запрос.
в заголовках не забываем про
x-figma-token
В ответе нас интересует только поле components. Оно содержит объект, ключи которого — идентификаторы узлов, а значения — metadata. Узлами в данном случае являются иконки. Идентификаторы мы будем использовать для получения информации об изображениях, а из metadata сможем взять name — название иконки.
Перед тем как мы перейдем к получению информации об изображениях, добавлю несколько слов об используемом подходе получения информации об иконках.
Подход с перечислением идентификаторов узлов с иконками может показаться не совсем удобным. И у него действительно есть свои минусы: таких узлов может быть много, и нужно следить за их актуальностью.
Альтернативой такому подходу может быть загрузка информации о всей странице с иконками без перечисления ids - GET https://api.figma.com/v1/files/x2vqkyeGdrL0nnSbLslveL
. Мы по-прежнему получим всю необходимую информацию в components, и при этом нет необходимости перечислять идентификаторы фреймов с иконками. Но при этом появляется риск получить в components какой-либо сторонний объект, который не является иконкой, и ссылка на его изображение будет вести на пустую страницу, что может приводить к ошибкам. Я лично сталкивался с такими проблемами, и, честно говоря, отладка таких проблем — не самое приятное занятие. Учитывайте это при выборе стратегии получения информации об иконках.
Получение информации об изображениях
Для получения информации об изображениях используется эндпоинт GET /v1/images/:key
. В параметрах запроса необходимо указать ids — идентификаторы узлов, для которых необходимы изображения — через запятую, а также format. Идентификаторы мы можем получить, взяв ключи объекта components из предыдущего запроса, а формат в нашем случае — svg.
Для примера запроса возьмём несколько первых идентификаторов: 6:8346, 6:8344 и 6:8343
Таким образом, url принимает следующий вид: https://api.figma.com/v1/images/x2vqkyeGdrL0nnSbLslveL?ids=6:8346,6:8344,6:8343&format=svg
В ответе мы получим для каждого указанного идентификатора соответствующий url для загрузки изображения в указанном формате. Пусть вас не смущает слово изображение: ссылки ведут на страницы с svg-иконками.
Их мы и будем загружать с помощью скрипта.
Загрузка иконок
Наш скрипт будет состоять из следующих шагов:
- запрашиваем информацию об иконках;
- запрашиваем изображения иконок в формате svg;
- загружаем svg-исходники иконок;
- сохраняем иконки во временной папке;
- делаем временную папку с иконками основной.
Временная папка необходима, чтобы не нарушать целостность основной папки с иконками во время выполнения скрипта на случай, если что-то пойдёт не так.
Инструменты
Используемая версия nodejs - 16.13.0.
Для выполнения шагов, описанных выше, воспользуемся Listr: он предоставляет удобный способ для выполнения последовательных (и не только) задач, а также делает красивый вывод информации о выполнении каждого шага в терминал.
Для работы с файловой системой будем использовать fs-extra, а для выполнения http-запросов — axios.
Настройки
Создадим следующую файловую структуру:
scripts/
icons/
load-icons.js // скрипт загрузки иконок
icons.config.js // настройки
figma-api.js // интерфейс для работы с Figma API
В icons.config.js вынесем константы, которые будем использовать в скрипте загрузки иконок:
module.exports = {
iconsFileKey: 'x2vqkyeGdrL0nnSbLslveL',
iconsFolder: 'icons',
iconsFramesIds: ['6:8347', '6:8877', '6:9532'],
tempIconsFolder: 'temp_icons',
personalAccessToken: 'YOUR_PERSONAL_ACCESS_TOKEN',
}
Если будете копировать пример, не забудьте заменить iconsFileKey и personalAccessToken.
скорее всего, ваш
personal access token
вы захотите хранить в переменных окружения, но для примера допустим, что он хранится так же в конфиге.
Интерфейс для работы с Figma API
В figma-api.js реализуем функцию-конструктор, которая принимает personal acess token и возвращает методы для работы с Figma API. В нашем случае нам необходимо только 2 метода: getFile и getImages. Так как мы выше уже разобрались, какие эндпоинты и с какими параметрами для этого необходимы, нам не составит труда их реализовать.
для работы с Figma API также есть множество библиотек, которые предоставляют удобный и полноценный api для работы с Figma, например figma-api
const axios = require('axios');
function FigmaApi({ personalAccessToken }) {
const instance = axios.create({
baseURL: 'https://api.figma.com/',
headers: { 'X-Figma-Token': personalAccessToken },
});
return {
getFile: (fileKey, { ids, ...opts } = {}) =>
instance
.get(`/v1/files/${fileKey}`, {
params: {
ids: ids?.join(','),
...opts,
}
})
.then(res => res.data),
getImage: (fileKey, { ids, ...opts } = {}) =>
instance
.get(`/v1/images/${fileKey}`, {
params: {
ids: ids?.join(','),
...opts,
}
})
.then(res => res.data),
}
}
module.exports = {
FigmaApi,
}
Скрипт загрузки иконок
Давайте разберем каждый шаг отдельно.
Шаг первый — запрашиваем информацию об иконках. Нас интересует только объект components
, поэтому сразу получим его через деструктуризацию:
const { components } = await figmaApi.getFile(iconsFileKey, {
ids: iconsFramesIds,
depth: 3,
});
Шаг второй — запрашиваем изображения иконок в формате svg. Для этого необходимо передать список идентификаторов иконок, а, как мы помним, ключами components
и являются эти идентификаторы.
const { images } = await figmaApi.getImage(iconsFileKey, {
ids: Object.keys(components),
format: 'svg',
});
Шаг третий — загружаем svg-исходники иконок. Так как images — это объект, значениями которого являются ссылки на страницы с иконками, сделать это можно, выполнив обычный GET-запрос для каждого значения в объекте images:
const sources = await Promise.all(
Object.keys(images).map(id => axios.get(images[id])
);
Таким образом, мы получим массив исходников для всех иконок, но уже на следующем шаге нам нужно знать название файла для каждого исходника. Поэтому мы немного модифицируем код, добавив после каждого GET-запроса создание объекта с полями fileName (используем название иконки) и source (исходники):
const sources = await Promise.all(
Object.keys(images).map(
id => axios.get(images[id]).then(res => ({
fileName: components[id].name,
source: res.data,
}))
)
);
Шаг четвертый - сохраняем иконки во временной папке.
await Promise.all(
sources.map(({ fileName, source }) =>
fs.outputFile(`${tempIconsFolder}/${fileName}.svg`, source)
)
);
Шаг пятый — делаем временную папку с иконками основной.
await fs.move(tempIconsFolder, iconsFolder, { overwrite: true });
Теперь остается собрать все шаги вместе. Каждый шаг мы будем выполнять как отдельные задачи с помощью инструмента Listr, о котором я упоминал ранее. Обмениваться информацией между шагами можно с помощью контекста ctx. В итоге мы получаем готовый скрипт загрузки иконок:
const fs = require('fs-extra');
const axios = require('axios');
const Listr = require('listr');
const { FigmaApi } = require('../figma-api');
const {
iconsFileKey,
iconsFolder,
iconsFramesIds,
personalAccessToken,
tempIconsFolder,
} = require('./icons.config')
const figmaApi = new FigmaApi({ personalAccessToken })
const tasks = new Listr([
{
title: 'Запрос информации об иконках',
task: async ctx => {
const { components } = await figmaApi.getFile(iconsFileKey, {
ids: iconsFramesIds,,
depth: 3,
});
ctx.components = components;
}
},
{
title: 'Запрос изображений иконок в формате svg',
task: async ctx => {
const { components } = ctx;
const { images } = await figmaApi.getImage(iconsFileKey, {
ids: Object.keys(components),
format: 'svg',
});
ctx.images = images;
}
},
{
title: 'Загрузка svg-исходников иконок',
task: async ctx => {
const { components, images } = ctx;
const sources = await Promise.all(
Object.keys(images).map(
id => axios.get(images[id]).then(res => ({
fileName: components[id].name,
source: res.data,
}))
)
);
ctx.sources = sources;
}
},
{
title: 'Сохранение иконок во временной папке',
task: async ctx => {
const { sources } = ctx;
await Promise.all(
sources.map(({ fileName, source }) =>
fs.outputFile(`${tempIconsFolder}/${fileName}.svg`, source)
)
);
}
},
{
title: 'Замена основной папки с иконками временной папкой',
task: async () => {
await fs.move(tempIconsFolder, iconsFolder, { overwrite: true });
}
},
])
tasks.run().catch(error => {
console.log(error);
fs.remove(tempIconsFolder);
});
В конце скрипта происходит запуск выполнения задач tasks.run() и обработка ошибок: если что-то пошло не так, выводим сообщение об ошибке и не забываем подчистить за собой, удалив временную папку с иконками.
Добавим в package.json алиас для запуска скрипта загрузки иконок:
"scripts": {
"load-icons": "node scripts/icons/load-icons"
}
Теперь можно вызвать скрипт и понаблюдать за его работой:
Заключение
Таким образом, у нас получилось полностью автоматизировать добавление иконок в проект.
Скрипт будет отличаться в зависимости от проекта, структуры макетов, договоренностей с дизайнерами и т.д. Также можно заняться его оптимизацией, например добавить режим, в котором уже загруженные иконки не будут загружаться — все в ваших руках.
В следующей части мы преобразуем иконки в готовые к использованию React-компоненты, используя библиотеку SVGR и Custom template. Также мы создадим компонент SvgIcon для наделения иконок некоторым API, после чего иконки можно будет использовать следующим образом:
import { AddAlert } from 'icons'
const Example = () => {
return <AddAlert size='large' color='warning' />
}
Posted on November 16, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
March 3, 2024