Фигма - потрясающий дизайнерский инструмент. Самая интересная и захватывающая функция, представленная в прошлом году, - это API-интерфейс Figma. Основная идея заключается в том, что, используя REST API, разработчики могут получить массу информации из дизайн-проекта.

Есть много разных способов применить эту информацию к вашему внешнему проекту. Как получить к нему доступ и что с ним делать, мы расскажем дальше.

Как работает Figma API?

У Figma отличная документация. Вы можете создать auth token и прямо здесь опробовать API в своем проекте Figma. Однако давайте копнем немного глубже и вместе исправим это.

Проект Figma Пример.

У нас в браузере открыт документ Figma. Давайте создадим рамку. Затем нарисуйте красный квадрат с помощью инструментов формы. Затем установите его border-radius (радиус угла на панели свойств в Figma) на 6 пикселей.

У этого файла в Figma есть key. Мы можем найти его в адресной строке браузера.

Любой элемент в Figma - это узел. У каждого узла есть свойство id, дочерние узлы и другие свойства. В браузере мы можем получить идентификатор узла только для фрейма из адресной строки, выбрав любые элементы внутри.

ПРИМЕЧАНИЕ. node_id в адресной строке - это декодированное значение URI. Чтобы получить реальную ценность, вам нужно ее расшифровать. Это можно сделать с помощью метода JavaScript decodeURIComponent.

Сделайте простой запрос API, чтобы получить информацию о нашем фрейме:

GET https://api.figma.com/v1/files/B1v7c2kZ8EnvF3tLlxmT69/nodes?ids=2%3A2

ПРИМЕЧАНИЕ. для выполнения запросов к API вы должны получить личный токен доступа и использовать его, передав токен API в заголовке X-Figma-Token вашего запроса.

Получаем ответ в формате JSON:

{
  "name": "how-to-delivery-svg-from-figma-to-react",
  "nodes": {
    "2:2": {
      "document": {
        "id": "2:2",
        "name": Frame 1",
        "type": "FRAME",
        "blendMode": "PASS_THROUGH",
        "children": [
          {
            "id": "2:4",
            "name": "Rectangle 1",
            "type": "RECTANGLE",
            "blendMode": "PASS_THROUGH",
            "absoluteBoundingBox": {
              "x": 128.0,
              "y": 87.0,
              "width": 100.0,
              "height": 100.0
            },
            "constraints": {...},
            "fills": [
              {
                "blendMode": "NORMAL",
                "type": "SOLID",
                "color": {
                  "r": 1.0,
                  "g": 0.0,
                  "b": 0.0,
                  "a": 1.0
                }
              }
            ],
            "strokes": [...],
            "strokeWeight": 1.0,
            "strokeAlign": "INSIDE",
            "effects": [...],
            "cornerRadius": 6.0,
            "rectangleCornerRadii": [...]
          }
        ],
        "absoluteBoundingBox": {...},
        "constraints": {...},
        "clipsContent": true,
        "background": [...],
        "fills": [...],
        "strokes": [...],
        "strokeWeight": 1.0,
        "strokeAlign": "INSIDE",
        "backgroundColor": {...},
        "effects": [...]
      },
      "components": {...},
      "schemaVersion": 0,
      "styles": {...}
    }
  }
}

Если мы внимательно посмотрим на структуру, мы можем найти узел с id: “2:2”. Это свойство документа представляет собой фрейм представления из Figma. Подобно структуре в дизайн-проекте, первый дочерний элемент кадра - это красный квадрат, который мы нарисовали ранее.

Мы можем проанализироватьfills и cornerRadiusproperties и сгенерировать CSS:

.box {
    border-radius: 6px;
    background-color: rgba(255, 0, 0, 0);
}

Это выглядит волшебно, не правда ли? 🦄 У любого элемента есть свой id, и мы можем взять этот элемент и получить из URL всю информацию о нем и всех его дочерних элементах.

