Badoo corporate blog
JavaScript
Interfaces
HTML
Node.JS
March 12

Sketch + Node.js: генерируем иконки для множества платформ и брендов. Часть 2

Original author: Cristiano Rastelli
Translation


Это вторая часть статьи о создании инструмента, способного экспортировать все помещённые в Sketch-файл иконки: в разных форматах, для разных платформ, с возможностью A/B-тестирования каждой из иконок.

Первую часть вы можете прочесть по ссылке.



В прошлый раз мы подготовили Sketch-файлы, содержащие все иконки в нужных стилях и с правильными названиями. Пришёл черёд написания кода.

Достаточно сказать, что мы шли путём проб и ошибок. После того как наш тимлид Нихил Верма, заложивший основы скрипта, разработал ключевой исходный код, я запустил процесс, потребовавший не менее трёх фаз рефакторинга и множества модификаций. По этой причине я не буду вдаваться в подробности создания скрипта и сосредоточусь на том, как он работает сегодня, в финальном виде.

Скрипт сборки


Написанный на Node.js скрипт сборки достаточно прямолинеен в своей работе: импортировав зависимости, объявив список Sketch-файлов для обработки (он представляет собой список брендов, каждому из которых сопутствует список относящихся к нему файлов) и убедившись в том, что на клиенте установлен Sketch, скрипт по очереди обрабатывает бренды, проделывая с каждым из них серию действий.

  1. Берёт соответствующие брендам дизайн-токены (нам нужны значения цветов).
  2. Клонирует ассоциированные с брендом Sketch-файлы, разархивирует их, извлекая внутренние JSON-файлы, и обрабатывает некоторые из их внутренних значений (об этом чуть позже).
  3. Считывает из этих JSON-файлов необходимые метаданные (document.json, meta.json и pages/pageUniqueID.json). Нас интересуют списки общих стилей и содержащихся в файлах ресурсов/иконок.
  4. Проведя ещё несколько манипуляций с JSON-файлами, воссоздаёт архив и при помощи Sketch-файлов (клонированных и обновлённых) экспортирует и создаёт финальные выходные файлы для трёх платформ (iOS, Android, Mobile Web).

Соответствующие части скрипта сборки вы найдёте здесь:

// ... modules imports here
const SKETCH_FILES = {
 badoo: ['icons_common'],
 blendr: ['icons_common', 'icons_blendr'],
 fiesta: ['icons_common', 'icons_fiesta'],
 hotornot: ['icons_common', 'icons_hotornot'],
};
const SKETCH_FOLDER_PATH = path.resolve(__dirname, '../src/');
const SKETCH_TEMP_PATH = path.resolve(SKETCH_FOLDER_PATH, 'tmp');
const DESTINATION_PATH = path.resolve(__dirname, '../dist');
console.log('Build started...');
if (sketchtool.check()) {
 console.log(`Processing Sketch file via ${sketchtool.version()}`);
 build();
} else {
 console.info('You need Sketch installed to run this script');
 process.exit(1);
}
// ----------------------------------------
function build() {
 // be sure to start with a blank slate
 del.sync([SKETCH_TEMP_PATH, DESTINATION_PATH]);
 // process all the brands declared in the list of Sketch files
 Object.keys(SKETCH_FILES).forEach(async (brand) => {
   // get the design tokens for the brand
   const brandTokens = getDesignTokens(brand);
  
   // prepare the Sketch files (unzipped) and get a list of them
   const sketchUnzipFolders = await prepareSketchFiles({
     brand,
     sketchFileNames: SKETCH_FILES[brand],
     sketchFolder: SKETCH_FOLDER_PATH,
     sketchTempFolder: SKETCH_TEMP_PATH
   });
   // get the Sketch metadata
   const sketchMetadata = getSketchMetadata(sketchUnzipFolders);
   const sketchDataSharedStyles = sketchMetadata.sharedStyles;
   const sketchDataAssets = sketchMetadata.assetsMetadata;
   generateAssetsPDF({
     platform: 'ios',
     brand,
     brandTokens,
     sketchDataSharedStyles,
     sketchDataAssets
   });
   generateAssetsSVGDynamicMobileWeb({
     platform: 'mw',
     brand,
     brandTokens,
     sketchDataSharedStyles,
     sketchDataAssets
   });
   generateAssetsVectorDrawableDynamicAndroid({
     platform: 'android',
     brand,
     brandTokens,
     sketchDataSharedStyles,
     sketchDataAssets
   });
 });
}

