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

nakjv5srowi99bsjteoqabwtoz8.png

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

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

s6lt2dttycpvlqbolmyeyacaeas.png

В прошлый раз мы подготовили 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_».

k7qodfiyytkacnk1uknx_6gxhzw.png

В примере выше экспортируемые ресурсы будут сохраняться в подпапке »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-тестированию) в разных файлах ассоциируются с одним и тем же брендом:

ygug-6xnds3cvysntithaenmbfw.png

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


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

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

jv83xyzcpvzmn4snh0xakkrfu8k.png

Для создания готовых ресурсов с корректными цветами, соответствующими обрабатываемому бренду, нам понадобится провести ещё несколько манипуляций с 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 выглядит так:


http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 
 badge-feature-like
 Created with sketchtool.
 
   
     
     
   
 


то итоговый ресурс/иконка 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 (
   
     
       
       
     
   
 );
};


Как видите, мы заменили статическое значение цвета fill динамическим, берущим значения из токенов (их можно сделать доступными для компонента React через 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.

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

ojecbp2no3lxsas5uwxcqmobcji.png

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

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


http://schemas.android.com/apk/res/android"
 android:width="128dp"
 android:height="128dp"
 android:viewportWidth="128"
 android:viewportHeight="128">
 
 


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

zun48q0knfv8k9xy4amb6eqrxxa.png

Стоит отметить, что 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 после завершения процесса:

ygncd9uupdngcnav-rmkrsq1dsg.png

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

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

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


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

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

kq7ln4kr6txurb-mvh6brqbhnqe.png

Однако должен признаться, что я начал сомневаться в том, что 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), которые разработали скрипты для импорта и приёма ресурсов для своих платформ.

Спасибо, ребята! Работать с вами над проектом и претворять его в жизнь было настоящим удовольствием.

© Habrahabr.ru