GET https://api.figma.com/v1/files/:file_key/nodes?ids=:node_id

Это дает вам возможность просматривать и извлекать любые объекты или слои, а также их свойства.

Итак, используя API Figma, мы можем просматривать и извлекать любые объекты или слои, а также их свойства и использовать их для синхронизации дизайнов Figma с компонентами React.

Однако не все так просто. Иконки SVG и локальный стиль документа - это самые простые вещи, которые можно реализовать во внешнем интерфейсе. Дизайнерские жетоны немного сложнее. А доставить сложнее всего молекулы и организмы.

Зачем нам нужно автоматизировать доставку SVG-иконок в проект React?

Используя стандартный подход к доставке ресурсов из дизайн-системы, разработчику необходимо выполнить множество рутинных действий, например, найти значок, экспортировать его, вставить в проект, и поэтому дизайнер должен постоянно уведомлять разработчика об изменениях.

Автоматизация экономит время на рутинных действиях, упрощает взаимодействие дизайнеров и разработчиков. И что самое главное, это просто ускоряет доставку товара! 🚀

Требования к SVG

Для наиболее удобного и гибкого использования значков SVG во внешнем интерфейсе каждый значок должен удовлетворять следующим требованиям:

  • Используйте квадратный атрибут viewBox, желательно 0 0 24 24
  • Используйте только один цвет (например, черный)
  • Для достижения наилучших результатов используйте элементы only<path>
  • Не использовать трансформации

Все это позволит вам легко масштабировать размер иконок и менять их цвет с помощью CSS.

Требования Figma

Требования к использованию SVG-иконок в проекте Figma следующие:

  • Иконки должны быть выполнены составными и размещаться в одном кадре.
  • Название значков в Figma будет определять имя значка во внешнем интерфейсе, поэтому дизайнер и разработчик должны более тесно взаимодействовать. Рекомендовать стиль именования параметров с маской icon-[name]. Например: icon-like.

Реагировать на требования проекта

Единственное требование - это возможность импортировать SVG прямо в React как компонент.

import { ReactComponent as Logo } from './logo.svg';
const App = () => {
  return (
    <div>
      {/* Logo is an actual React component */}
      <Logo />
    </div>
  );
}

Если вы используете create-response-app, вам понадобится [email protected] и выше, и [email protected] и выше.

Если вы используете настраиваемую конфигурацию веб-пакета, вам необходимо добавить правило для расширения .svg.

{
  test: /\.svg$/,
  use: ['@svgr/webpack', 'url-loader'],
}

Структура проекта

В корневом каталоге проекта создайте папку figma-import с отдельными зависимостями, в которой мы реализуем основной функционал для импорта значков.

Иконки будут помещены в папку src/components/Icons.

Основные настройки Figma указаны в .env файле. Чтобы загрузить переменные среды из файла .env в process.env, мы используем библиотеку dotenv.

.env
FIGMA_TOKEN="31972-e6f223ff-3ca1-4c35-ba90-548da496b4vb"
FILE_KEY="B1v7c2kZ8EkvF3tLlxmT69"
FRAME_WITH_ICONS_ID="2:3"

ПРИМЕЧАНИЕ. идентификатор кадра из адресной строки браузера - это значение в кодировке URL. Чтобы получить значение FRAME_WITH_ICONS_ID для файла .env, мы должны его декодировать.

Получение содержимого иконок

Как упоминалось ранее в требованиях Figma, все значки должны быть помещены в определенный фрейм.

Сделайте запрос API на известный URL-адрес:

GET https://api.figma.com/v1/files/B1v7c2kZ8EnvF3tLlxmT69/nodes?ids=2%3A3

Получаем ответ:

{
  "name": "how-to-delivery-svg-from-figma-to-react",
  "nodes": {
    "2:3": {
      "document": {
        "id": "2:3",
        "name": "frame-with-icons",
        "type": "FRAME",
        "blendMode": "PASS_THROUGH",
        "children": [
          {
            "id": "19:27",
            "name": "chicken",
            ...
          },
          {
            "id": "19:28",
            "name": "prawn",
            ...
          },
          {
            "id": "19:29",
            "name": "pizza",
            ...
          },
          {
            "id": "19:30",
            "name": "muffin",
            ...
          },
          {
            "id": "19:31",
            "name": "lollipop",
            ...
          }
        ],
      },
      "components": {...}
  }
}

Затем мы узнаем наш фрейм и видим значки в дочернем массиве.

Кроме того, мы должны зациклить дочерний массив throw, сохранить имя значка (это необходимо для импорта значка в качестве компонента в проект React) и сделать новый запрос для каждого значка.

GET https://api.figma.com/v1/images/B1v7c2kZ8EnvF3tLlxmT69/?ids=:icon_node_id&format=svg

Для значка с name=”lollipop” и id=”19:31”, использующего encodeURIComponent в качестве идентификатора, сделайте запрос:

GET https://api.figma.com/zwv1/images/B1v7c2kZ8EnvF3tLlxmT69/?ids=19%3A31&format=svg

Получаем ответ:

{
  "err": null,
  "images": {
    "19:31": "https://s3-us-west-2.amazonaws.com/figma-alpha-api/img/d330/8ced/303c7231dbcd55d5cc6a8f7200f6d71f"
  }
}

И по полученному URL-адресу мы можем скачать файл:

GET https://s3-us-west-2.amazonaws.com/figma-alpha-api/img/d330/8ced/303c7231dbcd55d5cc6a8f7200f6d71f

В ответ мы получаем содержимое самой иконки:

<svg width="54" height="80" viewBox="0 0 54 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M53.602 26.801C53.602 12.023 41.579 0 26.801 0C12.023 0 0 12.023 0 26.801C0 40.8971 10.9413 52.4776 24.7762 53.5182V80H29.6125V53.4537C43.0726 52.0431 53.602 40.6295 53.602 26.801ZM26.8002 48.7657C16.5441 48.7657 7.91053 41.6991 5.50448 32.1806C5.54962 21.3344 13.7447 13.1659 24.6053 13.1659C32.9971 13.1668 39.8251 19.9931 39.8251 28.3841C39.8251 34.83 34.5793 40.075 28.1326 40.0758C23.2423 40.0742 19.2645 36.0955 19.2645 31.2052C19.2645 27.5603 22.2307 24.5941 25.8757 24.5941C28.5251 24.5941 30.6813 26.751 30.6813 29.4013C30.6813 30.2976 30.3315 31.1408 29.6971 31.7759C29.0612 32.4111 28.2172 32.7609 27.3185 32.7617C25.9829 32.7625 24.9003 33.8458 24.9012 35.1807C24.902 36.5155 25.9845 37.598 27.3193 37.598C27.3193 37.598 27.3193 37.598 27.3209 37.598C29.5093 37.5972 31.5672 36.7436 33.1156 35.1968C34.6648 33.6484 35.5168 31.5889 35.5168 29.4013C35.5168 24.0854 31.1923 19.7602 25.8757 19.7586C19.5627 19.7586 14.4274 24.8939 14.4274 31.206C14.4274 38.7619 20.5751 44.9112 28.1318 44.9128C37.2457 44.9112 44.6614 37.4972 44.6614 28.3841C44.6614 17.3268 35.6643 8.33048 24.6061 8.32967C18.5648 8.32967 13.1168 10.4649 8.9463 14.0316C12.9354 8.46992 19.4491 4.83627 26.8002 4.83627C38.9118 4.83627 48.7649 14.6894 48.7649 26.801C48.7657 38.9118 38.9118 48.7657 26.8002 48.7657Z" fill="black"/>
</svg>

Таким образом, мы получили названия и содержание всех иконок, которые есть в проекте Figma.

Для работы с Figma API и получения содержимого значков вы можете создать набор методов в файле api.js. Для удобства создания HTTP-запросов воспользуемся библиотекой axios.

// figma-imports/utils/api.js
const api = require('axios');
const headers = {
  'X-FIGMA-TOKEN': process.env.FIGMA_TOKEN,
};
/**
 * api endpoint for files
 *
 */
const instanceFiles = api.create({
  baseURL: `https://api.figma.com/v1/files/${process.env.FILE_KEY}`,
  headers,
});
/**
 * api endpoint for images
 *
 */
const instanceImages = api.create({
  baseURL: `https://api.figma.com/v1/images/${process.env.FILE_KEY}`,
  headers,
});
/**
 * get Figma document info
 *
 * @return {Promise<Object>}
 */
const getDocument = async () => instanceFiles.get('/');
/**
 * get Figma node info
 *
 * @param {string} nodeId
 * @return {Promise<Object>}
 */
const getNode = async (nodeId) => instanceFiles.get(`/nodes?ids=${decodeURIComponent(nodeId)}`);
/**
 * get Figma node children
 *
 * @param {string} nodeId
 * @return {Promise<[Object]>}
 */
const getNodeChildren = async (nodeId) => {
  const {data: {nodes}} = await instanceFiles.get(`/nodes?ids=${decodeURIComponent(nodeId)}`);
  return nodes[nodeId].document.children;
};
/**
 * get svg image resource url
 *
 * @param {string} nodeId
 * @return {Promise<string>}
 */
const getSvgImageUrl = async (nodeId) => {
  const {data: {images, err}} = await instanceImages.get(`/?ids=${decodeURIComponent(nodeId)}&format=svg`);
  return images[nodeId];
};
const getIconContent = async (url) => api.get(url);
module.exports = {
  getDocument,
  getNode,
  getNodeChildren,
  getSvgImageUrl,
  getIconContent,
};

Сгенерировать компонент иконки React

Создайте шаблон для создания файла значка JSX:

// figma-import/utils/getIconJSXTemplate.js
module.exports = (name) => `
import React from 'react';
import {ReactComponent as ${name}Component} from './${name}.svg';
import Icon from '../Icon';
export const ${name} = (props) => (
  <Icon {...props}>
    <${name}Component/>
  </Icon>
);
${name}.propTypes = Icon.propTypes;
`;

Давайте создадим оболочку значка, чтобы мы могли отображать любой значок, который мы помещаем в него. Также с помощью оболочки мы можем определить набор свойств, с помощью которых мы можем изменять параметры значка, например, значения цвета и размера.

// src/components/Icons/Icon.jsx
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import './Icon.css';
const Icon = ({children, size = '100', color = 'dark', className}) => {
  return (
    <span className={classNames(
      'icon',
      `icon--size-${size}`,
      `icon--color-${color}`, className)
    }>
      {children}
    </span>
  );
};
Icon.propTypes = {
  size: PropTypes.oneOf('100', '200', '300'),
  color: PropTypes.oneOf('dark', 'light', 'accent'),
  className: PropTypes.string,
};
export default Icon;

Базовая таблица стилей оболочки:

// src/components/Icons/Icon.css
.icon {
    background-position: center;
    background-repeat: no-repeat;
    display: inline-block;
    flex-shrink: 0;
    position: relative;
    vertical-align: middle;
}
.icon svg {
    flex-shrink: 0;
    left: 50%;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 100%;
    height: 100%;
}
...

Задайте путь к папке, в которой будут располагаться значки:

// figma-import/icons.js
const IconsDir = path.resolve(__dirname, '../src/components/Icons');
const getIconFolderPath = (name) => path.resolve(IconsDir, pascalCase(name));

Получив имя иконки от узла и JSX-шаблона, сохраните файлы:

// figma-import/icons.js
/**
 * generate icon component
 * [iconName].jsx and [iconName].svg  files
 *
 * @param iconNode
 * @return {Promise<void>}
 */
const generateIcon = async (iconNode) => {
  const iconUrl = await api.getSvgImageUrl(iconNode.id);
const iconName = pascalCase(iconNode.name);
  const iconFolderPath = getIconFolderPath(iconName);
if (!fs.existsSync(iconFolderPath)) {
    fs.mkdirSync(iconFolderPath);
  }
const {data: iconContent} = await api.getImageContent(iconUrl);
  fs.writeFileSync(path.resolve(iconFolderPath, `${iconName}.svg`), iconContent, {encoding: 'utf8'});
const iconJSXTemplate = getIconJSXTemplate(iconName);
  fs.writeFileSync(path.resolve(iconFolderPath, `${iconName}.jsx`), iconJSXTemplate, {encoding: 'utf8'});
console.log(`${iconName} was written success!`);
};

В результате формируем список импортов полученных иконок:

// figma-imports/icons.js
const generateImports = (iconNodes) => {
  const fileWithImportsPath = path.resolve(IconsDir, 'index.js');
const importsContent = iconNodes
    .map(iconNode => {
      const iconName = pascalCase(iconNode.name);
return `export * from './${iconName}/${iconName}';`;
    })
    .join('\n');
fs.writeFileSync(fileWithImportsPath, importsContent, {encoding: 'utf8'});
console.log(`imports was written success!`);
};

Положил все это вместе:

// figma-imports/icons.js
const main = async () => {
  clearIconsDir();
const iconNodesArr = await api.getNodeChildren(process.env.FRAME_WITH_ICONS_ID);
iconNodesArr.forEach((iconNode) => {
    generateIcon(iconNode);
  });
await generateImports(iconNodesArr);
};

В будущем index.js можно легко расширить с помощью других скриптов для импорта сущностей из Figma, например, путем импорта токенов и локальных стилей. Однако мы можем оставить этот вопрос на другой раз в другой статье 😜.

// figma-imports/index.js
require('dotenv').config();
const importIcons = require('./icons');
const main = async () => {
  await importIcons();
};
main().catch((err) => {
  console.error('Unhandled rejection', err)
});

Добавьте скрипты npm:

// package.json
"scripts": {
  "figma-imports": "node ./figma-imports",
  "prestart": "yarn figma-imports",
  "start": "react-scripts start",
  "build": "react-scripts build"
},

Пример использования значков импорта в проекте

Вы можете найти пример полного кода в репозитории.

Что можно улучшить?

  • Распараллеливайте процессы. Этот пример был реализован в синхронном стиле для облегчения понимания. Однако для импорта 5 значков требуется довольно много времени - около 10 секунд. Это связано с тем, что вся работа, связанная с генерацией иконок, выполняется последовательно. Вы можете распараллелить обработку каждого значка и сделать это в несколько потоков. В этом нам отлично поможет библиотека bluebird. Пример асинхронной реализации доступен здесь.
  • Оптимизация SVG. Figma использует движок оптимизации изображений SVG, но мы можем легко интегрировать дополнительные инструменты синтаксического анализа и оптимизации SVG в наш поток. Пример с библиотекой SVGO доступен здесь .

Попробуйте сами

Вы можете найти пример полного кода в репо. Проверьте все ветки.

Проект Figma Пример.

Вы можете клонировать репо и попробовать пример в своем проекте Figma.

Резюме

Figma API позволяет разработчикам интерфейсов и дизайнерам тесно сотрудничать и делать разработку продукта быстрее и лучше. В результате мы можем автоматизировать очень рутинную часть вашей работы и значительно упростить процесс.

Доставка иконок в формате SVG - яркое тому подтверждение. Мы добились того, что все обновления, связанные с иконками, приходят в считанные секунды, вам просто нужно запустить скрипт.

Спасибо за ответы на мои раздражающие вопросы и помощь в понимании процесса дизайна UX / UI дизайнеру Анастасии Лапиковой 👏.

Ссылки