На самом деле, код конвейера значительно сложнее. Причина этой сложности скрывается в функциях prepareSketchFiles, getSketchMetadata и generateAssets[format][platform]. Ниже я попробую описать их более подробно.

Подготовка Sketch-файлов


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

Файлы, ассоциированные с конкретным брендом (например, в случае с Blendr это файлы icons_common.sketch и icons_blendr.sketch) клонируются во временную папку (точнее в подпапку, названную по имени обрабатываемого бренда) и разархивируются.

Затем обрабатываются JSON-файлы. К названию ресурсов, подлежащих A/B-тестированию, добавляется префикс — таким образом, при экспорте они будут сохранены в подпапке с заранее указанным названием (соответствующим уникальному имени эксперимента). Понять, подлежит ли ресурс A/B-тестированию, можно по названию страницы, на которой он хранится: если подлежит, в названии будет содержаться префикс "XP_".



В примере выше экспортируемые ресурсы будут сохраняться в подпапке «this__is_an_experiment» с названием файла вида «icon-name[variant-name].ext».

Считывание метаданных Sketch


Второй важный этап — извлечение всех необходимых метаданных из Sketch-файлов, а точнее из внутренних JSON-файлов. Как мы увидели выше, это два основных файла (document.json и meta.json) и файлы страниц (pages/pageUniqueId.json).

Файл document.json используется для получения списка общих стилей, который появится под свойством объекта layerStyles:

{
 "_class": "document",
 "do_objectID": "45D2DA82-B3F4-49D1-A886-9530678D71DC",
 "colorSpace": 1,
 ...
 "layerStyles": {
   "_class": "sharedStyleContainer",
   "objects": [
     {
       "_class": "sharedStyle",
       "do_objectID": "9BC39AAD-CDE6-4698-8EA5-689C3C942DB4",
       "name": "features/feature-like",
       "value": {
         "_class": "style",
         "fills": [
           {
             "_class": "fill",
             "isEnabled": true,
             "color": {
               "_class": "color",
               "alpha": 1,
               "blue": 0.10588235408067703,
               "green": 0.4000000059604645,
               "red": 1
             },
             "fillType": 0,
             "noiseIndex": 0,
             "noiseIntensity": 0,
             "patternFillType": 1,
             "patternTileScale": 1
           }
         ],
         "blur": {...},
         "startMarkerType": 0,
         "endMarkerType": 0,
         "miterLimit": 10,
         "windingRule": 1
       }
     },
     ...

Мы храним основную информацию о каждом стиле в объекте формата «ключ-значение». Он будет использован позднее, когда нам понадобится извлечь название стиля на основании уникального ID (свойство do_objectID в Sketch):

const parsedSharedStyles = {};
parsedDocument.layerStyles.objects.forEach((object) => {
 parsedSharedStyles[object.do_objectID] = {
   name: object.name,
   isFill: _.get(object, 'value.fills[0].color') !== undefined,
   isBorder: _.get(object, 'value.borders[0].color') !== undefined,
 };
});

Теперь мы переходим к файлу meta.json и получаем список страниц. Нас интересуют их unique-id и name:

{
 "commit": "623a23f2c4848acdbb1a38c2689e571eb73eb823",
 "pagesAndArtboards": {
   "EE6BE8D9-9FAD-4976-B0D8-AB33D2B5DBB7": {
     "name": "Icons",
     "artboards": {
       "3275987C-CE1B-4369-B789-06366EDA4C98": {
         "name": "badge-feature-like"
       },
       "C6992142-8439-45E7-A346-FC35FA01440F": {
         "name": "badge-feature-crush"
       },
       ...
       "7F58A1C4-D624-40E3-A8C6-6AF15FD0C32D": {
         "name": "tabbar-livestream"
       }
       ...
     }
   },
   "ACF82F4E-4B92-4BE1-A31C-DDEB2E54D761": {
     "name": "XP_this__is_an_experiment",
     "artboards": {
       "31A812E8-D960-499F-A10F-C2006DDAEB65": {
         "name": "this__is_an_experiment/tabbar-livestream[variant1]"
       },
       "20F03053-ED77-486B-9770-32E6BA73A0B8": {
         "name": "this__is_an_experiment/tabbar-livestream[variant2]"
       },
       "801E65A4-3CC6-411B-B097-B1DBD33EC6CC": {
         "name": "this__is_an_experiment/tabbar-livestream[control]"
       }
     }
   },

Затем мы считываем соответствующие каждой странице JSON-файлы в папке pages (повторю, что названия файлов — вида [pageUniqueId].json) и изучаем хранящиеся на этой странице ресурсы (они выглядят как слои). Таким образом, мы получаем имя, ширину/высоту каждой иконки, метаданные Sketch по иконке данного слоя, а если мы имеем дело со страницей эксперимента, то ещё и названия A/B-теста и варианта данной иконки.

Примечание: объект page.json имеет очень сложное устройство, поэтому я не буду на нём останавливаться. Если вам интересно, что внутри, советую создать новый пустой Sketch-файл, добавить в него какой-нибудь контент и сохранить; затем переименовать его расширение в ZIP, разархивировать его и изучить один из файлов в папке pages.

В процессе обработки артбордов мы также создадим список экспериментов (и соответствующих ресурсов). Он нам понадобится, чтобы определить, какие варианты иконки использованы в каком эксперименте, — названия вариантов иконки привязаны к «базовому» объекту.

Для каждого обрабатываемого Sketch-файла, относящегося к бренду, мы создаём объект assetsMetadata, который выглядит следующим образом:

{
 "navigation-bar-edit": {
   "do_objectID": "86321895-37CE-4B3B-9AA6-6838BEDB0977",
   ...sketch_artboard_properties,
   "name": "navigation-bar-edit",
   "assetname": "navigation-bar-edit",
   "source": "icons_common",
   "width": 48,
   "height": 48
   "layers": [
     {
       "do_objectID": "A15FA03C-DEA6-4732-9F85-CA0412A57DF4",
       "name": "Path",
       ...sketch_layer_properties,
       "sharedStyleID": "6A3C0FEE-C8A3-4629-AC48-4FC6005796F5",
       "style": {
         ...
         "fills": [
           {
             "_class": "fill",
             "isEnabled": true,
             "color": {
               "_class": "color",
               "alpha": 1,
               "blue": 0.8784313725490196,
               "green": 0.8784313725490196,
               "red": 0.8784313725490196
             },
           }
         ],
         "miterLimit": 10,
         "startMarkerType": 0,
         "windingRule": 1
       },
     },
   ],
   ...
 },
 "experiment-name/navigation-bar-edit[variant]": {
   "do_objectID": "00C0A829-D8ED-4E62-8346-E7EFBC04A7C7",
   ...sketch_artboard_properties,
   "name": "experiment-name/navigation-bar-edit[variant]",
   "assetname": "navigation-bar-edit",
   "source": "icons_common",
   "width": 48,
   "height": 48
   ...

Как видите, в порядке эксперимента одной иконке (в данном случае navigation-bar-edit) может соответствовать множество ресурсов. При этом одна и та же иконка может появляться под тем же названием в другом ассоциируемом с брендом Sketch-файле. Это очень полезно: мы пользуемся этой хитростью, чтобы скомпилировать общий набор иконок, а затем определить конкретные варианты в соответствии с брендом. Именно поэтому мы объявили ассоциируемые с определённым брендом Sketch-файлы как массив:

const SKETCH_FILES = {
 badoo: ['icons_common'],
 blendr: ['icons_common', 'icons_blendr'],
 fiesta: ['icons_common', 'icons_fiesta'],
 hotornot: ['icons_common', 'icons_hotornot'],
};

В данном случае порядок имеет принципиальное значение. По сути, в вызываемой скриптом функции getSketchMetadata мы не возвращаем объекты assetsMetadata по одному на файл в виде списка. Вместо этого мы осуществляем глубокое слияние объектов — объединяем их и возвращаем единый объект assetsMetadata.

В общем, это не что иное, как «логическое» слияние Sketch-файлов и их ресурсов в едином файле. Однако логика не настолько проста, как кажется. Вот схема, которую мы создали в попытке разобраться, что происходит, когда иконки с одинаковыми названиями (и, возможно, подлежащие A/B-тестированию) в разных файлах ассоциируются с одним и тем же брендом:



Создание готовых файлов в разных форматах для разных платформ


Заключительный этап нашего процесса — непосредственно создание файлов иконок в разных форматах для разных платформ (PDF для iOS, SVG/JSX для Web и VectorDrawable для Android).

Как можно понять из количества передаваемых функциям generateAssets[format][platform] параметров, эта часть конвейера самая сложная. Именно здесь процесс начинает дробиться и меняться в зависимости от платформы. Ниже вы увидите логический ход скрипта целиком и то, как относящаяся к генерированию ресурсов часть разделяется на три похожих, но отличающихся друг от друга процесса:



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

Для генерирования файлов для Android необходимо выполнить дополнительное действие (о нём чуть позже): мы меняем свойство fill-rule каждого слоя с even-odd на non-zero (этим управляет свойство JSON-объекта windingRule, в котором 1 означает «чёт/нечет», а 0 — «не равно нулю»).

Проделав эти манипуляции, мы упаковываем JSON-файлы обратно в стандартный Sketch-файл, чтобы затем обработать и экспортировать ресурсы с обновлёнными свойствами (клонированные и обновлённые файлы — обыкновенные Sketch-файлы, их можно открывать, просматривать, редактировать, сохранять и т. д.).

После этого мы используем SketchTool (в обёртке под Node) для автоматического экспорта всех ресурсов в подходящих платформам форматах. Для каждого из ассоциируемых с брендом файлов (а вернее их клонированных и обновлённых версий) мы запускаем следующую команду:

sketchtool.run(`export slices ${cloneSketchFile} --formats=svg --scales=1 --output=${destinationFolder} --overwriting`);

Как можно догадаться, эта команда экспортирует ресурсы в папку назначения в конкретном формате, опционально применяя масштабирование (мы пока сохраняем первоначальный масштаб). Ключевой здесь является опция -overwriting: подобно тому, как мы проводим глубокое слияние объектов assetsMetadata (соответствующее «логическому» Sketch-файлов), при экспорте мы сливаем множество файлов в один каталог (относящийся к бренду/платформе). Это означает, что, если ресурс — идентифицируемый по названию слоя — уже существовал в предыдущем Sketch-файле, он будет переписан при следующем экспорте. Опять же, это не более чем обычная операция слияния.

Впрочем, в этом примере некоторые ресурсы могут оказаться «призраками». Такое происходит, когда иконка в файле подвергается A/B-тестированию, но в последующем файле перезаписывается. Тогда файлы вариантов экспортируются в папку назначения, имеют соответствующую ресурсу ссылку в объекте assetsMetadata (со своими ключом и свойствами), но не ассоциируются ни с одним базовым ресурсом (из-за глубокого слияния объектов assetsMetadata). Такие файлы будут удалены позже, перед завершением процесса.



Как уже было сказано, для разных платформ требуются разные итоговые форматы. iOS подходят PDF-файлы, и мы можем напрямую экспортировать их с помощью команды SketchTool. Для Mobile Web необходимы JSX-файлы, а для Android — VectorDrawable. По этой причине мы экспортируем ресурсы в формате SVG во временную папку и уже после этого подвергаем обработке.

PDF-файлы для iOS


Как ни странно, PDF — единственный (?) формат, который поддерживается Xcode и OS/iOS для импорта и визуализации векторных ресурсов (вот короткое объяснение данного выбора Apple).

Поскольку мы можем напрямую экспортировать в PDF через SketchTool, никаких дополнительных действий не потребуется: просто сохраните файлы непосредственно в папке назначения, и всё.

Файлы в формате React/JSX для Web


В случае с Web мы используем SVGR— библиотеку Node, позволяющую конвертировать SVG в компоненты React. Однако мы хотим делать кое-что покруче: «динамически раскрашивать» иконку во время исполнения (цвета при этом берутся из токенов). Для этого перед конвертированием мы меняем значения fill для векторов, к которым прежде применялся общий стиль, на значения из токенов, соответствующие этому стилю.

Так что если экспортированный из Sketch файл badge-feature-like.svg выглядит так:

<?xml version="1.0" encoding="UTF-8"?>
<svg width="128px" height="128px" viewBox="0 0 128 128" version="1.1" xmlns="<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>" xmlns:xlink="<a href="http://www.w3.org/1999/xlink">http://www.w3.org/1999/xlink</a>">
 <!-- Generator: sketchtool 52.2 (67145) -<a href="http://www.bohemiancoding.com/sketch"> http://www.bohemiancoding.com/sketch</a> -->
 <title>badge-feature-like</title>
 <desc>Created with sketchtool.</desc>
 <g id="Icons" fill="none" fill-rule="evenodd">
   <g id="badge-feature-like">
     <circle id="circle" fill="#E71032" cx="64" cy="64" r="64">
     <path id="Shape" fill="#FFFFFF" d="M80.4061668,..."></path>
   </g>
 </g>
</svg>

то итоговый ресурс/иконка badge-feature-like.js будет выглядеть так:

/* This file is generated automatically - DO NOT EDIT */
/* eslint-disable max-lines,max-len,camelcase */
const React = require('react');
module.exports = function badge_feature_like({ tokens }) {
 return (
   <svg data-origin="pipeline" viewBox="0 0 128 128">
     <g fill="none" fillRule="evenodd">
       <circle fill={tokens.TOKEN_COLOR_FEATURE_LIKED_YOU} cx={64} cy={64} r={64} />
       <path fill="#FFF" d="M80.4061668,..." />
     </g>
   </svg>
 );
};

Как видите, мы заменили статическое значение цвета fill динамическим, берущим значения из токенов (их можно сделать доступными для компонента React <Icon/> через Context API, но это отдельная история).

Эта замена возможна благодаря метаданным Sketch для ресурсов объекта assetsMetadata: рекурсивно пройдясь по слоям, вы можете создать селектор DOM (для примера выше это #Icons #badge-feature-like #circle) и использовать его для поиска узла в древе SVG и замены значения его атрибута fill (для этого нам нужна библиотека cheerio).

Файлы VectorDrawable для Android


Android поддерживает векторную графику с помощью кастомного векторного формата VectorDrawable. Обычно конвертирование из SVG в VectorDrawable производится прямо в Android Studio. Однако нам хотелось полностью автоматизировать процесс, поэтому мы искали способ конвертирования при помощи кода.

Изучив различные инструменты и библиотеки, мы остановились на svg2vectordrawable. Она не только активно поддерживается (во всяком случае активнее, чем все остальные), но и более функциональна, чем остальные.

Реалии таковы, что VectorDrawable и SVG не одинаковы в своей функциональности: некоторые функции SVG (к примеру, радиальные градиенты и комплексное выделение) не поддерживаются VectorDrawable, а другие начали поддерживаться совсем недавно (начиная с версии Android API 24). Одна из вытекающих из этого проблем — то, что в более старых версиях (до 24) не поддерживается значение even-odd атрибута fill-rule. Однако нам в Badoo необходима поддержка версии Android 5 и выше. Вот почему на одном из более ранних этапов мы привели fill каждого вектора в Sketch-файлах к значению non-zero.

В принципе, дизайнеры могут выполнить это действие вручную:



Но об этом легко забыть и допустить ошибку. Поэтому мы решили добавить в процесс для Android дополнительный этап, на котором все векторы в JSON автоматически конвертируются в non-zero. Это делается для того, чтобы при экспорте иконок в SVG они уже были в необходимом формате, а каждый создаваемый объект VectorDrawable поддерживался устройствами на Android 5.

Готовый файл badge-feature-like.xml при этом выглядит так:

<!-- This file is generated automatically - DO NOT EDIT -->
<vector xmlns:android="<a href="http://schemas.android.com/apk/res/android">http://schemas.android.com/apk/res/android</a>"
 android:width="128dp"
 android:height="128dp"
 android:viewportWidth="128"
 android:viewportHeight="128">
 <path
   android:fillColor="?color_feature_liked_you"
   android:pathData="M64 1a63 63 0 1 0 0 126A63 63 0 1 0 64 1z"
 />
 <path
   android:fillColor="#FFFFFF"
   android:pathData="M80.406 ..."
 />
</vector>

В файлы VectorDrawable мы вставляем переменные имена для цветов fill, которые ассоциируются с дизайн-токенами через общие стили в Android-приложениях.



Стоит отметить, что Android Studio отличается строгими требованиями к организации ресурсов: никаких вложенных папок и больших букв в названиях! Так что нам пришлось придумать новый формат для названий иконок: в случае с подлежащими тестированию ресурсами они выглядят примерно так: ic_icon-name__experiment-name__variant-name.

Словарь JSON как библиотека ресурсов


После того как файлы ресурсов сохранены в конечном формате, остаётся лишь собрать всю полученную в ходе сборки метаинформацию и сохранить её в «словарь», чтобы использовать, когда ресурсы будут импортироваться и использоваться кодовой базой различных платформ.

После извлечения плоского списка иконок из объекта assetsMetadata мы проходимся по нему, проверяя каждую из них:

  • обычный ли это ресурс (например, tabbar-livestream); если да, то просто оставляем его;
  • если это вариант для A/B-теста (например, experiment/tabbar-livestream[variant]), мы ассоциируем его название, путь, имена A/B-теста и варианта со свойством abtests
    базового ресурса (в нашем случае это tabbar-livestream), после чего удаляем запись о варианте из списка/объекта (имеет значение лишь «базовый» элемент);
  • если это «призрак», то удаляем файл и убираем запись из списка/объекта.

После завершения данного процесса словарь будет содержать список всех базовых иконок (и их A/B-тестов, если таковые имеются), и только их. Информация о каждой из них включает в себя название, размер, путь и, если иконка подлежит A/B-тестированию, информацию о различных её вариантах.

Словарь сохраняется в формате JSON в папке назначения для бренда и платформы. Вот, например, файл assets.json, сгенерированный для приложения Blendr под Mobile Web:

{
 "platform": "mw",
 "brand": "blendr",
 "assets": {
     "badge-feature-like": {
     "assetname": "badge-feature-like",
     "path": "assets/badge-feature-like.jsx",
     "width": 64,
     "height": 64,
     "source": "icons_common"
   },
   "navigation-bar-edit": {
     "assetname": "navigation-bar-edit",
     "path": "assets/navigation-bar-edit.jsx",
     "width": 48,
     "height": 48,
     "source": "icons_common"
   },
   "tabbar-livestream": {
     "assetname": "tabbar-livestream",
     "path": "assets/tabbar-livestream.jsx",
     "width": 128,
     "height": 128,
     "source": "icons_blendr",
     "abtest": {
       "this__is_an_experiment": {
         "control": "assets/this__is_an_experiment/tabbar-livestream__control.jsx",
         "variant1": "assets/this__is_an_experiment/tabbar-livestream__variant1.jsx",
         "variant2": "assets/this__is_an_experiment/tabbar-livestream__variant2.jsx"
       },
       "a_second-experiment": {
         "control": "assets/a_second-experiment/tabbar-livestream__control.jsx",
         "variantA": "assets/a_second-experiment/tabbar-livestream__variantA.jsx"
       }
     }
   },
   ...
 }
}

Теперь остаётся лишь упаковать все папки assets в ZIP-архивы для более удобного скачивания.

Итог


Описанный в статье процесс — от клонирования и манипуляций со Sketch-файлами до экспорта и конвертирования ресурсов в поддерживаемые платформами форматы и сохранения собранной метаинформации в библиотеке ресурсов — повторяется с каждым объявленным в скрипте сборки брендом.

Вот скриншот, демонстрирующий внешний вид папок src и dist после завершения процесса:



На данном этапе с помощью одной простой команды вы можете загрузить все ресурсы (JSON, ZIP и файлы ресурсов) в удалённое хранилище и сделать их доступными для всех платформ для скачивания и использования в кодовой базе.

То, как именно платформы получают и обрабатывают ресурсы (с помощью кастомных скриптов, созданных специально для этих целей), не выходит за рамки статьи. И этот вопрос наверняка будет освещён в одном из следующих постов кем-то из моих коллег.

Заключение (и усвоенные уроки)


Я всегда любил Sketch. Программа долгие годы была инструментом «по умолчанию» для разработчиков и дизайнеров. Поэтому мне было очень любопытно изучить средства интеграции вроде html-sketchapp и другие подобные инструменты, которые мы могли бы использовать в рабочем процессе и своих конвейерах.

Я, как и многие другие, всегда стремился к такому (идеальному) процессу:



Однако должен признаться, что я начал сомневаться в том, что Sketch — подходящий инструмент, особенно с учётом дизайн-системы. Поэтому я стал присматриваться к другим сервисам, таким как Figma с его открытыми API и Framer X с удобной интеграцией с React, поскольку не чувствовал у Sketch движения к интеграции с кодом (каким бы он ни был).

Так вот, этот проект меня переубедил. Не полностью, но во многом.

Пусть Sketch и не открывает свои API, но само устройство внутренней структуры его файлов служит своего рода «неофициальным» API. Создатели могли бы использовать зашифрованные названия или скрывать ключи в JSON-объектах, но вместо этого они придерживаются понятного, читабельного и концептуального соглашения о наименовании. Не думаю, что это случайность.

Тот факт, что Sketch-файлами можно управлять подобным образом, открыл мне дорогу ко множеству будущих разработок и улучшений: от плагинов для проверки наименования, стилизации и структуры слоёв для иконок до интеграции с нашей Wiki и документацией нашей дизайн-системы (обоюдной). Создав Node-приложения на Electron или Carlo, мы можем облегчить для дизайнеров процесс выполнения множества рутинных действий.

Одним из неожиданных (по крайней мере, для меня) бонусов стало то, что Sketch-файлы с иконками Cosmos стали «источником истины» — нечто похожее произошло и с дизайн-системой Cosmos. Если иконки нет, то её не существует в кодовой базе (вернее не должно существовать; но теперь мы знаем, что такие случаи — исключение). Теперь это кажется очевидным, но так было не всегда — опять же, по крайней мере, для меня.

Когда к нам пришло осознание того, что Sketch-файлами можно манипулировать, то, что начиналось как MVP-проект, обернулось глубоким погружением в их внутреннее устройство. Мы ещё не знаем, куда нас приведёт эта тропа, но пока нам сопутствует удача. Дизайнеры, разработчики, продакт-менеджеры, руководство — все сходятся в том, что нововведение позволит упростить работу каждому из нас и предотвратить возникновение многих ошибок. Кроме того, перед нами открылись двери к новым способам использования иконок.

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

Главное, чем я хотел поделиться, — что всё возможно. Может быть, другим способом, с другими подходами и другими файловыми форматами, с меньшей сложностью (например, вам может не понадобиться поддержка нескольких брендов и A/B-тестирования), но теперь вы можете автоматизировать процесс доставки иконок с кастомным скриптом Node.js и Sketch.

Ищите свой способ! Это весело и довольно просто.

Благодарности


Этот огромный проект был разработан совместно с Нихилом Вермой (Mobile Web), создавшим первую версию скрипта сборки, Артёмом Рудым (Android) и Игорем Савельевым (iOS), которые разработали скрипты для импорта и приёма ресурсов для своих платформ.

Спасибо, ребята! Работать с вами над проектом и претворять его в жизнь было настоящим удовольствием.
+26
3.8k 33
Leave a comment
Top of the day